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

Tengo un control que tengo que hacer grandes modificaciones. Me gustaría evitar completamente que vuelva a dibujar mientras lo hago, SuspendLayout y ResumeLayout no son suficientes. ¿Cómo puedo suspender la pintura para un control y sus hijos?

En mi trabajo anterior tuvimos problemas para obtener nuestra rica aplicación de interfaz de usuario para pintar al instante y sin problemas. Estábamos usando controles .Net estándar, controles personalizados y controles devexpress.

Después de mucho uso de google y reflector me encontré con el mensaje WM_SETREDRAW win32. Esto realmente detiene el dibujo de controles mientras los actualiza y puede aplicarse, IIRC al panel principal / que contiene.

Esta es una clase muy simple que demuestra cómo usar este 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 corresponda, este es un ejemplo similar en VB:

 Public Module Extensions  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  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  Public Sub SuspendDrawing(ByVal Target As Control) SendMessage(Target.Handle, WM_SETREDRAW, False, 0) End Sub  Public Sub ResumeDrawing(ByVal Target As Control) ResumeDrawing(Target, True) End Sub End Module 

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

Usualmente 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 anidar suspend / reanudar llamadas. Debe asegurarse de hacer coincidir cada SuspendDrawing con un ResumeDrawing . Por lo tanto, probablemente no sea una buena idea hacerlos públicos.

Para ayudar a no olvidarte de 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(); }); 

Una buena solución sin usar interoperabilidad:

Como siempre, simplemente habilite DoubleBuffered = true en su CustomControl. Luego, si tiene contenedores como FlowLayoutPanel o TableLayoutPanel, obtenga una clase de cada uno de estos tipos y en los constructores, habilite el doble almacenamiento en búfer. Ahora, simplemente use sus Contenedores derivados en lugar de los Contenedores Windows.Forms.

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

Aquí hay una combinación de ceztko y ng5000 para traer una versión de extensiones VB que no use pinvoke

 Imports System.Runtime.CompilerServices Module ControlExtensions Dim WM_SETREDRAW As Integer = 11 '''  ''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called '''  '''  '''   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 '''  ''' Resume from SuspendPaint method '''  '''  '''   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 

Sé que esta es una vieja pregunta, ya respondida, pero aquí está mi opinión sobre esto; Refactoré la suspensión de actualizaciones en un IDisposable, de esa manera puedo adjuntar las declaraciones que quiero ejecutar en una statement de 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(); } } 

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 

Utilizar:

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

Esto es incluso más simple, y tal vez hacky, ya que puedo ver mucho músculo GDI en este hilo , y obviamente solo es una buena opción para ciertos escenarios. YMMV

En mi caso, utilizo lo que denominaré UserControl “principal” y, durante el evento Load , simplemente elimino el control a ser manipulado de la colección .Controls la .Controls , y el contenido principal de Parent se encarga de pintando completamente el control del niño de cualquier manera especial … quitando completamente las capacidades de pintura del niño fuera de línea.

Ahora, entrego la rutina de pintura de mi hijo a un método de extensión basado en este concepto de Mike Gold para imprimir formularios de Windows .

Aquí necesito un subconjunto de tags para que se vuelvan perpendiculares al diseño:

diagrama simple de su IDE de Visual Studio

Luego, exijo que el control secundario no sea 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 ParentUserControl en alguna parte, por ejemplo, Windows Form, descubro que Visual Studio 2015 muestra el formulario correctamente tanto en Design Time como en runtime: ParentUserControl alojado en un formulario de Windows o tal vez otro control de usuario

Ahora, dado que mi manipulación particular gira el control infantil 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 por una etiqueta de paquete que necesitaba una vista previa e impresión, que funcionó bien para mí.

Si hay formas de reintroducir los puntos calientes y el control a mi control intencionalmente huérfano, me encantaría aprender sobre eso algún día (no para este escenario, por supuesto, pero … solo para aprender). Por supuesto, WPF apoya esa locura OOTB … pero … hey … WinForms es muy divertido todavía, ¿verdad?

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