我有一个控件,我必须对其进行较大的修改。我想完全阻止它重绘,而SuspendLayout和ResumeLayout不够。如何为控件及其子控件暂停绘画?
我有一个控件,我必须对其进行较大的修改。我想完全阻止它重绘,而SuspendLayout和ResumeLayout不够。如何为控件及其子控件暂停绘画?
Answers:
在我之前的工作中,我们一直在努力获取丰富的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();
}
}
对此有更充分的讨论-Google for C#和WM_SETREDRAW,例如
并且可能与之相关的是VB中的类似示例:
Public Module Extensions
<DllImport("user32.dll")>
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
<Extension()>
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
<Extension()>
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
End Sub
<Extension()>
Public Sub ResumeDrawing(ByVal Target As Control)
ResumeDrawing(Target, True)
End Sub
End Module
Control
基类已经对和方法进行的操作。自己发送消息并不比使用这些方法为您完成繁重的工作更好,而且肯定不会产生不同的结果。BeginUpdate
EndUpdate
Control.Handle
将强制创建窗口句柄并可能影响性能。例如,如果您要在窗体上显示控件之前将其移动,则如果SuspendDrawing
事先调用它,则移动速度会变慢。可能if (!parent.IsHandleCreated) return
两种方法都应进行检查。
以下是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();
}
}
Message
和NativeWindow
位置会更好。在文档中搜索名为的类Message
并不是真正有趣的事情。
Invalidate()
Refresh()
除非跟着一个跟随,否则效果不佳。
我通常会使用ngLink的answer的少许修改版本。
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
一个ResumeDrawing
。因此,将其公开可能不是一个好主意。
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
。另一种选择是在一个IDisposable
类中实现此功能,并将绘图部分包含在using
-statement中。该句柄将传递给构造函数,该构造函数将暂停绘制。
DllImport
声明wParam
为bool
吗?
为了帮助您不要忘记重新启用图形:
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();
});
action()
抛出异常呢?(尝试一下/最后尝试)
一个不使用互操作的不错的解决方案:
与往常一样,只需在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
}
这是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
我知道这是一个古老的问题,已经回答,但这是我对此的看法。我将更新的暂停重构为一个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肌肉,并且显然仅适合某些情况。青年汽车
在我的场景中,我使用的是我称为“父母”的UserControl-在Load
活动期间,我只是从Parent的.Controls
集合中删除了要操作的控件,而Parent OnPaint
则负责完全绘制孩子以任何特殊方式进行控制。.使孩子的绘画能力完全脱机。
现在,我将我的孩子绘画例程交给基于Mike Gold的这一概念的扩展方法,用于打印Windows窗体。
在这里,我需要一个标签子集来垂直于布局渲染:
然后,通过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度,因此我确定该区域中的所有热点和互动性都已被破坏-但是,我要解决的问题全是需要预览和打印的包装标签,这对我来说很好。
如果有办法将热点和控制力重新引入我有目的的孤立控制中-我很想有一天了解这一点(当然,不是针对这种情况,而是..学习)。当然,WPF支持这种疯狂的OOTB。但是..嘿。WinForms仍然很有趣,对吗?