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

Roy*_*son 105 c# user-controls flicker winforms

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

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.DoubleBuffer, true);
Run Code Online (Sandbox Code Playgroud)

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

Han*_*ant 300

双缓冲可以解决的不是那种闪烁.也不是BeginUpdate或SuspendLayout.你有太多的控制,和backgroundImage可以把它很多更坏.

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

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

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Run Code Online (Sandbox Code Playgroud)

你可以做很多事情来提高绘画速度,以至于闪烁不再明显.首先解决BackgroundImage问题.当源图像很大并且需要缩小以适应控件时,它们可能非常昂贵.将BackgroundImageLayout属性更改为"Tile".如果这样可以显着提高速度,请返回绘图程序并调整图像大小以更好地匹配典型的控件尺寸.或者在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;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,子控件将自己绘制在背景图像的顶部.你可能仍然会看到他们一个接一个地画自己,但丑陋的中间白洞或黑洞是不可见的.

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


use*_*810 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;
    }
Run Code Online (Sandbox Code Playgroud)


Pat*_*ick 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);
}
Run Code Online (Sandbox Code Playgroud)

并使用属性使控件无效 NeedRepaint

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


小智 5

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

如果您正在制作自定义控件,您可能需要将此标志添加到您的构造函数中:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Run Code Online (Sandbox Code Playgroud)

您可以选择在表单/控件中使用此代码:

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

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

有关双缓冲技术的更多信息可以在这里找到。

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

protected override CreateParams CreateParams
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
        return parms;
    }
}
Run Code Online (Sandbox Code Playgroud)

WS_EX_COMPOSITED- 使用双缓冲按从下到上的绘制顺序绘制窗口的所有后代。

您可以在此处找到更多此类样式标志。

希望有帮助!