【问题标题】:Winforms: SuspendLayout/ResumeLayout is not enough?Winforms:SuspendLayout/ResumeLayout 不够用?
【发布时间】:2010-10-24 12:30:18
【问题描述】:

我有一个包含一些“自定义控件”的库。本质上,我们有自己的按钮、圆角面板和一些带有一些自定义油漆的组合框。尽管 OnPaint 方法中有“数学”,但控件是相当标准的。大多数时候,我们所做的只是绘制圆角并向背景添加渐变。我们使用 GDI+。

这些控件没问题(根据我们的客户说看起来非常漂亮),但是尽管有 DoubleBuffer,您还是可以看到一些重绘,尤其是当同一表单上有 20++ 个按钮时(例如)。在表单加载时,您会看到按钮绘制……这很烦人。

我很确定我们的按钮不是世界上最快的,但我的问题是:如果双缓冲区“开启”,那么所有重绘不应该在后台发生,Windows 子系统应该“立即”显示结果“?

另一方面,如果存在将创建标签的“复杂”foreach 循环,则将它们添加到面板(双缓冲)并更改它们的属性,如果我们在循环之前暂停面板的布局并在以下情况下恢复面板的布局循环结束了,所有这些控件(标签和按钮)不应该“几乎立即”出现吗?不是这样的,你可以看到面板被填满了。

知道为什么这没有发生吗?我知道没有示例代码很难评估,但这也很难复制。我可以用相机制作视频,但相信我,它并不快 :)

【问题讨论】:

  • 您还应该尝试暂停/恢复重绘操作...查看我的更新答案。
  • 你肯定有性能问题。我不认为绘制渐变和四分之一圆应该那么慢。
  • 好吧,正如我所说,UI 库不是最快的,但我们也有很多 GDI+ 绘图代码来使按钮看起来像我们想要的那样。它不仅仅是一个 draw.arc x 4 并用渐变绘制表面。我想我们也必须努力解决这个问题……但我想知道是否有办法加快速度。如果它双缓冲,它应该在“翻转”时快速显示它,不是吗?
  • 我还在调查这个问题,很快就会报告。感谢到目前为止的想法。

标签: c# winforms performance gdi+ doublebuffered


【解决方案1】:

我们也遇到过这个问题。

我们已经看到“修复”它的一种方法是完全暂停控件的绘制,直到我们准备好开始。为此,我们向控件发送 WM_SETREDRAW 消息:

// Note that WM_SetRedraw = 0XB

// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

...

// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);

【讨论】:

  • 那是 C++ 吗?我没有 UsafeSharedNativeMethods……我缺少参考吗? (呵呵,我听起来像 vcs 编译器);)
  • 它可能是 C++,但它与我在答案中的链接中包含的概念相同。
  • 它不是 C++,它是命名 PInvoke 类的一种非常标准的方式。您应该创建自己的静态类并添加一个名为 SendMessage 的 PInvoke 方法。 WindowMessages 显然也是一个用户定义的枚举,其值从 Win32 API 正确指定。最后,亚当,你还没有发这个,你只是发了一个链接,你的态度很烦人。
【解决方案2】:

您应该查看的其中一件事是您是否在面板的任何子控件上设置了 BackColor=Transparent。 BackColor=Transparent 会显着降低渲染性能,尤其是在父面板使用渐变的情况下。

Windows 窗体不使用真正的透明度,而是使用“假”的透明度。每个子控件绘制调用都会在父控件上生成绘制调用,因此父控件可以绘制其背景,子控件在其上绘制其内容,使其看起来透明。

因此,如果您有 50 个子控件,则会在父控件上生成额外的 50 个绘制调用以进行背景绘制。而且由于梯度通常较慢,您会看到性能下降。

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    我将从性能角度解决您的问题。

    将创建标签的foreach循环, 将它们添加到面板(双缓冲) 并改变它们的属性

    如果事情是这样完成的,那么还有改进的余地。首先创建所有标签,更改它们的属性,当它们都准备好后,将它们添加到面板中:Panel.Controls.AddRange(Control[])

    大多数时候,我们所做的只是画画 圆角并添加渐变 到后台

    您是否一遍又一遍地做同样的事情?你的渐变是如何生成的?写一张图片不能那么慢。我曾经不得不在内存中创建一个 1680x1050 的渐变,而且速度非常快,就像,对于Stopwatch 来说太快了,所以绘制渐变不会那么难。

    我的建议是尝试缓存一些东西。打开 Paint,绘制角并保存到磁盘,或在内存中生成图像一次。然后根据需要加载(并调整大小)。渐变也一样。

    即使不同的按钮具有不同的颜色,但主题相同,您也可以使用 Paint 或其他工具创建位图,并在运行时加载它并将颜色值乘以另一种颜色。

    编辑:

    如果我们在 循环结束时循环和恢复面板布局

    这不是 SuspendLayout 和 ResumeLayout 的用途。它们暂停布局逻辑,即控件的自动定位。与 FlowLayoutPanel 和 TableLayoutPanel 最相关。

    至于双缓冲,我不确定它是否适用于自定义绘制代码(没有尝试过)。我想你应该实现你自己的。

    双缓冲: 很简单,几行代码。在绘制事件上,渲染到位图而不是渲染到Graphics 对象,然后将该位图绘制到Graphics 对象。

    【讨论】:

    • 如果你想走那条路,我还有一些想法。 (星期一,因为 5 月 8 日是法国的假期)
    • 会考虑这一切。 Tres bien ;) 在西班牙不是一个假期。我会告诉你更多关于我正在做的事情,也许还有很多改进的空间。 ;) 谢谢。
    • 关于创建整个Control[] 并使用AddRange 设置一次的提示完美地解决了我的问题。非常感谢,因为我认为我自己已经有一段时间没有尝试过了;)
    【解决方案4】:

    除了DoubleBuffered 属性之外,还可以尝试将其添加到控件的构造函数中:

    SetStyle(ControlStyles.OptimizedDoubleBuffer | 
             ControlStyles.AllPaintingInWmPaint, true);
    

    如果这还不够(我会勉强说它还不够),请考虑查看我对this question 的回答并暂停/恢复面板的重绘或表格。这将使您的布局操作完成,然后在完成后执行所有绘图。

    【讨论】:

    • 我们已经在这样做了,还有 ControlStyles.UserPaint;我想我们已经尝试了所有可能的组合。结果总是或多或少相同:S
    • 我遇到了与另一个问题的 OP 相同的行为“它没有帮助......我还注意到,当弹出菜单覆盖一些按钮并强制它们重绘时,重绘特别慢(例如,您看到按钮边框被绘制,按钮填充纯色,最后按钮绘制它的图片,然后跟随下一个按钮)“现在不仅超级慢,而且它还有一个边框......这很奇怪:S
    • 在您的自定义控件绘图代码中,您是每次都重绘整个控件,还是只重绘需要的部分?
    • 这似乎与根据referencesource.microsoft.com/#System.Windows.Forms/winforms/… 设置DoubleBuffered 属性完全相同。
    【解决方案5】:

    您可能想查看我的问题的答案,How do I suspend painting for a control and its children? 以获得更好的暂停/恢复。

    【讨论】:

      【解决方案6】:

      听起来您正在寻找的是“合成”显示,其中整个应用程序被一次绘制,几乎就像一个大位图。这就是 WPF 应用程序所发生的情况,除了应用程序周围的“镶边”(标题栏、调整大小手柄和滚动条等)。

      请注意,通常情况下,除非您弄乱了某些窗口样式,否则每个 Windows 窗体控件都负责绘制自己。也就是说,每个控件都会对 WM_PAINT、WM_NCPAINT、WM_ERASEBKGND 等绘制相关消息进行破解,并独立处理这些消息。这对您意味着双缓冲仅适用于您正在处理的单个控件。为了在一定程度上接近于干净的复合效果,您不仅需要关注正在绘制的自定义控件,还需要关注放置它们的容器控件。例如,如果您的 Form 包含一个 GroupBox,而 GroupBox 又包含许多自定义绘制的按钮,则这些控件中的每一个都应将 DoubleBuffered 属性设置为 True。请注意,此属性是受保护的,因此这意味着您要么最终继承各种控件(仅设置双缓冲属性),要么使用反射来设置受保护的属性。此外,并非所有 Windows 窗体控件都尊重 DoubleBuffered 属性,因为在内部,其中一些控件只是原生“通用”控件的包装。

      如果您的目标是 Windows XP(可能是更高版本),有一种方法可以设置复合标志。有 WS_EX_COMPOSITED 窗口样式。我以前用它来混合结果。它不适用于 WPF/WinForm 混合应用程序,也不适用于 DataGridView 控件。如果你走这条路,请确保在不同的机器上进行大量测试,因为我看到了奇怪的结果。最后,我放弃了使用这种方法。

      【讨论】:

      • 我玩这个标志很开心,但最后我无法解决一个错误,该错误会导致 CPU 内核旋转到 100% 并停留在任何打开选项卡的表单中。
      【解决方案7】:

      也许首先在一个仅控制的“可见”(私有)缓冲区上绘制,然后渲染它:

      在你的掌控中

      BufferedGraphicsContext gfxManager;
      BufferedGraphics gfxBuffer;
      Graphics gfx;
      

      安装图形的功能

      private void InstallGFX(bool forceInstall)
      {
          if (forceInstall || gfxManager == null)
          {
              gfxManager = BufferedGraphicsManager.Current;
              gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
              gfx = gfxBuffer.Graphics;
          }
      }
      

      在它的paint方法中

      protected override void OnPaint(PaintEventArgs e)
      {
          InstallGFX(false);
          // .. use GFX to draw
          gfxBuffer.Render(e.Graphics);
      }
      

      在它的resize方法中

      protected override void OnSizeChanged(EventArgs e)
      {
          base.OnSizeChanged(e);
          InstallGFX(true); // To reallocate drawing space of new size
      }
      

      上面的代码已经过一些测试。

      【讨论】:

        【解决方案8】:

        在切换我想要显示的用户控件时,我遇到了与 tablelayoutpanel 相同的问题。

        通过创建一个继承表的类,然后启用双缓冲,我完全摆脱了闪烁。

        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Windows.Forms;
        
        namespace myNameSpace.Forms.UserControls
        {
            public class TableLayoutPanelNoFlicker : TableLayoutPanel
            {
                public TableLayoutPanelNoFlicker()
                {
                    this.DoubleBuffered = true;
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          过去我遇到过很多类似的问题,我解决的方法是使用第三方 UI 套件(即DevExpress)而不是标准的 Microsoft 控件。

          我开始使用 Microsoft 标准控件,但我发现我一直在调试由它们的控件引起的问题。由于 Microsoft 通常不会修复任何已识别的问题,并且他们几乎没有提供合适的解决方法,因此问题变得更糟。

          我切换到 DevExpress,我只有好话要说。该产品是可靠的,他们提供了很好的支持和文档,是的,他们实际上听取了客户的意见。每当我有任何疑问或问题时,我都会在 24 小时内得到友好的答复。在某些情况下,我确实发现了一个错误,并且在这两种情况下,他们都为下一个服务版本实施了修复。

          【讨论】:

          • 感谢 Dennis,我很想改用类似的东西,但我们使用自定义绘制的 UI(“唯一”),这是我们应用程序的商标。切换到“只是另一组 Windows 控件”对我们不起作用。我们的界面是面向触觉的 + TabletPC,所以我们只使用 WinCtl 中的“一些控件”,但几乎总是在此基础上进行自定义绘图。
          • @Martin,我们在这里做了同样的事情。不好的选择,我想。我们正在考虑升级到 WPF,但工作量很大......
          • @benjol 是的,将此应用程序更改为 WPF 需要几个月的时间!或者更多! :S
          【解决方案10】:

          我看到在控件引用缺失字体的表单上出现错误的 winforms。

          这可能并不常见,但如果您尝试过其他所有方法,则值得研究一下。

          【讨论】:

            猜你喜欢
            • 2010-11-23
            • 2012-08-16
            • 2010-10-27
            • 2012-07-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-10-02
            相关资源
            最近更新 更多