我有一个控件,我必须对其进行大量修改。当我这样做时,我想完全阻止它重绘 - SuspendLayout和ResumeLayout是不够的。如何为控件及其子项暂停绘画?
答案 0 :(得分:289)
在我之前的工作中,我们努力让我们丰富的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,例如。
对于它可能关注的人,这是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
答案 1 :(得分:51)
以下是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();
}
}
答案 2 :(得分:14)
我通常使用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
相匹配。因此,公开它们可能不是一个好主意。
答案 3 :(得分:12)
帮助您不要忘记启用绘图:
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();
});
答案 4 :(得分:8)
不使用互操作的好方案:
与往常一样,只需在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;
}
}
答案 5 :(得分:4)
根据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
}
答案 6 :(得分:3)
以下是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
答案 7 :(得分:2)
我知道这是一个老问题,已经回答了,但这是我对此的看法;我将更新暂停重构为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();
}
}
答案 8 :(得分:2)
这更简单,也许是hacky - 因为我可以在这个帖子上看到很多GDI肌肉,并且显然只适合某些场景。 YMMV
在我的场景中,我使用我将称为“父”UserControl的内容 - 并且在Load
事件期间,我只是从父.Controls
中删除要操纵的控件。 }集合,父亲的OnPaint
负责以任何特殊的方式完全绘制子控件..完全将孩子的绘画功能脱机。
现在,我将我的子绘画例程移交给基于此concept from Mike Gold for printing windows forms的扩展方法。
这里我需要一组标签来将垂直呈现给布局:
然后,我在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还是那么有趣,amiright?
答案 9 :(得分:-4)
或者只使用Control.SuspendLayout()
和Control.ResumeLayout()
。