5

有谁知道如何摆脱闪烁?我研究了 SO,网络,并尝试了许多不同的事情,例如在不绘制 OnPaint() 时将 TickerControl 放入双缓冲面板中,例如双缓冲:为什么它不起作用?等等。除了许多其他的事情。它仍然闪烁,不是在每次重新绘制时,而是每秒几次。

此外,即使在 OnPaint 中删除“g.Clear(BackColor)”之后,仍然必须清除背景,因为文本继续以可读的方式滚动。

这里是我的 TickerControl 类的相关部分:

class TickerControl : Control
{
    private static readonly StringFormat stringFormat = new StringFormat(StringFormatFlags.NoWrap);

    private const int padding = 40;
    private const int scrollSleep = 10;
    private const int scrollAdvancePixels = 1;

    private float textWidth;
    private float currentX;

    private static readonly Timer scrollTimer = new Timer();

    public TickerControl()
    {
        this.SetStyle(ControlStyles.UserPaint |
                      ControlStyles.AllPaintingInWmPaint |
                      ControlStyles.OptimizedDoubleBuffer
                      , true);
        scrollTimer.Tick += AdvanceText;
        scrollTimer.Interval = scrollSleep;
        scrollTimer.Enabled = true;
    }

    private void AdvanceText(object sender, EventArgs e)
    {
        if (IsDisposed)
            return;

        currentX -= scrollAdvancePixels;
        if (currentX <= -textWidth)
            currentX = 0;
        Invalidate();
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;

        g.Clear(BackColor);

        using (SolidBrush brush = new SolidBrush(ForeColor))
        {
            g.DrawString(Text, Font, brush, currentX, 0, stringFormat);
            g.DrawString(Text, Font, brush, currentX + textWidth, 0, stringFormat);
        }
    }

有任何想法吗?

更新:

正如sallushan 建议的手动双缓冲,让我补充一下,我之前已经尝试过,使用下面的代码。但在屏幕上它看起来与上面完全一样,所以问题似乎并不在于我的 OnPaint 方法。我想它一定在我的控件设置中的某个地方。

    private Bitmap backBuffer;

    protected override void OnPaint(PaintEventArgs e)
    {
        if (backBuffer == null)
            backBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);

        Graphics g = Graphics.FromImage(backBuffer);

        g.Clear(BackColor);

        using (SolidBrush brush = new SolidBrush(ForeColor))
        {
            g.DrawString(Text, Font, brush, currentX, 0, stringFormat);
            g.DrawString(Text, Font, brush, currentX + textWidth, 0, stringFormat);
        }

        g.Dispose();

        e.Graphics.DrawImageUnscaled(backBuffer, 0, 0);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        // Don't call base!
    }

    protected override void OnSizeChanged(EventArgs e)
    {
        if (backBuffer != null)
        {
            backBuffer.Dispose();
            backBuffer = null;
        }
        base.OnSizeChanged(e);
    }
4

6 回答 6

5

在您的表单中设置此代码。它将消除闪烁

 protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x02000000;

                return cp;
            }
        }

希望能帮助到你

于 2011-08-19T07:25:50.317 回答
1

创建一个常量位图作为框架。并在该位图上进行更改,然后在控件上呈现该位图。这应该消除闪烁。

class MyControl : System.Windows.Forms.Control
{
    Bitmap bmp;
    Graphics g;
    int x = 100;

    public MyControl()
    {
        this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint | System.Windows.Forms.ControlStyles.UserPaint | System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer , true);

        bmp = new Bitmap(100, 100);
        g = Graphics.FromImage(bmp);
    }        

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        this.g.FillRectangle(Brushes.White, new Rectangle(0, 0, 100, 100));
        this.g.DrawString("Hello", this.Font, Brushes.Black, (float)x, 10);

        e.Graphics.DrawImage(bmp, new Point(0, 0));

        x--;
    }
}
于 2011-08-19T10:23:33.787 回答
0

尝试使用Invalidate(rectangle) 或 Invalidate(Region)方法将重新绘制的数量限制为实际需要完成的数量。您当前正在每个 OnPaint 事件期间重绘整个控件。

于 2011-08-19T07:04:22.987 回答
0

好吧,除了三重缓冲的想法——坦率地说,我从来没有在我的自定义控件中使用过,而且我有相当复杂的想法——我会说每 10 毫秒使控件无效与它有关。也就是每秒 100 次。就运动的流畅性而言,25fps 的电影对人眼来说是舒适的,但您重新绘制控件的次数是其 4 倍。

尝试更高的值:比如 40。

此外,当您重新绘制控件时,您可以只重新绘制它的一个区域,即发生变化的部分:形成旧文本位置和新文本位置的两个区域的并集。您不需要重新绘制任何仍然存在的周围区域(无论是背景还是其他)。

与 TextRenderer 的DrawText相比,DrawString() 相当慢- 大约 6 次,您可能想要使用它。您将失去一些功能(使用 GDI,而不是 GDI+),但它可能适合您的场景。

哦,我还会对您在外部的每个 OnPaint() 中作为类成员重新创建的 SolidBrush 画笔进行微优化并拉出,仅在 ForeColor 更改时更新它。

于 2011-08-19T11:25:39.413 回答
0

看起来您正在绘制彼此相邻的 2 个字符串:

g.DrawString(Text, Font, brush, currentX, 0, stringFormat);
g.DrawString(Text, Font, brush, currentX + textWidth, 0, stringFormat);

这应该只是一个Invalidate仅对已更改的控件部分进行操作的情况。最简单的方法是缓存/计算字符串的高度并执行以下操作:

 Invalidate(new Rectangle((int)currentX, 0, (int)textWidth*2, (int)textHeight));

在我的机器上,这明显比使整个控件无效更平滑。

于 2011-08-19T11:28:52.953 回答
0

将 Hans Passant 的评论变成答案,以便我接受:

这种效果不叫闪烁,叫撕裂。您会看到旧位图的一部分和新位图的一部分。这在移动物体上变得非常明显,它们看起来很紧张。在 Winforms 中无法修复,谷歌“垂直消隐间隔”。——汉斯·帕桑特

于 2011-08-20T00:46:04.787 回答