尽管有双缓冲,股票行情仍然闪烁

发布于 2024-11-30 12:10:45 字数 2964 浏览 3 评论 0原文

有谁知道如何消除闪烁?我研究了网络,并尝试了许多不同的东西,例如将 TickerControl 放入双缓冲面板中 不在 OnPaint() 中绘制时进行双缓冲:为什么不这样做工作?等等,还有很多其他的事情。它仍然闪烁,不是每次重新绘制时都会闪烁,而是每秒闪烁几次。

此外,即使在 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);
    }

Does anyone have an idea about how to get rid of flickering? I researched on SO, the web, and tried out many different things like putting TickerControl into a double buffered Panel a la Double Buffering when not drawing in OnPaint(): why doesn't it work? etc. besides many other things. It still flickers, not on every repaint, but a couple times per second.

Also, even after removing the "g.Clear(BackColor)" in OnPaint, something must still be clearing the background, as the text continues to scroll readably.

Here the relevant parts of my TickerControl class:

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);
        }
    }

Any ideas?

Update:

As sallushan suggested manual double buffering, let me add that I had tried that before, using the code below. But on screen it looks exactly like the above, so the problem does not seem to lie inside my OnPaint method. I guess it must be somewhere in the setup of my Control.

    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);
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

那请放手 2024-12-07 12:10:45

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

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

                return cp;
            }
        }

希望有帮助

Set this code in your form. It will remove the flicker

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

                return cp;
            }
        }

Hope it helps

深海少女心 2024-12-07 12:10:45

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

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--;
    }
}

Create a constant bitmap as a frame. And make your changes on that bitmap then render that bitmap on the control. This should remove the flickering.

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--;
    }
}
灯角 2024-12-07 12:10:45

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

Try using the Invalidate(rectangle) or Invalidate(Region) methods to limit the amount of repainting to that which actually needs to be done. You are currently redrawing the complete control during every OnPaint Event.

那片花海 2024-12-07 12:10:45

好吧,除了三重缓冲的想法(坦率地说,我从未在自定义控件中使用过它,而且我的自定义控件非常复杂)之外,我想说的是,每 10 毫秒使控件无效与此有关。即每秒 100 次。 25 fps 的电影在运动的流畅性方面对人眼来说很舒适,但您重新绘制控件的次数比这多 4 倍。

尝试更高的值:比如 40。

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

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

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

Well, apart from the triple buffering idea - which frankly I never used in my custom controls, and I had quite complex ones -, I would say that invalidating the control every 10ms has something to do with it. That is 100 times a second. Movies with 25fps are comfortable for the human eye in terms of fluidity of motion, yet you repaint your control 4 times more than that.

Try a higher value: say 40.

Also, when you repaint the control, you can repaint just a region of it, that parts that changed: the union of the two regions that form the old text location and new text location. You dont need to repaint any surrounding area that is still in place (be it background or whatnot).

DrawString() is pretty slow compared to TextRenderer's DrawText - around 6 times, you might want to use that. You will loose some features (uses GDI, instead of GDI+) but maybe it suits your scenario.

Oh, I would also micro-optimize and pull out that SolidBrush brush you re-create in every OnPaint() outside as a class member, update it only when ForeColor changes.

死开点丶别碍眼 2024-12-07 12:10:45

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

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

这应该只是仅使控件中已更改的部分无效的情况。最简单的方法是缓存/计算字符串的高度,然后执行以下操作:

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

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

As it looks like you're drawing 2 of the string next to each other:

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

It should simply be a case of just Invalidate'ing only that part of the control which has changed. The simplest method would be to cache/calculate the height of the string and do this:

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

On my machine, this is noticably smoother than invalidating the whole control.

两人的回忆 2024-12-07 12:10:45

将 Hans Passant 的评论转化为答案,以便我可以接受它:

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

Turning Hans Passant's comment into an answer so that I can accept it:

This effect is not called flicker, it is called tearing. You see part
of the old bitmap and part of the new bitmap. Which becomes very
noticeable on moving objects, they appear jittery. Not fixable in
Winforms, google "vertical blanking interval". – Hans Passant

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文