Jeremy
Jeremy

Reputation: 15

Prevent flickering of custom ProgressBar

For the app I'm making, I made a custom ProgressBar based on the one made by William Daniel: How to change the color of progressbar in C# .NET 3.5?

After doing some testing with lower percentage values I noticed this weird flicker:

The weird black lines shouldn't be there

I tried setting DoubleBuffered to true but they still show up.

Does anyone know why these weird lines show up?

Upvotes: 1

Views: 964

Answers (1)

Jimi
Jimi

Reputation: 32248

That's not flickering, that's a form of tearing. The Custom Control is not always drawn completely; it can happen if you have a close loop that doesn't allow the Form to repaint itself and its child controls correctly.
Possibly, make the procedure that fills the ProgressBar asynchronous.

Some suggested modifications to make it smoother:

  • Use floating point values for your calculations (don't cast to int), and use a RectagleF to define the bounds of your drawing.

  • Remove the Bitmap and set ControlStyles.OptimizedDoubleBuffer:

    If true, the control is first drawn to a buffer rather than directly to the screen, which can reduce flicker. If you set this property to true, you should also set the AllPaintingInWmPaint to true.

  • Then of course set ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint:

    AllPaintingInWmPaint: If true, the control ignores the window message WM_ERASEBKGND to reduce flicker. This style should only be applied if the UserPaint bit is set to true.
    UserPaint: If true, the control paints itself rather than the operating system doing so. If false, the Paint event is not raised. This style only applies to classes derived from Control.

  • ControlStyles.Opaque removes the background and lets ProgressBarRenderer do its job, to fill the base graphics of the ProgressBar.

Calculate the current width of ProgressBar's colored parts based on the current Value set:
float width = (this.Width - 3) * ((float)this.Value / this.Maximum)
If width > 0, then paint the ProgressBar colors using the Forecolor and BackColor properties. Of course you could use a different set of properties to define these colors.

Add some anti-alias to the drawing, setting the Graphics.SmoothingMode, to make the color transition generated by the LinearGradientBrush smoother. This is more useful when you set LinearGradientMode.Horizontal.

Resulting in:

Custom drawn ProgressBar


public class ProgressBarCustom : ProgressBar
{
    public ProgressBarCustom()
    {
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | 
            ControlStyles.AllPaintingInWmPaint | 
            ControlStyles.UserPaint | 
            ControlStyles.Opaque, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        ProgressBarRenderer.DrawHorizontalBar(e.Graphics, ClientRectangle);

        float width = (Width - 3) * ((float)Value / Maximum);
        if (width > 0) {
            var rect = new RectangleF(1, 1, width, Height - 3);
            e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
            using (var brush = new LinearGradientBrush(rect, BackColor, ForeColor, LinearGradientMode.Horizontal)){
                e.Graphics.FillRectangle(brush, rect);
            }
        }
    }
}

As a small improvement, you could draw a line 9 pixels in height, with a semi-transparent color, to the top side of the ProgressBar, to simulate the original reflection.
Change the code to add Graphics.DrawLine():

// [...]
using (var brush = new LinearGradientBrush(rect, BackColor, ForeColor, LinearGradientMode.Horizontal))
using (var pen = new Pen(Color.FromArgb(40, 240,240,240), 9)) {
    e.Graphics.FillRectangle(brush, rect);
    e.Graphics.DrawLine(pen, 1.0f, 1.0f, width, 1.0f);
}

Custom drawn ProgressBar + reflaction

Upvotes: 2

Related Questions