如何为控件及其子项暂停绘画?

时间:2009-01-28 13:46:34

标签: c# .net winforms paint

我有一个控件,我必须对其进行大量修改。当我这样做时,我想完全阻止它重绘 - SuspendLayout和ResumeLayout是不够的。如何为控件及其子项暂停绘画?

10 个答案:

答案 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,例如。

C# Jitter

Suspending Layouts

对于它可能关注的人,这是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();
        }
    }
}

这允许挂起/恢复调用。您必须确保将每个SuspendDrawingResumeDrawing相匹配。因此,公开它们可能不是一个好主意。

答案 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的扩展方法。

这里我需要一组标签来将垂直呈现给布局:

simple diagram of your Visual Studio IDE

然后,我在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在设计时和运行时正确呈现表单ParentUserControl hosted in a Windows Form or perhaps other user control

现在,由于我的特殊操作将子控件旋转90度,我确信所有热点和交互性在该区域都被破坏了 - 但是,我解决的问题是所有需要预览的包标签和打印,对我来说很好。

如果有办法将热点和控制权重新引入我故意的孤儿控制中 - 我希望有一天能够了解这一点(当然不是这种情况,但是......只是为了学习)。当然,WPF支持这样的疯狂OOTB ..但是......嘿.. WinForms还是那么有趣,amiright?

答案 9 :(得分:-4)

或者只使用Control.SuspendLayout()Control.ResumeLayout()