1

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?

Jimi
  • 29,621
  • 8
  • 43
  • 61
Jeremy
  • 15
  • 5

1 Answers1

2

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

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Had a bit of trouble with this at first since it seemed to make things worse. That was until I found that it's better to use `this.Refresh()` instead of `this.Invalidate()`. Thanks for the answer! – Jeremy Mar 08 '21 at 13:16
  • *At first*: and after that? -- If you have some problems with this implementation, post the code that uses it, so I can take a look and suggest how to fix it. – Jimi Mar 08 '21 at 13:19
  • It seemed to cause a worse gradient and retain its tear, but this was probably since I invalidated the update rectangle every time the value changed. Refreshing it instead works a lot better and removes all tear. – Jeremy Mar 08 '21 at 13:21
  • I should work calling `Invalidate()` (asynchronous). If you call `Refresh()` (synchronous), you block the current operation (what triggered the `ProgressBar.Value` change) while the drawing is performed (not that it takes much, but...). So, you probably have a synchronous operation going on (note that the standard ProgressBar actually uses a Timer to constantly repaint itself, so it's an *improvement* anyway). You should try make your code asynchronous, everything will work better at all levels. – Jimi Mar 08 '21 at 13:25