Winforms:SuspendLayout / ResumeLayout是不够的?

时间:2009-05-07 14:47:30

标签: c# winforms performance gdi+ doublebuffered

我有一些“自定义控件”库。基本上我们有自己的按钮,圆角板和一些带有一些定制油漆的组合箱。尽管OnPaint方法中存在“数学”,但控件非常标准。大多数情况下,我们所做的只是绘制圆角并为背景添加渐变。我们使用GDI +。

这些控件都可以(根据我们的客户非常好看),但是尽管有DoubleBuffer,你可以看到一些重绘,特别是当同一表格上有20个++按钮时(例如)。在表单加载时,您会看到绘制的按钮...这很烦人。

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

另一方面,如果有“复杂”的foreach循环将创建标签,将它们添加到面板(双缓冲)并更改其属性,如果我们在循环之前挂起面板并恢复面板的布局时循环结束了,不应该所有这些控件(标签和按钮)“几乎立即”出现吗?这不会发生这种情况,您可以看到面板被填充。

知道为什么不发生这种情况?我知道很难在没有示例代码的情况下进行评估,但这也难以复制。我可以用相机制作一个视频,但相信我这个,它并不快:)

10 个答案:

答案 0 :(得分:12)

我们也看到了这个问题。

我们已经看到“修复”它的一种方法是完全暂停控制的绘制,直到我们准备好去。为此,我们将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);

答案 1 :(得分:11)

您应该注意的一件事是您是否在面板的任何子控件上设置了BackColor = Transparent。 BackColor = Transparent将显着降低渲染性能,尤其是在父面板使用渐变时。

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

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

希望这有帮助。

答案 2 :(得分:9)

我会从性能角度处理你的问题。

  

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

如果订单已经完成,那么还有改进的余地。首先创建所有标签,更改其属性,并在它们准备就绪后,将它们添加到面板:Panel.Controls.AddRange(Control[])

  

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

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

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

即使不同的按钮具有不同的颜色,但是相同的图案,您可以使用Paint或其他任何东西创建位图,并在运行时加载它并将Color值乘以另一种颜色。

修改

  

如果我们在之前暂停布局   循环结束时循环并恢复面板的布局

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

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

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

答案 3 :(得分:4)

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

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

如果这最终还不够(我会说出来并且说不是),请考虑查看我对this question的回答并暂停/恢复重绘小组或表格。这将使您的布局操作完成,然后在完成后完成所有绘图。

答案 4 :(得分:2)

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

在您的控件中

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

在其绘画方法

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

在其调整大小方法

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

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

答案 5 :(得分:2)

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

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

如果您的目标是Windows XP(可能是稍后),则可以设置合成标志。有WS_ EX_ COMPOSITED窗口样式。我以前用它来混合结果。它不适用于WPF / WinForm混合应用程序,也不能很好地与DataGridView控件。如果你走这条路,请确保你在不同的机器上做了很多测试,因为我看到了奇怪的结果。最后,我放弃了这种方法的使用。

答案 6 :(得分:2)

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

答案 7 :(得分:0)

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

我开始使用Microsoft标准控件,但我发现我一直在调试由控件引起的问题。微软通常不解决任何已识别的问题,而且它们在提供合适的解决方法方面做得很少,这使问题更加严重。

我切换到DevExpress,我只有好话要说。产品坚固,它们提供了很好的支持和文档,是的,它们实际上是在倾听客户的意见。每当我遇到问题或问题时,我都会在24小时内得到友好的回复。在一些情况下,我确实发现了一个错误,在这两个实例中,他们都为下一个服务版本实现了修复。

答案 8 :(得分:0)

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

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

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 :(得分:0)

我看到糟糕的winforms在控件引用缺失字体的表单上闪烁。

这可能并不常见,但是如果你已经尝试了其他所有内容,那就值得研究一下。