Winforms:SuspendLayout / ResumeLayout是不够的?

我有一些库“自定义控件”。 基本上我们有自己的按钮,更圆的角落面板,以及一些带有一些定制油漆的combobox。 尽管OnPaint方法中有“数学”,但控件非常标准。 大多数情况下,我们所做的只是绘制圆角并为背景添加渐变。 我们使用GDI +来实现这一切。

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

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

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

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

我们也看到了这个问题。

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

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

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

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

希望这可以帮助。

我会从性能角度来解决你的问题。

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

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

大多数情况下,我们所做的只是绘制圆角并为背景添加渐变

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

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

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

编辑:

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

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

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

简而言之,Doublebuffering:非常简单,只需几行代码。 在paint事件上,渲染到位图而不是渲染到Graphics对象,然后将该位图绘制到Graphics对象。

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

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

如果这最终还不够(我会想出来并说它不是),请考虑查看我对这个问题的回答并暂停/恢复重新绘制面板或表格。 这将使您的布局操作完成,然后完成所有绘图。

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

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

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

您可能想看看我的问题的答案, 如何暂停控件及其子项的绘画? 为了更好的暂停/恢复。

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

在你的控制中

 BufferedGraphicsContext gfxManager; BufferedGraphics gfxBuffer; Graphics gfx; 

一个安装图形的function

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

在其resize的方法

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

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

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

通过创建一个inheritance该表的类,然后启用了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; } } } 

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

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

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

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

这可能不常见,但是如果你已经尝试了其他一切,那么值得研究一下。