28

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

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

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

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

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

4

10 回答 10

13

我们也看到了这个问题。

我们已经看到“修复”它的一种方法是完全暂停控件的绘制,直到我们准备好开始。为此,我们向控件发送 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);
于 2009-05-07T15:03:31.530 回答
13

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

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

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

希望这可以帮助。

于 2009-05-18T18:30:16.260 回答
9

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

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

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

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

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

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

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

编辑:

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

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

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

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

于 2009-05-07T16:37:04.457 回答
4

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

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

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

于 2009-05-07T14:52:52.583 回答
3

您可能想查看我的问题的答案,如何暂停控件及其子项的绘制?以获得更好的暂停/恢复。

于 2009-10-30T16:01:26.627 回答
2

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

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

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

于 2009-08-17T21:26:42.360 回答
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
}

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

于 2009-07-16T10:36:38.320 回答
0

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

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

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

于 2009-06-16T13:09:37.040 回答
0

在切换我想要显示的用户控件时,我遇到了与 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;
        }
    }
}
于 2010-02-04T19:23:03.147 回答
0

我在控件引用缺少字体的表单上看到了错误的 winforms 闪烁。

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

于 2016-01-24T04:14:10.033 回答