当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何为控件及其子项暂停绘画?》经验,为你挑选了6个好方法。

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



1> ng5000..:

在我之前的工作中,我们努力让我们丰富的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#抖动

暂停布局

它可能涉及到谁,这是VB中的类似示例:

Public Module Extensions
    
    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
    
    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

    
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
    End Sub

    
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module


多么棒的答案和巨大的帮助!我为Control类创建了SuspendDrawing和ResumeDrawing扩展方法,因此我可以在任何上下文中为任何控件调用它们.
@Cody Gray - Panel也没有.非常有用的代码.
@Cody Gray - 例如TableLayoutPanels没有BeginUpdate.
这不是特别有用.这正是WinForms控件的*all*的`Control`基类*已经为`BeginUpdate`和`EndUpdate`方法做*.自己发送消息并不比使用这些方法为您做繁重的工作更好,当然也不会产生不同的结果.
如果您的代码可以在显示控件之前调用这些方法,请注意 - 调用`Control.Handle`将强制创建窗口句柄并可能影响性能.例如,如果您在窗体显示之前移动控件,如果您事先调用此"SuspendDrawing",则移动速度会变慢.可能应该在两种方法中都使用`if(!parent.IsHandleCreated)return`检查.
有一个像树一样的控件,如果你折叠它只会在重新排列它的孩子时正确刷新节点然后扩展它,这导致丑陋的闪烁.这完美地解决了它.谢谢!(有趣的是,控件已经导入了SendMessage,*和*定义了WM_SETREDRAW,但实际上并没有使用它.现在确实如此.)
@pelesl为什么不呢?泄漏抽象法则.WinForms是Win32 API的包装器,如果您了解它的设计方式和工作原理,那么它就非常有意义.但是,如果你试图通过WinForms包装器来理解它,那就没有多大意义.如果Form类上有一个Begin/EndUpdate方法,它将无法执行您期望的操作.它只会阻止表单本身更新,它对该表单托管的子控件没有任何影响.在Win32中,一切都是窗口.所有窗户都负责自己的绘图.

2> ceztko..:

以下是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"的类的文档实际上并不是那么有趣.
暂停控件意味着控制区域中根本不会执行任何绘图,并且您可能会从该表面中的其他窗口/控件中获取剩余部分.你可以尝试使用DoubleBuffer设置为true的控件,如果你想要绘制前一个"状态",直到控件恢复(如果我明白你的意思),但我不能保证它会起作用.无论如何,我认为你错过了使用这种技术的重点:它意味着避免用户看到渐进和缓慢的对象绘制(更好地看到它们一起出现).对于其他需求,请使用其他技术.
`Invalidate()`不能像`Refresh()`一样好,除非后面跟着一个.

3> Ozgur Ozcita..:

我通常使用ngLink 答案的一点修改版本.

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与a 匹配ResumeDrawing.因此,公开它们可能不是一个好主意.


这有助于保持两个调用平衡:`SuspendDrawing(); 试试{DrawSomething(); } finally {ResumeDrawing(); }`.另一种选择是在`IDisposable`类中实现它,并将绘图部分包含在`using`语句中.句柄将传递给构造函数,构造函数将暂停绘图.

4> 小智..:

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

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



5> Eugenio De H..:

不使用互操作的好方案:

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


你是对的,它解决了闪烁的问题,而不是具体的屏幕外重绘问题.当我在寻找闪烁的解决方案时,我遇到了像这样的几个相关线程,当我找到它时,我可能没有在最相关的线程中发布它.然而,当大多数人想要暂停绘画时,他们可能指的是在屏幕上绘画,这通常是比冗余的屏幕外绘画更明显的问题,所以我仍然认为其他观众可能会发现这个解决方案在这个帖子中有用.
这当然是一种有用的技术 - 我常常用于ListViews - 但它实际上并没有阻止重绘的发生; 它们仍然在屏幕外发生.

6> Koray..:

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

推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有