我有一个控件,我必须做大量修改.当我这样做时,我想完全阻止它重绘 - SuspendLayout和ResumeLayout是不够的.如何为控件及其子项暂停绘画?
在我之前的工作中,我们努力让我们丰富的UI应用程序能够即时,流畅地进行绘制.我们使用标准的.Net控件,自定义控件和devexpress控件.
经过大量的谷歌搜索和反射器使用后,我遇到了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中的类似示例:
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
以下是ng5000的相同解决方案,但不使用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(); } } }
这允许挂起/恢复调用.你必须确保每个都SuspendDrawing
与a 匹配ResumeDrawing
.因此,公开它们可能不是一个好主意.
为了帮助不忘记重新绘图:
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(); });
不使用互操作的好方案:
与往常一样,只需在CustomControl上启用DoubleBuffered = true即可.然后,如果您有任何容器,如FlowLayoutPanel或TableLayoutPanel,从这些类型和构造函数中派生类,则启用双缓冲.现在,只需使用派生的容器而不是Windows.Forms容器.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel { public TableLayoutPanel() { DoubleBuffered = true; } } class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel { public FlowLayoutPanel() { DoubleBuffered = true; } }
基于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 }