C# 如何暂停控件及其子控件的绘制?

C# 如何暂停控件及其子控件的绘制?,c#,.net,winforms,paint,C#,.net,Winforms,Paint,我有一个控制,我必须作出重大修改。我想在我这样做的时候完全阻止它重新绘制-SuspendLayout和ResumeLayout是不够的。如何暂停控件及其子控件的绘制?在我之前的工作中,我们一直在努力让我们的富UI应用程序能够立即、顺利地绘制。我们使用标准的.Net控件、自定义控件和devexpress控件 在大量使用谷歌和reflector之后,我发现了WM_SETREDRAW win32消息。这实际上会在更新控件时停止控件的绘制,并且可以将其应用到父/包含面板 这是一个非常简单的类,演示如何使

我有一个控制,我必须作出重大修改。我想在我这样做的时候完全阻止它重新绘制-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
}