6

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);
    }
Community
  • 1
  • 1
Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
  • 4
    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 Aug 19 '11 at 11:38
  • Your sample code doesn't flicker on my Win7-64. BTW, you don't assign textWidth a value in your code. – LarsTech Aug 19 '11 at 11:41
  • @Hans: Thanks for the info. When I first read your comment, I thought you were kidding me. Especially as I've better tickers 10 years ago in a freaking Java 1.0 applet, when there still were CRTs around that did actually have a "vertical blanking interval". Looks like I'm out of luck here - but will try to look at the DirectX option you mentioned in another answer here on SO. P.S. Why don't you make your comment an answer? I'd accept it. – Evgeniy Berezovsky Aug 19 '11 at 11:58
  • @Lars: Yep, I did not paste that textWidth bit. I'm on Win7-64, too. And here, it does, well, if not flicker, at least it does 'tear'. – Evgeniy Berezovsky Aug 19 '11 at 12:04

6 Answers6

6

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

Marshal
  • 6,551
  • 13
  • 55
  • 91
  • 1
    It did not remove the flicker. I tried that snippet on the Form and on the Panel that contains the TickerControl, to no avail. Btw., it would be nice if you added the meaning of 0x02000000 so that it can be understood or googled more easily. It is the constant WS_EX_COMPOSITED. – Evgeniy Berezovsky Aug 19 '11 at 09:56
  • 3
    Don't bicker about the ticker flicker! – Russ Clarke Aug 19 '11 at 11:00
  • 9
    Russ, although your comment did evoke a little snicker, a no-flicker ticker would be even slicker. – Evgeniy Berezovsky Aug 19 '11 at 11:11
  • While using this ExStyle, I got 100% cpu-load (winXP .net4). Is there a solution for this? – basti Jun 05 '12 at 10:15
  • On windows 7 (using Aero), this madfe my flickering problem even worse. – Sam-Elie Feb 19 '13 at 10:10
  • I don't even want to know why this works. It is so beautiful how nice my UI is now. – John Buchanan Apr 13 '16 at 04:42
  • It actually helps. But some times, I get issue in painting the control. It shows black screen. After minimize and maximize, it shows properly. Seems some kind of timing issue. Can anyone help? – Arpit Gupta May 02 '18 at 07:39
1

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--;
    }
}
sallushan
  • 1,134
  • 8
  • 16
  • sallushan, I tried manual double buffering, but it looks just the same as WinForms' builtin OptimizedDoubleBuffering (and of course I tried the non-optimized DoubleBuffering as well, to no avail). – Evgeniy Berezovsky Aug 19 '11 at 10:56
  • yes I realized it later. What I remember from QBasic experience we used to put a hardware "Wait" after frame rendering to resolve Vertical blanking Interval. But don't know how to do it here. – sallushan Aug 20 '11 at 15:44
0

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.

Mark Hall
  • 53,938
  • 9
  • 94
  • 111
  • In a ticker, basically everything moves all the time. How could I restrict the repainting to a smaller area? With double buffering, there should not be any need for this. – Evgeniy Berezovsky Aug 19 '11 at 07:15
  • @Eugene - Thats not true, you have a definite area which either a) Currently contains the text or b) Used to contain the text which could be invalidated. Simply calculate one rectange which contains the intersection of these areas and invalidate that! – Jamiec Aug 19 '11 at 11:21
  • Jamiec, a ticker scrolls, because it is too small to show all text at the same time. In other words, a real ticker will always be filled from left to right, and every change will basically affect the whole drawing area. Of course there are always many very small rectangles where nothing changes. But it would be a waste trying to figure those out and it wouldn't fix the flicker issue anyway. – Evgeniy Berezovsky Aug 19 '11 at 11:30
0

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.

Vladimir
  • 3,599
  • 18
  • 18
  • 1
    Well, regardless of what you pass in as the interval, the resolution of a `System.Windows.Forms.Timer` is 55ms, so it will never run that fast. At best you will get < 20fps. – Ed S. Aug 19 '11 at 16:07
  • That's nice info, never bumped into that. – Vladimir Aug 19 '11 at 16:23
  • Yeah, and even if it weren't, the normal system timer resolution is typically about 15ms. To get higher precision than that you would need to use the CPU's high performance counter. – Ed S. Aug 19 '11 at 17:29
  • @Ed You would be right, if MS' implementation adhered to their documentation. I measured, it doesn't. In a real client app (with heavy load during startup) I Stopwatch'd ~2000 Ticks of Forms.Timer. Here the observed intervals. min 1ms, max 1088ms, mean 16ms, standard deviation 26ms. This is .net 3.5 on Win 7 64bit. – Evgeniy Berezovsky Sep 02 '11 at 03:26
  • I forgot to mention: The Timer's interval in the previous comment was set to 1ms. – Evgeniy Berezovsky Sep 02 '11 at 08:58
0

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.

Jamiec
  • 133,658
  • 13
  • 134
  • 193
0

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

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156