116

在我的应用程序中,我不断地从一个控件转移到另一个控件。我创造了没有。用户控件,但在导航期间我的控件会闪烁。更新需要 1 或 2 秒。我试图设置这个

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.DoubleBuffer, true);

但它没有帮助......每个控件都有相同的背景图像和不同的控件。那么它的解决方案是什么..
谢谢。

4

12 回答 12

327

不是双缓冲能解决的那种闪烁。也不是 BeginUpdate 或 SuspendLayout。你有太多的控件,BackgroundImage 会使它变得更糟。

它在 UserControl 绘制自身时开始。它绘制 BackgroundImage,在子控件窗口所在的位置留下孔。然后每个子控件都会收到一条消息来绘制自己,他们将用他们的窗口内容填充这个洞。当你有很多控件时,这些漏洞对用户来说是可见的一段时间。它们通常是白色的,与背景图像在黑暗时形成鲜明对比。或者,如果表单设置了 Opacity 或 TransparencyKey 属性,它们可能是黑色的,与几乎任何东西形成鲜明对比。

这是 Windows 窗体的一个非常基本的限制,它与 Windows 呈现窗口的方式有关。顺便说一句,由 WPF 修复,它不使用窗口作为子控件。你想要的是双缓冲整个表单,包括子控件。这是可能的,请检查我在此线程中的代码以获取解决方案。它虽然有副作用,但实际上并没有提高绘画速度。代码很简单,将其粘贴到您的表单中(而不是用户控件):

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 

您可以做很多事情来提高绘画速度,以至于闪烁不再明显。从处理 BackgroundImage 开始。当源图像很大并且需要缩小以适应控件时,它们可能非常昂贵。将 BackgroundImageLayout 属性更改为“平铺”。如果这样可以显着加快速度,请返回您的绘画程序并调整图像大小以更好地匹配典型控件大小。或者在 UC 的 OnResize() 方法中编写代码来创建一个适当大小的图像副本,这样就不必在每次控件重绘时都调整它的大小。对该副本使用 Format32bppPArgb 像素格式,它的渲染速度比任何其他像素格式快约 10 倍。

接下来你可以做的是防止这些孔太明显并且与图像形成鲜明对比。可以关闭UC 的 WS_CLIPCHILDREN 样式标志,该标志防止 UC 在子控件所在的区域进行绘画。将此代码粘贴到 UserControl 的代码中:

protected override CreateParams CreateParams {
  get {
    var parms = base.CreateParams;
    parms.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN
    return parms;
  }
}

子控件现在将自己绘制在背景图像之上。你可能仍然会看到他们一幅幅地画自己,但丑陋的中间白色或黑洞将不可见。

最后但同样重要的是,减少子控件的数量始终是解决绘制缓慢问题的好方法。覆盖 UC 的 OnPaint() 事件并绘制现在在孩子中显示的内容。特定的 Label 和 PictureBox非常浪费。方便点击,但它们的轻量级替代方案(绘制字符串或图像)仅在 OnPaint() 方法中使用一行代码。

于 2010-04-10T11:58:32.127 回答
11

这是一个真正的问题,Hans Passant 给出的答案对于节省闪烁非常有用。但是,正如他所提到的,有副作用,它们可能很丑(UI 丑)。如前所述,“您可以关闭 UC 的WS_CLIPCHILDREN样式标志”,但这只会为 UC 关闭它。主窗体上的组件仍然存在问题。

例如,面板滚动条不会绘制,因为它在技术上位于子区域中。然而,子组件不会绘制滚动条,因此直到鼠标悬停(或另一个事件触发它)才会绘制它。

此外,动画图标(在等待循环中更改图标)不起作用。删除 a 上的图标tabPage.ImageKey不会适当地调整其他 tabPages 的大小/重新绘制。

所以我一直在寻找一种方法来关闭WS_CLIPCHILDREN初始绘画,以便我的表单可以很好地加载,或者更好的是只在使用大量组件调整表单大小时才打开它。

诀窍是让应用程序以CreateParams所需的WS_EX_COMPOSITED/WS_CLIPCHILDREN样式调用。我在这里发现了一个黑客(https://web.archive.org/web/20161026205944/http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of- flicker-on-windows-forms-applications.aspx),效果很好。感谢愤怒的黑客!

我将TurnOnFormLevelDoubleBuffering()调用放在表单ResizeBegin事件中并TurnOffFormLevelDoubleBuffering()在表单 ResizeEnd 事件中调用(或者WS_CLIPCHILDREN在最初正确绘制后将其保留。)

    int originalExStyle = -1;
    bool enableFormLevelDoubleBuffering = true;

    protected override CreateParams CreateParams
    {
        get
        {
            if (originalExStyle == -1)
                originalExStyle = base.CreateParams.ExStyle;

            CreateParams cp = base.CreateParams;
            if (enableFormLevelDoubleBuffering)
                cp.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED
            else
                cp.ExStyle = originalExStyle;

            return cp;
        }
    }

    public void TurnOffFormLevelDoubleBuffering()
    {
        enableFormLevelDoubleBuffering = false;
        this.MaximizeBox = true;
    }
于 2013-05-20T18:42:37.820 回答
7

如果您在控件中进行任何自定义绘制(即覆盖 OnPaint),您可以自己尝试双缓冲。

Image image;
protected override OnPaint(...) {
    if (image == null || needRepaint) {
        image = new Bitmap(Width, Height);
        using (Graphics g = Graphics.FromImage(image)) {
            // do any painting in image instead of control
        }
        needRepaint = false;
    }
    e.Graphics.DrawImage(image, 0, 0);
}

并使用属性使您的控件无效NeedRepaint

否则,上述 SuspendLayout 和 ResumeLayout 的答案可能就是您想要的。

于 2010-04-10T08:40:19.107 回答
4

将下面的代码放入您的构造函数或 OnLoad 事件中,如果您使用某种具有子控件的自定义用户控件,则需要确保这些自定义控件也是双缓冲的(即使在 MS 文档中他们说默认情况下它设置为true)。

如果您正在制作自定义控件,您可能希望将此标志添加到您的 ctor 中:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

或者,您可以在表单/控件中使用此代码:

foreach (Control control in Controls)
{
    typeof(Control).InvokeMember("DoubleBuffered",
        BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
        null, control, new object[] { true });
}

我们遍历表单/控件中的所有控件并访问它们的DoubleBuffered属性,然后我们将其更改为 true 以使表单上的每个控件都有双缓冲。我们在这里进行反思的原因是,假设您有一个控件,其中包含不可访问的子控件,这样,即使它们是私有控件,我们仍然会将它们的属性更改为 true。

有关双缓冲技术的更多信息,请参见此处

我通常会覆盖另一个属性来解决这个问题:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
        return parms;
    }
}

WS_EX_COMPOSITED- 使用双缓冲以自下而上的绘制顺序绘制窗口的所有后代。

您可以在此处找到更多此类风格标志。

希望有帮助!

于 2017-04-21T10:05:40.683 回答
3

尝试 BeginUpdate/EndUpdate 或 SuspendLayout/ResumeLayout 方法。请参阅以下
如何修复嵌套的 winform 控件闪烁问题
在更新 WinForms 中的控件时闪烁(例如 DataGridView)

于 2010-04-10T07:54:07.717 回答
3

在背景图像所在的主窗体或用户控件上,将BackgroundImageLayout属性设置为CenterStretch。当用户控件呈现时,您会注意到一个很大的不同。

于 2014-05-31T15:57:56.217 回答
2

只是为了补充汉斯给出的答案:

(TLDR 版本:透明度比你想象的要重,到处只使用纯色)

如果 WS_EX_COMPOSITED、DoubleBuffered 和 WS_CLIPCHILDREN 不能解决您的闪烁问题(对我来说 WS_CLIPCHILDREN 让情况变得更糟),请尝试以下操作:检查您的所有控件和所有代码,以及无论您有任何透明度或半透明度 BackColor、ForeColor 或任何其他颜色,只需将其删除,仅使用纯色。在您认为只需要使用透明度的大多数情况下,您不需要。重新设计您的代码和控件,并使用纯色。我有可怕的,可怕的闪烁,程序运行缓慢。一旦我删除了透明度,它就会显着加快速度,并且闪烁为 0。

编辑:进一步补充,我刚刚发现 WS_EX_COMPOSITED 不必是窗口范围的,它可以仅应用于特定控件!这为我省去了很多麻烦。只需从您需要的任何控件继承自定义控件,然后粘贴已发布的 WS_EX_COMPOSITED 覆盖。这样你就可以只在这个控件上获得低级双缓冲,避免在应用程序的其余部分产生讨厌的副作用!

于 2013-03-15T16:08:36.017 回答
2

我试图将此添加为评论,但我没有足够的积分。这是唯一能帮助我解决闪烁问题的方法,非常感谢 Hans 的帖子。对于像我这样使用 c++ builder 的人来说,这里是翻译

将 CreateParams 声明添加到应用程序的主表单 .h 文件中,例如

class TYourMainFrom : public TForm
{
protected:
    virtual void __fastcall CreateParams(TCreateParams &Params);
}

并将其添加到您的 .cpp 文件中

void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
    Params.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    TForm::CreateParams(Params);
}
于 2016-09-05T09:10:15.717 回答
1

我知道这个问题很老了,但想给我的经验。

在使用 .NET 4.0Tabcontrol覆盖OnPaint和/或在 Windows 8 中闪烁时,我遇到了很多问题。OnPaintBackGround

唯一有效的方法是不使用覆盖中的方法,换句话说,当直接对提供的图形进行绘制时,即使绘制了所有矩形,闪烁也消失了。但是如果调用该方法,即使是绘制一个裁剪的位图(为双缓冲而创建),也会出现闪烁。Graphics.DrawImageOnPaintPaintEventArgsDrawImage

希望能帮助到你!

于 2014-06-27T17:57:58.313 回答
1

我将这个闪烁修复这个字体修复结合起来,然后我必须添加一些我自己的代码来启动一个绘制计时器,以在 TabControl 离开屏幕和返回时使 TabControl 无效等。

这三个都做到了:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TabControlEx:TabControl
{
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    private const int WM_PAINT = 0x0f;
    private const int WM_SETFONT = 0x30;
    private const int WM_FONTCHANGE = 0x1d;
    private System.Drawing.Bitmap buffer;
    private Timer timer = new Timer();
    public TabControlEx()
    {
        timer.Interval = 1;
        timer.Tick += timer_Tick;
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }
    void timer_Tick(object sender, EventArgs e)
    {
        this.Invalidate();
        this.Update();
        timer.Stop();
    }
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_PAINT) timer.Start();
        base.WndProc(ref m);
    }
    protected override void OnPaint(PaintEventArgs pevent)
    {
        this.SetStyle(ControlStyles.UserPaint, false);
        base.OnPaint(pevent);
        System.Drawing.Rectangle o = pevent.ClipRectangle;
        System.Drawing.Graphics.FromImage(buffer).Clear(System.Drawing.SystemColors.Control);
        if (o.Width > 0 && o.Height > 0)
        DrawToBitmap(buffer, new System.Drawing.Rectangle(0, 0, Width, o.Height));
        pevent.Graphics.DrawImageUnscaled(buffer, 0, 0);
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        buffer = new System.Drawing.Bitmap(Width, Height);
    }
    protected override void OnCreateControl()
    {
        base.OnCreateControl();
        this.OnFontChanged(EventArgs.Empty);
    }
    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        IntPtr hFont = this.Font.ToHfont();
        SendMessage(this.Handle, WM_SETFONT, hFont, (IntPtr)(-1));
        SendMessage(this.Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
        this.UpdateStyles();
    }
}

我不是创建者,但据我了解,位图会绕过所有错误。

这是唯一为我彻底解决 TabControl(带图标)闪烁的问题。

差异结果视频:vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

附言。您将需要设置 HotTrack = true,因为这也修复了该错误

于 2016-03-23T23:53:33.473 回答
-2

你试过Control.DoubleBuffered物业吗?

获取或设置一个值,该值指示此控件是否应使用辅助缓冲区重绘其表面以减少或防止闪烁。

此外,可能会有所帮助。

于 2010-04-10T10:06:59.953 回答
-9

不需要任何双缓冲和所有这些东西......

一个简单的解决方案...

如果您使用的是 MDI 接口,只需将以下代码粘贴到主窗体中。它将消除页面上的所有闪烁。然而,一些需要更多时间加载的页面将在 1 或 2 秒内显示。但这比显示一个闪烁的页面要好,其中每个项目都一个一个出现。

这是整个应用程序的唯一最佳解决方案。请参阅要放入主窗体的代码:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
于 2012-10-02T19:37:52.587 回答