web-dev-qa-db-fra.com

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

J'ai un contrôle sur lequel je dois apporter d'importantes modifications. J'aimerais éviter complètement de le redessiner - SuspendLayout et ResumeLayout ne suffisent pas. Comment suspendre la peinture pour un contrôle et ses enfants?

178
Simon

À mon emploi précédent, nous avions du mal à faire peindre notre application d'interface utilisateur riche instantanément et en douceur. Nous utilisions des contrôles .Net, des contrôles personnalisés et des contrôles devexpress standard.

Après de nombreuses recherches sur Google et l'utilisation du réflecteur, je suis tombé sur le message WM_SETREDRAW win32. Cela arrête vraiment le dessin des contrôles lorsque vous les mettez à jour et peut être appliqué, IIRC au panneau parent/contenant.

C'est une classe très très simple montrant comment 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 y a de plus amples discussions à ce sujet - google pour C # et WM_SETREDRAW, par exemple.

C # Jitter

Suspendre les mises en page

Et à qui de droit, ceci 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
294
ng5000

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();
    }
}
51
ceztko

J'utilise généralement une version légèrement modifiée de réponse de ngLink.

public class MyControl : Control
{
    private int suspendCounter = 0;

    private void SuspendDrawing()
    {
        if(suspendCounter == 0) 
            SendMessage(this.Handle, WM_SETREDRAW, false, 0);
        suspendCounter++;
    }

    private void ResumeDrawing()
    {
        suspendCounter--; 
        if(suspendCounter == 0) 
        {
            SendMessage(this.Handle, WM_SETREDRAW, true, 0);
            this.Refresh();
        }
    }
}

Cela permet d'imbriquer/reprendre les appels. Vous devez vous assurer de faire correspondre chaque SuspendDrawing avec un ResumeDrawing. Par conséquent, ce ne serait probablement pas une bonne idée de les rendre publics.

15
Ozgur Ozcitak

Pour aider à ne pas oublier de réactiver le 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();
}

usage:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
13
Jonathan H

Une solution intéressante sans interop:

Comme toujours, activez simplement DoubleBuffered = true sur votre CustomControl. Ensuite, si vous avez des conteneurs tels que FlowLayoutPanel ou TableLayoutPanel, dérivez une classe de chacun de ces types et, dans les constructeurs, activez la mise en mémoire tampon double. Maintenant, utilisez simplement vos conteneurs dérivés au lieu des conteneurs Windows.Forms.

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

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
8
Eugenio De Hoyos

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

Utilisation:

using (this.BeginSuspendlock())
{
    //update GUI
}
4
Koray

Voici une combinaison de Ceztko et de ng5000 pour apporter une version de 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
3
goughy000

Je sais que c'est une vieille question à laquelle j'ai déjà répondu, mais voici ce que je pense de ceci. J'ai refactoré la suspension des mises à jour dans un IDisposable - de cette façon, je peux inclure les instructions que je veux exécuter dans une instruction 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
Scott Baker

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

Dans mon scénario, j'utilise ce que j'appellerai un contrôle de parent "parent" - et lors de l'événement Load, je supprime simplement le contrôle à manipuler de la .Controls _ collection, et le OnPaint du parent se charge de peindre complètement le contrôle enfant de quelque manière que ce soit .. complètement en mettant les capacités de peinture de l'enfant hors ligne.

Maintenant, je laisse ma routine Paint enfant à une méthode d’extension basée sur ceci concept de Mike Gold pour l’impression de formulaires Windows .

Il me faut ici un sous-ensemble d’étiquettes pour rendre perpendiculairement la mise en page:

simple diagram of your Visual Studio IDE

Ensuite, j'exempte le contrôle enfant d'être peint, avec ce code dans le ParentUserControl.Load gestionnaire d'événements:

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

Ensuite, dans le même ParentUserControl, nous peignons le contrôle à manipuler à 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 bien le formulaire correctement au moment de la conception et de l'exécution: ParentUserControl hosted in a Windows Form or perhaps other user control

Maintenant, puisque ma manipulation particulière fait pivoter le contrôle enfant de 90 degrés, je suis sûr que tous les points chauds et l’interactivité ont été détruits dans cette région. ce qui a bien fonctionné pour moi.

S'il existe des moyens de réintroduire les points chauds et le contrôle dans mon contrôle volontairement orphelin - j'aimerais en apprendre un jour à ce sujet (pas pour ce scénario, bien sûr, mais ... juste pour apprendre). Bien sûr, WPF supporte une telle folie OOTB .. mais .. hé .. WinForms est tellement amusant encore, amiright?

2
bkwdesign