如何修复用户控件中的闪烁

时间:2010-04-10 06:24:18

标签: c# winforms user-controls flicker

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

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

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

12 个答案:

答案 0 :(得分:293)

双缓冲可以解决的不是那种闪烁。也不是BeginUpdate或SuspendLayout。你有太多的控件,BackgroundImage可以让它很多更差。

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

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

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

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

接下来你要做的就是防止这些孔明显,并与图像形成鲜明对比。您可以turn off 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()方法中只需要一行代码。

答案 1 :(得分:8)

这是一个真正的问题,Hans Passant给出的答案非常适合保存闪烁。然而,正如他所提到的那样,有副作用,而且它们可能很难看(UI难看)。如上所述,“您可以关闭UC的WS_CLIPCHILDREN样式标志”,但这只会为UC关闭它。主表单上的组件仍有问题。

示例,面板滚动条不会绘制,因为它在技术上位于子区域。但是子组件不会绘制滚动条,因此在鼠标悬停(或其他事件触发)之前它不会被绘制。

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

所以我正在寻找一种方法来在初始绘画时关闭WS_CLIPCHILDREN,这样我的Form将加载很好的绘制,或者更好,但只有在使用大量组件调整表单时才打开它。

诀窍是让应用程序使用所需的WS_EX_COMPOSITED / WS_CLIPCHILDREN样式调用CreateParams?我在这里找到了一个黑客(http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-windows-forms-applications.aspx)并且效果很好。谢谢AngryHacker!

我以ResizeBegin事件的形式调用TurnOnFormLevelDoubleBuffering()。 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;
    }

答案 2 :(得分:6)

如果您在控件中进行任何自定义绘制(即重写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的上述答案可能就是您想要的。

答案 3 :(得分:3)

尝试使用BeginUpdate / EndUpdate或SuspendLayout / ResumeLayout方法。 见下文
How to fix nested winform control flicker issues
Flickering during updates to Controls in WinForms (e.g. DataGridView)

答案 4 :(得分:2)

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

答案 5 :(得分:2)

我尝试将其添加为评论,但我没有足够的积分。这是唯一能帮助我解决问题的事情,感谢汉斯的这篇文章。对于像我这样使用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);
}

答案 6 :(得分:2)

将代码放在构造函数或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。

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

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

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

WS_EX_COMPOSITED - 使用双缓冲以底部到顶部的绘画顺序绘制窗口的所有后代。

您可以找到更多这些样式标记here

希望有所帮助!

答案 7 :(得分:1)

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

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

如果WS_EX_COMPOSITED,DoubleBuffered和WS_CLIPCHILDREN没有解决你的闪烁(对我来说WS_CLIPCHILDREN让它变得更糟),试试这个:浏览所有控件和所有代码,无论你有什么透明或BackColor的半透明, ForeColor或任何其他颜色,只需删除它,只使用纯色。在大多数情况下,如果您认为自己 使用透明度,则不会。重新设计代码和控件,并使用纯色。 我有可怕的,可怕的闪烁,程序运行缓慢。一旦我删除了透明度,它就显着加快了,并且闪烁了0次。

编辑:为了进一步增加,我刚刚发现WS_EX_COMPOSITED不必是窗口范围的,它可以仅应用于特定的控件!这给我带来了很多麻烦。只需从您需要的任何控件继承自定义控件,并粘贴已发布的WS_EX_COMPOSITED覆盖。这样你只能在这个控件上获得低级双缓冲,避免了应用程序其余部分的恶意副作用!

答案 8 :(得分:0)

我知道这个问题已经很老了,但我想借鉴它的经验。

我在使用.NET 4.0的Windows 8中覆盖Tabcontrol和/或OnPaint的表单中OnPaintBackGround闪烁时出现了很多问题。

唯一有效的想法是 NOT USE Graphics.DrawImage中的OnPaint方法覆盖,换句话说,直接绘制到PaintEventArgs提供的图形时{1}},甚至画了所有的矩形,闪烁消失了。但是如果调用DrawImage方法,甚至绘制一个剪切的位图(为双缓冲而创建),则会出现闪烁。

希望它有所帮助!

答案 9 :(得分:0)

我合并了this flicker fixthis font fix,然后我不得不添加一些我自己的代码来启动绘制时的计时器,以便在它离开屏幕和返回时使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

PS。你需要设置HotTrack = true,因为这也修复了这个bug

答案 10 :(得分:-2)

您是否尝试过Control.DoubleBuffered财产?

  

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

thisthis也可能会有所帮助。

答案 11 :(得分:-9)

不需要任何Double缓冲和所有那些东西......

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

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

这是整个应用程序的唯一最佳解决方案。请参阅主要表单中的代码:

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