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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/487661/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 05:20:18  来源:igfitidea点击:

How do I suspend painting for a control and its children?

c#.netwinformspaint

提问by Simon

I have a control which I have to make large modifications to. I'd like to completely prevent it from redrawing while I do that - SuspendLayout and ResumeLayout aren't enough. How do I suspend painting for a control and its children?

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

采纳答案by ng5000

At my previous job we struggled with getting our rich UI app to paint instantly and smoothly. We were using standard .Net controls, custom controls and devexpress controls.

在我之前的工作中,我们努力让我们丰富的 UI 应用程序能够立即流畅地绘制。我们使用了标准的 .Net 控件、自定义控件和 devexpress 控件。

After a lot of googling and reflector usage I came across the WM_SETREDRAW win32 message. This really stops controls drawing whilst you update them and can be applied, IIRC to the parent/containing panel.

经过大量的谷歌搜索和反射器使用后,我遇到了 WM_SETREDRAW win32 消息。这确实会在您更新控件时停止绘制,并且可以将 IIRC 应用到父/包含面板。

This is a very very simple class demonstrating how to use this message:

这是一个非常非常简单的类,演示如何使用此消息:

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();
    }
}

There are fuller discussions on this - google for C# and WM_SETREDRAW, e.g.

对此有更全面的讨论 - 谷歌 C# 和 WM_SETREDRAW,例如

C# Jitter

C# 抖动

Suspending Layouts

暂停布局

And to whom it may concern, this is similar example in VB:

对于它可能关心的人来说,这是 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

回答by Ozgur Ozcitak

I usually use a little modified version of ngLink's answer.

我通常使用 ngLink's 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();
        }
    }
}

This allows suspend/resume calls to be nested. You must make sure to match each SuspendDrawingwith a ResumeDrawing. Hence, it wouldn't probably be a good idea to make them public.

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

回答by Eugenio De Hoyos

A nice solution without using interop:

一个不使用互操作的不错的解决方案:

As always, simply enable DoubleBuffered=true on your CustomControl. Then, if you have any containers like FlowLayoutPanel or TableLayoutPanel, derive a class from each of these types and in the constructors, enable double buffering. Now, simply use your derived Containers instead of the Windows.Forms Containers.

与往常一样,只需在您的 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;
    }
}

回答by ceztko

The following is the same solution of ng5000 but doesn't use P/Invoke.

以下是 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();
    }
}

回答by JustJoost

Or just use Control.SuspendLayout()and Control.ResumeLayout().

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

回答by goughy000

Here is a combination of ceztko's and ng5000's to bring a VB extensions version that doesn't use pinvoke

这是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

回答by Scott Baker

I know this is an old question, already answered, but here is my take on this; I refactored the suspension of updates into an IDisposable - that way I can enclose the statements I want to run in a usingstatement.

我知道这是一个老问题,已经回答了,但这是我对此的看法;我将更新的暂停重构为 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();
    }
}

回答by Jonathan H

To help with not forgetting to reenable drawing:

为了帮助不要忘记重新启用绘图:

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();
}

usage:

用法:

SuspendDrawing(myControl, () =>
{
    somemethod();
});

回答by bkwdesign

This is even simpler, and perhaps hacky - as I can see a lot of GDI muscle on this thread, and is obviously only a good fit for certain scenarios.YMMV

这甚至更简单,而且可能是 hacky - 因为我可以在这个线程上看到很多 GDI 肌肉,并且显然只适合某些场景。青年会

In my scenario, I use what I'll refer to as a "Parent" UserControl - and during the Loadevent, I simply remove the control-to-be-manipulated from the Parent's .Controlscollection, and the Parent's OnPainttakes care of completely painting the child control in whatever special way.. fully taking the child's paint capabilities offline.

在我的场景中,我使用我将称之为“父”用户控件的东西 - 在Load事件期间,我只是从父的.Controls集合中删除要操作的控件,父的OnPaint负责完全绘制子以任何特殊方式进行控制……完全离线使用孩子的绘画能力。

Now, I hand off my child paint routine to an extension method based off this concept from Mike Gold for printing windows forms.

现在,我将我的孩子的绘画程序移交给基于Mike Gold 用于打印窗体的概念的扩展方法。

Here I'm needing a sub-set of labels to render perpendicularto the layout:

在这里,我需要一组标签来垂直于布局呈现:

simple diagram of your Visual Studio IDE

Visual Studio IDE 的简单图表

Then, I exempt the child control from being painted, with this code in the ParentUserControl.Loadevent handler:

然后,我在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

Then, in the same ParentUserControl, we paint the control-to-be-manipulated from the ground up:

然后,在同一个 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

Once you host the ParentUserControl somewhere, e.g. a Windows Form - I'm finding that my Visual Studio 2015 renders the formcorrectly at Design Time as well as runtime: ParentUserControl hosted in a Windows Form or perhaps other user control

一旦您在某处托管 ParentUserControl,例如 Windows 窗体 - 我发现我的 Visual Studio 2015在设计时和运行时正确呈现表单ParentUserControl 托管在 Windows 窗体或其他用户控件中

Now, since my particular manipulation rotates the child control 90 degrees, I'm sure all the hot spots and interactivity has been destroyed in that region - but, the problem I was solving was all for a package label that needed to preview and print, which worked out fine for me.

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

If there are ways to reintroduce the hot spots and control-ness to my purposely orphaned control - I'd love to learn about that someday (not for this scenario, of course, but.. just to learn). Of course, WPF supports such craziness OOTB.. but.. hey.. WinForms is so much fun still, amiright?

如果有办法将热点和控制重新引入我故意孤立的控制中 - 我很想有一天了解这一点(当然不是针对这种情况,但是......只是为了学习)。当然,WPF 支持这种疯狂的 OOTB .. 但是.. 嘿.. WinForms 仍然很有趣,对吧?

回答by Koray

Based on ng5000's answer, I like using this extension:

基于 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

Use:

用:

using (this.BeginSuspendlock())
{
    //update GUI
}