Comment puis-je suspendre la peinture pour un contrôle et ses enfants?

j'ai un contrôle sur lequel je dois faire de grandes modifications. J'aimerais l'empêcher complètement de se redessiner pendant que je le fais - SuspendLayout et ResumeLayout ne sont pas assez. Comment puis-je suspendre la peinture pour un contrôle et ses enfants?

171
demandé sur Simon 2009-01-28 16:46:34

10 réponses

à mon travail précédent, nous avons eu du mal à obtenir notre application D'interface utilisateur riche pour peindre instantanément et en douceur. Nous utilisions les commandes standard .Net, les commandes personnalisées et les commandes devexpress.

après beaucoup d'utilisation de googling et de réflecteur, je suis tombé sur le message win32 WM_SETRAW. Cela arrête vraiment le dessin des commandes pendant que vous les mettez à jour et peut être appliqué, IIRC au panneau parent/containing.

il s'agit d'une classe très très simple démontrant comment pour utiliser ce message:

class DrawingControl
{
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);

    private const int WM_SETREDRAW = 11; 

    public static void SuspendDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
    }

    public static void ResumeDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
        parent.Refresh();
    }
}

Il ya des discussions plus complètes sur ce-google pour C# et WM_SETREW, par exemple

C# Jitter

Suspension De Mises En Page

et à qui cela peut concerner, c'est un exemple similaire dans VB:

Public Module Extensions
    <DllImport("user32.dll")>
    Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
    End Function

    Private Const WM_SETREDRAW As Integer = 11

    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, 0)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
    End Sub

    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module
278
répondu ng5000 2018-05-22 23:30:29

ce qui suit est la même solution de ng5000 mais n'utilise pas P/Invoke.

public static class SuspendUpdate
{
    private const int WM_SETREDRAW = 0x000B;

    public static void Suspend(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }

    public static void Resume(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);

        control.Invalidate();
    }
}
50
répondu ceztko 2011-06-10 04:36:33

pour aider à ne pas oublier de reenable dessin:

public static void SuspendDrawing(Control control, Action action)
{
    SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    action();
    SendMessage(control.Handle, WM_SETREDRAW, true, 0);
    control.Refresh();
}

utilisation:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
11
répondu Jonathan H 2015-03-10 11:40:42

une solution agréable sans utiliser interop:

comme toujours, il vous suffit d'activer DoubleBuffered=true sur votre CustomControl. Ensuite, si vous avez des conteneurs comme FlowLayoutPanel ou TableLayoutPanel, dérivez une classe de chacun de ces types et dans les constructeurs, activez double buffering. Maintenant, utilisez simplement vos conteneurs dérivés au lieu des fenêtres.Formulaires Conteneurs.

class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
    public TableLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
8
répondu Eugenio De Hoyos 2010-03-10 22:53:44

voici une combinaison de ceztko et ng5000 pour apporter une version VB extensions qui n'utilise pas pinvoke

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)

    Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgSuspendUpdate)

End Sub

''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)

    Dim wparam As New System.IntPtr(1)
    Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgResumeUpdate)

    ctrl.Invalidate()

End Sub

End Module
4
répondu goughy000 2012-03-12 15:47:57

je sais que c'est une vieille question, déjà répondu, Mais voici mon point de vue sur ce; j'ai refactorisé la suspension des mises à jour dans un IDisposable - de cette façon, je peux joindre les déclarations que je veux exécuter dans une déclaration using .

class SuspendDrawingUpdate : IDisposable
{
    private const int WM_SETREDRAW = 0x000B;
    private readonly Control _control;
    private readonly NativeWindow _window;

    public SuspendDrawingUpdate(Control control)
    {
        _control = control;

        var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

        _window = NativeWindow.FromHandle(_control.Handle);
        _window.DefWndProc(ref msgSuspendUpdate);
    }

    public void Dispose()
    {
        var wparam = new IntPtr(1);  // Create a C "true" boolean as an IntPtr
        var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);

        _window.DefWndProc(ref msgResumeUpdate);

        _control.Invalidate();
    }
}
3
répondu Scott Baker 2015-01-05 23:56:50

basé sur la réponse de ng5000, j'aime utiliser cette extension:

        #region Suspend
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
        private const int WM_SETREDRAW = 11;
        public static IDisposable BeginSuspendlock(this Control ctrl)
        {
            return new suspender(ctrl);
        }
        private class suspender : IDisposable
        {
            private Control _ctrl;
            public suspender(Control ctrl)
            {
                this._ctrl = ctrl;
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
            }
            public void Dispose()
            {
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
                this._ctrl.Refresh();
            }
        }
        #endregion

utiliser:

using (this.BeginSuspendlock())
{
    //update GUI
}
3
répondu Koray 2016-10-28 16:07:53

C'est encore plus simple, et peut - être hacky-comme je peux voir beaucoup de muscle GDI sur ce fil , et est évidemment seulement un bon ajustement pour certains scénarios. YMMV

dans mon scénario, j'utilise ce que je vais appeler un contrôle D'utilisateur "Parent" - et pendant l'événement Load , je supprime tout simplement le contrôle-à-être-manipulé de la collection .Controls du Parent, et le OnPaint du Parent s'occupe de tout peindre le contrôle de l'enfant d'une manière spéciale.. enlever complètement les capacités de peinture de l'enfant.

maintenant, je passe ma routine de peinture d'enfant à une méthode d'extension basée sur ce concept de Mike Gold pour l'impression des formes de fenêtres .

J'ai besoin D'un sous-ensemble d'étiquettes pour rendre perpendiculaire au layout:

simple diagram of your Visual Studio IDE

alors, je dispense le contrôle de l'enfant d'être peint, avec ce code dans le ParentUserControl.Load event handler:

Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    SetStyle(ControlStyles.UserPaint, True)
    SetStyle(ControlStyles.AllPaintingInWmPaint, True)

    'exempt this control from standard painting: 
    Me.Controls.Remove(Me.HostedControlToBeRotated) 
End Sub

puis, dans le même ParentUserControl, nous peignons le contrôle-à-être-manipulé à partir de la base:

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    'here, we will custom paint the HostedControlToBeRotated instance...

    'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
    e.Graphics.RotateTransform(-90)
    MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)

    e.Graphics.ResetTransform()
    e.Graphics.Dispose()

    GC.Collect()
End Sub

une fois que vous hébergez le ParentUserControl quelque part, par exemple un formulaire Windows - je constate que mon Visual Studio 2015 rend le formulaire correctement à la conception Temps ainsi que de l'exécution: ParentUserControl hosted in a Windows Form or perhaps other user control

maintenant, puisque ma manipulation particulière fait tourner le contrôle de l'enfant à 90 degrés, je suis sûr que tous les points chauds et l'interactivité ont été détruits dans cette région - mais, le problème que je résolvais était tout pour une étiquette de paquet qui avait besoin de prévisualiser et imprimer, qui a fonctionné très bien pour moi.

S'il y a des moyens de réintroduire les points chauds et le contrôle à mon intention contrôle orphelin - j'aimerais apprendre à ce sujet un jour (pas pour ce scénario, bien sûr, mais.. tout juste de l'apprendre). Bien sûr, la WPF soutient une telle folie OOTB.. mais.. Hey.. WinForms est toujours si amusant, n'est-ce pas?

2
répondu bkwdesign 2016-01-08 17:25:36

ou utilisez simplement Control.SuspendLayout() et Control.ResumeLayout() .

-4
répondu JustJoost 2012-10-26 19:24:47