Windows Forms:使用BackgroundImage会减慢窗体控件的绘制速度。

33

我有一个Windows窗体(C# .NET 3.5),上面有许多按钮和其他控件,都分配给顶级面板,该面板跨越整个窗体。例如,层次结构如下:窗体→面板→其他控件。

一旦我为面板分配BackgroundImage,控件的绘制就会变得非常缓慢。如果我使用Form的BackgroundImage属性并将Panel的BackgroundColor设置为"透明",则会产生相同的效果。似乎首先绘制具有背景的窗口,然后每个控件逐个添加,并在绘制下一个控件之前稍微延迟。换句话说,您实际上可以按照每个控件绘制到窗体的顺序进行跟踪。一旦所有控件都被绘制一次,这种效果就不再发生了,但窗体的响应速度仍然很慢。

在Visual Studio的设计器中,我得到了相同的效果,尤其是在移动控件时更为明显。有时,窗体的绘图完全停止一两秒钟,这使得使用BackgroundImage非常麻烦,无论是在设计器中还是在最终应用程序中。

当然,我尝试了使用DoubleBuffered = true,并使用反射在所有控件上进行设置,但没有效果。

此外,这是窗体的加载代码,因为它有点不寻常。它将所有控件从另一个窗体复制到当前窗体。这样做是为了能够单独编辑每个屏幕的视觉外观,同时共享公共窗体和公共代码基础。我有一种预感,这可能是减速的原因,但仍无法解释为什么在设计器中已经注意到了减速。

private void LoadControls(Form form)
{
    this.SuspendLayout();

    this.DoubleBuffered = true;
    EnableDoubleBuffering(this.Controls);

    this.BackgroundImage = form.BackgroundImage;
    this.BackColor = form.BackColor;

    this.Controls.Clear();
    foreach (Control c in form.Controls)
    {
        this.Controls.Add(c);
    }

    this.ResumeLayout();
}

正如你所看到的,SuspendLayout()ResumeLayout() 用于避免不必要的重绘。

然而,一旦使用了 BackgroundImage,表单就会变得“慢得要死”。我甚至尝试将其转换为PNG、JPG和BMP格式,看看是否有任何区别。此外,图像大小为1024x768,但较小的图像也具有相同的减速效果(尽管略微减少)。

我该怎么办?


如果你使用的是带有性能分析器的 VS 版本,那么分析器会显示你花费了所有时间的地方在哪里? - Greg D
可能是如何修复用户控件中的闪烁问题的重复问题。 - Hans Passant
6个回答

47

SuspendLayout()ResumeLayout()不会暂停绘图,只会暂停布局操作。试试这个方法:

public static class ControlHelper
{
    #region Redraw Suspend/Resume
    [DllImport("user32.dll", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
    private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
    private const int WM_SETREDRAW = 0xB;

    public static void SuspendDrawing(this Control target)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 0, 0);
    }

    public static void ResumeDrawing(this Control target) { ResumeDrawing(target, true); }
    public static void ResumeDrawing(this Control target, bool redraw)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 1, 0);

        if (redraw)
        {
            target.Refresh();
        }
    }
    #endregion
}

使用方法应该相当简单,语法与 SuspendLayout()ResumeLayout() 相同。这些是扩展方法,将显示在任何 Control 实例上。


这对我的用户控件也产生了很大的影响。谢谢! - Paul H.
哇,差別真的很大!謝謝亞當!! - Lee Richardson
你好,你能帮我学习如何使用这个吗?谢谢。 - MMakati
我发现这个解决方案非常有用,谢谢。然而,在深入研究后,当以这种方式使用WM_SETREDRAW时,存在一些可用性问题,您应该注意(摘要:在禁用时,控件可以“点击穿透”,这意味着其他隐藏在其后面的窗口/控件可能会被触发)。请参见:http://fgaillard.com/2011/02/the-unfortunate-effect-of-wm_setredraw/,以及另一种替代实现http://blogs.msdn.com/b/oldnewthing/archive/2014/04/07/10514610.aspx。 - Sverrir Sigmundarson
当我暂停父控件的绘制而不是控件本身时,它对我起作用。 - TH Todorov
显示剩余2条评论

5

我也遇到了同样的问题,通过降低背景图片的分辨率来解决了这个问题。当你使用大尺寸(例如:1280X800)的图片作为背景时,会花费时间在窗体上绘制控件。 最好在“画图”中打开图片,将其缩小至比你的窗体小,然后保存为“bmp”格式。现在尝试将此图片添加为您的窗体背景。


我尝试了很多方法。最后,我想试一试这个,令人惊讶的是它对我起了作用。有了显著的区别...谢谢。 - curiousBoy

4

避免在添加控件时进行永久重绘的另一种非常简单的方法是在添加控件之前使父控件不可见。然后在添加完控件之后,将父控件(例如面板)设置为可见状态,这样就可以避免所有那些重绘了。 :-)

panelParent.visible = false;

for(...) {
    // Add your controls here:
    panelParent.Controls.Add(...);
}

panelParent.visible = true;

3

我使用了PictureBox解决了相同的问题。只需将PictureBox添加到您的容器中,选择“停靠在父容器中”(或属性Dock=Fill),并设置其图像。它看起来就像父控件的背景图像。


你能详细说明一下吗?我正在尝试解决类似的问题,只不过我是在表单上进行绘制,而非添加控件。包含图像的图片框减少了绘制时的延迟,但现在我在表单上看不到我的绘图内容了。 - Bobby Byrnes
@BobbyByrnes 你也可以在图片上绘制(使用PictureBox的“Paint”事件)。 - Pavel

3

尽管我的表单大小与图像大小匹配,但默认的BackgroundImageLayout属性设置为Tile是问题的根源。将其更改为Stretch后,问题得到解决。 - Tim

1
我知道这是一个旧的线程,但我在搜索有关同一问题的信息时发现了它,因此如果对某人有用的话:

我的情况:我有一个13 x 12的面板网格,其中动态设置背景图像,并根据用户选择定期更改。每个面板还添加了一个文本标签控件。为了使文本覆盖背景图像,必须将其设置为透明(顺便说一句 - 我的经验是Zoom,Stretch,Center的BackgroundImageLayout几乎没有影响。BackColor设置为透明或白色也几乎没有效果)。

每次绘制面板网格(包括调整大小)大约需要1秒钟 - 不错但在较慢的机器上会出现可见性问题。

我的图像不是很大,但有些过大。

我发现:通过在设置backgroundimage之前将图像集调整为精确的面板大小,绘制时间大大降低 - 大约0.1秒左右。由于我的程序根据窗口大小动态调整面板大小,因此在设置156个面板的背景之前,在窗口调整大小事件上动态调整图像集的大小一次。

事后看来,这是一个显而易见的优化...一次调整8个图像而不是重复156次。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接