我如何中止绘画控制及其子女?

我有一个控制,我必须做大的修改。 我想完全防止它重绘,而我这样做 – SuspendLayout和ResumeLayout是不够的。 我如何中止绘画控制及其子女?

在我以前的工作中,我们努力让我们的丰富的UI应用程序即时和平稳地绘制。 我们使用标准的.Net控件,自定义控件和devexpress控件。

经过大量的谷歌search和reflection器使用,我遇到了WM_SETREDRAW win32消息。 这确实停止控制绘图,而你更新它们,可以应用,IIRC到父/包含面板。

这是一个非常简单的类,演示如何使用此消息:

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

有更完整的讨论 – 谷歌的C#和WM_SETREDRAW,例如

C#抖动

暂停布局

对于它可能关心的人来说,这在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 

以下是与ng5000相同的解决scheme,但不使用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(); } } 

我通常使用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(); } } } 

这允许挂起/恢复呼叫被嵌套。 您必须确保将每个SuspendDrawingResumeDrawing进行匹配。 因此,把它们公诸于众不是一个好主意。

帮助不忘记重新启用绘图:

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

用法:

 SuspendDrawing(myControl, () => { somemethod(); }); 

一个不使用interop的好解决scheme:

与往常一样,只需在您的CustomControl上启用DoubleBuffered = true即可。 然后,如果你有像FlowLayoutPanel或TableLayoutPanel这样的容器,从这些types和构造函数中派生一个类,启用双缓冲。 现在,只需使用派生容器而不是Windows.Forms容器。

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

这是一个ceztko和ng5000的组合,带来一个VB扩展版本,不使用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 

我知道这是一个老问题,已经回答了,但这是我的承诺。 我将更新暂停重构为一个IDisposable – 这样我就可以把我要运行的语句放在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(); } } 

这更简单了, 也许还有点怪异 – 正如我在这个主题上看到很多GDI肌肉一样显然只适合某些场景。 因人而异

在我的场景中,我使用了我将引用的“Parent”UserControl – 在Load事件期间,我只是从Parent的.Controls集合中删除了要操纵的控件,而Parent的OnPaint负责完全以任何特殊的方式绘制孩子的控制权。充分把孩子的绘画能力脱机。

现在,我把我的孩子绘画例程交给基于迈克·戈尔德(Mike Gold)这个概念的扩展方法来打印Windows窗体 。

在这里,我需要一个标签的子集垂直于布局呈现:

您的Visual Studio IDE的简单图

然后,我用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 

然后,在同一个ParentUserControl中,我们从头开始绘制要操纵的控件:

 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 

一旦您将ParentUserControl托pipe在某个地方,例如Windows窗体 – 我发现我的Visual Studio 2015在devise时和运行时都正确地呈现窗体ParentUserControl托管在Windows窗体或其他用户控件中

现在,由于我的特殊操作把孩子的控制旋转了90度,我确信所有的热点和交互性已经在那个地区被破坏了 – 但是我解决的问题是所有需要预览和打印的包装标签,这对我来说工作得很好。

如果有办法把热点和控制权重新引入到我故意孤立的控制中 – 我很想知道有一天(当然不是这个场景,但是只是为了学习)。 当然,WPF支持这种疯狂的OOTB ..但是..嘿.. WinForms还是那么有趣,amiright?

根据ng5000的回答,我喜欢使用这个扩展:

  #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 

使用:

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

或者只使用Control.SuspendLayout()Control.ResumeLayout()