c# - studio - ¿Cómo puedo suspender la pintura para un control y sus hijos?
paint visual studio c# (10)
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
'''''' <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
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?
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
}
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
Y a quien corresponda, este es un ejemplo similar en VB:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Integer, _
ByVal wMsg As Integer, _
ByVal wParam As Integer,
ByVal lParam As Integer) As Integer
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, 1, 0)
If Redraw Then
Target.Refresh()
End If
End Sub
<Extension()>
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Target.Handle, WM_SETREDRAW, 0, 0)
End Sub
<Extension()>
Public Sub ResumeDrawing(ByVal Target As Control)
ResumeDrawing(Target, True)
End Sub
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 etiquetas para que se vuelvan perpendiculares al diseño:
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:
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?
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();
}
}
O simplemente use Control.SuspendLayout()
y Control.ResumeLayout()
.
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();
});
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 declaración 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();
}
}
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;
}
}
Usualmente uso una pequeña versión modificada de la answer 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.