C# Winforms:SuspendLayout/ResumeLayout 还不够?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/835100/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 03:32:31  来源:igfitidea点击:

Winforms: SuspendLayout/ResumeLayout is not enough?

c#winformsperformancegdi+doublebuffered

提问by Martin Marconcini

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.

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

These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.

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

I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?

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

On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.

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

Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)

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

回答by Adam Robinson

In addition to the DoubleBufferedproperty, also try adding this to your control's constructor:

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

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

And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this questionand suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.

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

回答by Judah Gabriel Himango

We've seen this problem too.

我们也看到了这个问题。

One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:

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

回答by DonkeyMaster

I'll approach your problem from a performance angle.

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

foreach loop that will create labels, add them to a panel (double buffered) and change their properties

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

If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])

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

Most of the time, all we do is draw the rounded corners and add gradient to the background

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

Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.

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

My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.

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

Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.

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

EDIT:

编辑:

if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over

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

That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.

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

As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.

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

Doublebuffering in a nutshell:It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphicsobject, and then draw that bitmap to the Graphicsobject.

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

回答by DevComponents - Denis Basaric

One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.

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

Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.

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

So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.

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

Hope this helps.

希望这可以帮助。

回答by Dennis Ecclestone

I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.

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

I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.

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

I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.

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

回答by Dennis Ecclestone

Maybe first draw on a control-only 'visible' (private) buffer and then render it:

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

In your control

在您的掌控中

BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;

A function to install graphics

一个安装图形的功能

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

In its paint method

在它的绘画方法中

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

In its resize method

在其调整大小方法中

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

The code above has been somewhat tested.

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

回答by Dennis Ecclestone

It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).

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

Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.

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

There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.

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

回答by Simon

You may want to look at the answer to my question, How do I suspend painting for a control and its children?for a better Suspend/Resume.

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

回答by Kevin

I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.

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

I completely got rid of the flicker by creating a class that inherited the table, then enabled 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;
        }
    }
}

回答by jm.

I have seen bad winforms flicker on forms where the controls referred to a missing font.

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

This is probably not common, but it's worth looking into if you've tried everything else.

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