C# 如何暂停控件及其子控件的绘制?
我有一个控制,我必须作出重大修改。我想在我这样做的时候完全阻止它重新绘制-SuspendLayout和ResumeLayout是不够的。如何暂停控件及其子控件的绘制?在我之前的工作中,我们一直在努力让我们的富UI应用程序能够立即、顺利地绘制。我们使用标准的.Net控件、自定义控件和devexpress控件 在大量使用谷歌和reflector之后,我发现了WM_SETREDRAW win32消息。这实际上会在更新控件时停止控件的绘制,并且可以将其应用到父/包含面板 这是一个非常简单的类,演示如何使用此消息:C# 如何暂停控件及其子控件的绘制?,c#,.net,winforms,paint,C#,.net,Winforms,Paint,我有一个控制,我必须作出重大修改。我想在我这样做的时候完全阻止它重新绘制-SuspendLayout和ResumeLayout是不够的。如何暂停控件及其子控件的绘制?在我之前的工作中,我们一直在努力让我们的富UI应用程序能够立即、顺利地绘制。我们使用标准的.Net控件、自定义控件和devexpress控件 在大量使用谷歌和reflector之后,我发现了WM_SETREDRAW win32消息。这实际上会在更新控件时停止控件的绘制,并且可以将其应用到父/包含面板 这是一个非常简单的类,演示如何使
类绘图控件
{
[DllImport(“user32.dll”)]
公共静态外部int SendMessage(IntPtr hWnd、Int32 wMsg、bool wParam、Int32 lParam);
私有常量int WM_SETREDRAW=11;
公共静态无效SuspendDrawing(控件父级)
{
SendMessage(parent.Handle,WM_SETREDRAW,false,0);
}
公共静态图形(控制父级)
{
SendMessage(parent.Handle,WM_SETREDRAW,true,0);
parent.Refresh();
}
}
关于这一点有更充分的讨论——例如,谷歌的C#和WM#U SETREDRAW
对于可能涉及的人,这是VB中的类似示例:
公共模块扩展
私有函数SendMessage(ByVal hWnd作为IntPtr,ByVal Msg作为Integer,ByVal wParam作为Boolean,ByVal lParam作为IntPtr)作为Integer
端函数
私有常量WM_SETREDRAW为整数=11
“控制的扩展方法”
公共子图(ByVal目标为控件,ByVal重绘为布尔值)
SendMessage(Target.Handle,WM_SETREDRAW,True,0)
如果重新绘制,则
Target.Refresh()
如果结束
端接头
公共子SuspendDrawing(ByVal目标作为控件)
SendMessage(Target.Handle,WM_SETREDRAW,False,0)
端接头
公共子图(ByVal目标作为控件)
恢复绘图(目标,真)
端接头
端模块
我通常使用ngLink的一些修改版本
这允许嵌套挂起/恢复调用。您必须确保将每个
SuspendDrawing
与ResumeDrawing
匹配。因此,将它们公开可能不是一个好主意。一个不使用互操作的好解决方案:
一如既往,只需在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的相同解决方案,但不使用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();
}
}
或者只需使用
Control.SuspendLayout()
和Control.resumealayout()
以下是ceztko和ng5000的组合,以提供一个不使用pinvoke的VB扩展版本
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
导入System.Runtime.CompilerServices
模块控制扩展
Dim WM_SETREDRAW为整数=11
'''
''更强的“SuspendLayout”完全保持控件绘制,直到调用ResumePaint
'''
'''
'''
公共子SuspendPaint(按Windows.Forms.Control的ByVal ctrl键)
Dim msgSuspendUpdate As Windows.Forms.Message=Windows.Forms.Message.Create(ctrl.Handle、WM_SETREDRAW、System.IntPtr.Zero、System.IntPtr.Zero)
将窗口调暗为Windows.Forms.NativeWindow=Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)
window.DefWndProc(msgSuspendUpdate)
端接头
'''
''从SuspendPaint方法恢复
'''
'''
'''
公共子ResumePaint(按Windows.Forms.Control的ByVal ctrl键)
作为新系统的Dim wparam。IntPtr(1)
Dim msgResumeUpdate为Windows.Forms.Message=Windows.Forms.Message.Create(ctrl.Handle、WM_SETREDRAW、wparam、System.IntPtr.Zero)
将窗口调暗为Windows.Forms.NativeWindow=Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)
window.DefWndProc(msgResumeUpdate)
ctrl.Invalidate()
端接头
端模块
我知道这是一个古老的问题,已经得到了回答,但以下是我对这个问题的看法;我将暂停更新重构为IDisposable,这样我就可以使用语句将我想要运行的语句封装在一个中
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();
}
}
要帮助避免忘记重新启用图形,请执行以下操作:
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();
});
这甚至更简单,可能有点骇人听闻——因为我可以在这个线程上看到很多GDI肌肉,而且显然只适合某些场景。YMMV
在我的场景中,我使用我将称之为“父”的用户控件-在加载事件期间,我只需从父控件的集合中删除要操作的控件,父控件的OnPaint
以任何特殊方式完全绘制子控件。。完全让孩子的绘画能力离线
现在,我将我的孩子绘画例程交给一个基于此的扩展方法
在这里,我需要一组标签来渲染垂直于布局的:
然后,我在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托管在某个位置,例如Windows窗体,我发现我的Visual Studio 2015在设计时和运行时都能正确呈现窗体:
现在,由于我的特殊操作将子控件旋转90度,我确信该区域的所有热点和交互性都已被破坏-但是,我解决的问题是需要预览和打印的包装标签,这对我来说很好
如果有办法重新引入热点并控制我的注意力,我会有意识地去做
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
#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
}