¿Cómo suspendo la pintura para un control y sus hijos?


Tengo un control al que tengo que hacer grandes modificaciones. Me gustaría evitar completamente que se vuelva a dibujar mientras hago eso: Suspender el despliegue y reanudar el despliegue no son suficientes. ¿Cómo suspendo la pintura para un control y sus hijos?

Author: Simon, 2009-01-28

10 answers

En mi trabajo anterior tuvimos problemas para conseguir que nuestra aplicación de interfaz de usuario enriquecida pintara al instante y sin problemas. Usábamos controles. Net estándar, controles personalizados y controles devexpress.

Después de mucho googleo y uso de reflectores me encontré con el mensaje WM_SETREDRAW win32. Esto realmente detiene los controles de dibujo mientras los actualiza y se puede aplicar, IIRC al panel padre / que contiene.

Esta es una clase muy, muy simple que demuestra cómo usar esto mensaje:

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

Hay discusiones más completas sobre esto - google para C# y WM_SETREDRAW, por ejemplo,

C # Jitter

Suspensión de diseños

Y a quien le concierne, este es un ejemplo similar en 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
Author: ng5000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-22 23:30:29

La siguiente es la misma solución de ng5000 pero no usa 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
Author: ceztko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-10 04:36:33

Normalmente uso una pequeña versión modificada de la respuesta 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();
        }
    }
}

Esto permite que las llamadas suspend/resume sean anidadas. Debe asegurarse de hacer coincidir cada SuspendDrawing con un ResumeDrawing. Por lo tanto, probablemente no sería una buena idea hacerlos públicos.

 15
Author: Ozgur Ozcitak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:32

Para ayudar a no olvidar volver a habilitar el dibujo:

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

Uso:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
 11
Author: Jonathan H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-10 11:40:42

Una buena solución sin usar interop:

Como siempre, simplemente habilite DoubleBuffered=true en su CustomControl. Luego, si tiene algún contenedor como FlowLayoutPanel o TableLayoutPanel, derive una clase de cada uno de estos tipos y en los constructores, habilite el doble buffering. Ahora, simplemente use sus contenedores derivados en lugar de las Ventanas.Formas Contenedores.

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

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
 8
Author: Eugenio De Hoyos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-10 22:53:44

Aquí hay una combinación de ceztko y ng5000 para traer una versión de extensiones VB que no usa 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
Author: goughy000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-12 15:47:57

Sé que esta es una vieja pregunta, ya respondida, pero aquí está mi opinión sobre esto; refactoricé la suspensión de actualizaciones en un identificable - de esa manera puedo adjuntar las sentencias que quiero ejecutar en una sentencia 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
Author: Scott Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-05 23:56:50

Basado en la respuesta de ng5000, me gusta usar esta extensión:

        #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

Uso:

using (this.BeginSuspendlock())
{
    //update GUI
}
 3
Author: Koray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-28 16:07:53

Esto es aún más simple, y tal vez hacky - como puedo ver una gran cantidad de músculo GDI en este hilo, y es obviamente solo un buen ajuste para ciertos escenarios. YMMV

En mi escenario, utilizo lo que me referiré como un control de Usuario "Padre" - y durante el evento Load, simplemente elimino el control-a-ser-manipulado de la colección del Padre .Controls, y el Padre OnPaint se encarga de pintar completamente el control hijo de cualquier manera especial.. tomando completamente la capacidades de pintura sin conexión.

Ahora, paso mi rutina de pintura infantil a un método de extensión basado en este concepto de Mike Gold para imprimir formularios de windows.

Aquí necesito un subconjunto de etiquetas para renderizar perpendicular al diseño:

diagrama simple de su IDE de Visual Studio

Entonces, eximo el control hijo de ser pintado, con este código en el controlador de eventos ParentUserControl.Load:

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

Luego, en el mismo ParentUserControl, pintamos el control-a-ser-manipulado desde cero:

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

Una vez que aloja el ParentUserControl en algún lugar, por ejemplo, un Formulario de Windows, me doy cuenta de que mi Visual Studio 2015 renderiza el formulario correctamente tanto en tiempo de diseño como en tiempo de ejecución: ParentUserControl alojado en un formulario de Windows o tal vez otro control de usuario

Ahora, dado que mi manipulación particular gira el control hijo 90 grados, estoy seguro de que todos los puntos calientes y la interactividad se han destruido en esa región, pero el problema que estaba resolviendo era todo para una etiqueta de paquete que necesitaba previsualizar e imprimir, lo que funcionó bien para mí.

Si hay formas de reintroducir los puntos calientes y el control en mi control huérfano a propósito, me encantaría aprender sobre eso algún día (no para este escenario, por supuesto, pero.. solo para aprender). Por supuesto, WPF apoya tal locura OOTB.. pero.. Hola.. WinForms es tan divertido todavía, amiright?

 2
Author: bkwdesign,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-08 17:25:36

O simplemente use Control.SuspendLayout() y Control.ResumeLayout().

 -4
Author: JustJoost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-26 19:24:47