Click here to Skip to main content
Click here to Skip to main content
Go to top

Extended ProgressBar

, 17 Jun 2005
Rate this:
Please Sign up or sign in to vote.
Progressbar with percentage indicator.

Sample Image

Introduction

This is my first article at CodeProject and my first (English-language) article at all, so forgive my syntax mistakes and poor language phrases (English is not my native language).

While coding an application, I faced a situation where I wanted an indicator about my application working progress so I dropped the .NET ProgressBar and completed my work. After test running my application, I noted that this default control did not fit in my case. I think it doesn't fit in many other cases and is not sufficient when dealing with a serious well designed UI application. So I started searching and looking for some more powerful Progress controls that could help me. I found a couple dozen of them hanging around all over the net but no one matched my percentage indicator goal, so I decided to implement my own control and that was the beginning of this article.

Using the code

Let's start this mid-sized article step by step (I think it's better for beginners to download the source code to be in touch with any explanation that might be confusing).

Preparing the scene stage

First of all, when you code some intensive drawing functionality, a small reflection may appear that prevents the normalized look of the drawing operation, or at least reflects a badly optimized drawing algorithm. Well.. that’s a situation where a little low-level optimization may fit, but this low-level optimization requires more experience and more hard work to implement. Fortunately, .NET provides the perfect solution with the price of one more line of code, by calling the SetStyle (System.Windows.Froms.Control.SetStyle) function with the appropriate parameters. All the (behind the scene) work is done for you here.

The SetStyle function expects a value of ControlStyles enumerator (System.Windows.Forms.ControlStyles) that specifies what to do with the control. This enumeration has the FlagsAttribute which indicates that the members of the enumeration can be combined using a bitwise OR operator (|). The members of ControlStyles that concern us here are those members that reduce the flicker in drawing our control:

  • ControlStyles.UserPaint: causes the control to draw itself rather than the OS.
  • ControlStyles.AllPaintingInWmPaint: causes the control to ignore the WM_ERASEBKGND message.
  • ControlStyles.DoubleBuffer: causes the control to use a buffer with the drawing operation.

At this point, you just need to understand that combining all these parameters will reduce the flicker and satisfy our goal. For a better understanding of these members, you can surly refer the MSDN.

Reduce the flicker:

this.SetStyle(ControlStyles.UserPaint | 
  ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer,true);

Although our control already uses the SetStyle function, there is another use of it which may fit in this situation (and if not, I decided to mention it for the sake of completeness). The feature I'm talking about is the background transparency of the control, i.e. the control shows its parent control background color instead of its own (of course, when its background is set to Transparent!).

To enable the transparency of a control you could use the SetStyle function and turn on the ControlStyles.SupportsTransparentBackColor flag. This option allows your control to accept a color with an alpha component of less than 255 for its BackColor property (a color with 255 alpha component is a completely opaque color and a color with 0 alpha component is a fully transparent color).

Allow the control transparent background color (optional and may be skipped):

this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); 

Coding actual work

You might now be thinking "What the hell is this guy talking about? and where is the Progress related stuff? is there any real code explanation?". Well.. yes, there is such an explanation and a large amount of it, but keep in mind that the actual idea behind coding a progressbar control and specially calculating its percentage value is a matter of math guys stuff, and I'm not going to give you a lecture about mathematics, addition, multiplication ..etc, nor explain the (area of a rectangle, size of circle, and length of a vector) rules.

What we really need here is a logical width to draw the work progress to it. You might think that we could use the value reached (the current drawing edge) till now and draw according to it, keeping the width of the control in mind! That's a perfect solution, but what if the user of the control decides that the maximum value must be greater than the control width or less than it?? The perfect solution won't be perfect anymore. It will work fine in cases of equality of both the control width and the maximum value. To overcome this problem, think of this calculation:

  • ( (Control Width) * (Current drawing value) / ( Maximum value - Minimum value) )
drawingWidth = (int)(this.Width * _value) / (maxValue - minValue);

This will give us the exact point at which the drawing should reach (as a float value that would be converted into int).

What about the percentage of the current point? Another calculation would be enough:

  • ( (Current drawing value / Maximum value) * 100 )
percentageValue = (_value / maxValue) * 100;

After calculating these values we may continue drawing our control as a filled rectangle from (0,0) to (logical width, control height), and that's all it takes!

e.Graphics.FillRectangle(_Drawer, 0, 0, drawingWidth, this.Height);

To this point in the code, we have a regular but colored progress control, and all we need is to add the percentage value (we just calculated) to it, and that's a simple DrawString function call with the correct values passed to it. These values depend on the style of percentage we want to draw. There are three styles for percentage drawing (None, Center, Movable), don't be confused, handling them is too simple. Now do the following:

  • For the None style don't draw any percentage at all (too hard to code?!!).
  • For the Center style just draw the percentage value centered vertically and horizontally (that's all shown in the code).
  • For the last Movable style draw the percentage value centered vertically and horizontally located at our logical width value (also coded).

What else?

I have used other techniques that I think are worth some explanation.

  1. LinearGradientBrush located in System.Drawing.Drawing2D is a brush that has more than the base color all mixed together in a gradient manner.
  2. ColorBlend also located in System.Drawing.Drawing2D is used for mixing more than two colors in a gradient brush.
  3. ControlPaint located in System.Windows.Forms is used to extract dark and light values of a specified color.

Note that what I just said is not by any means an explanation of these classes or their functionality, not at all. All I have said is for use in this article only and doesn't represent an actual reference on using them. Every one of these classes deserve a dedicated article for its own, but you can always refer to the MSDN for further investigation if you are interested.

Final Version

Whether to write the previous details in the Main or in a different function is not a good question to ask here, but for those who haven't caught it yet (if any), you should put it in the Paint event handler, and the actual code should look something like this:

private void ProgressEx_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    // Just draw inside the control.
    if(_value != 0 & _value <= maxValue)
    {
        // Calculate the right side of drawing edge
        drawingWidth=(int)(this.Width * _value) / (maxValue - minValue);

        // Calculate the Percentage according to the logical value reached
        percentageValue = (_value / maxValue) * 100;

        // Tie our color mixer with the just created brush
        _Drawer.InterpolationColors = gradientBlender;

        // Now we ready to draw, so do the actual drawing
        e.Graphics.FillRectangle(_Drawer, 0, 0, drawingWidth, this.Height);

        // Prepare for Percentage writing only when required
        if(percentageDrawingMode != PercentageDrawingMode.None)
        {
            string text=((int)percentageValue).ToString() + "%";

            // Calculate Percentage rectangle size
            SizeF textSize=e.Graphics.MeasureString(text,writingFont);

            if(percentageDrawingMode == PercentageDrawingMode.Movable)
            {
                e.Graphics.DrawString(text,writingFont, 
                   writingBrush,drawingWidth,
                   (e.ClipRectangle.Height / 2 - textSize.Height / 2));
            }
            else if(percentageDrawingMode == PercentageDrawingMode.Center)
            {
                e.Graphics.DrawString(text, writingFont, writingBrush, 
                  new PointF((e.ClipRectangle.Width / 2 - textSize.Width / 2),
                  (e.ClipRectangle.Height / 2 - textSize.Height / 2)));
            }
        }
    }
}

Mission accomplished

As we finish our custom user control, I have a few words to say:

This control doesn't inherit from the ProgressBar class (System.Window.Forms.ProressBar). Instead it's inherited from the Control class (System.Windows.Forms.Control). That's why there is no override for Paint or Maximum (or any other members that originally exist in the ProgressBar control) in it. And if you feel uncomfortable with the source code, then you have to look for an entire article about creating custom controls (inheriting from System.Windows.Forms.Control or System.ComponentModel.Component) or some related thing (there are some good articles in CodeProject about this subject, search and read some of them).

Points of Interest

The wise men said "don't reinvent the wheel" (I don't know who these men are, nor do I know if they said that or even if they ever existed) and I totally agree with them! What I am trying to say is this:

If you want a rectangular wheel, don't bother yourself reinventing it, just inherit the existing one and override its circular shape and that should be fine. But on the other hand, doing some (from-scratch) work with a user control is worth the bother and a good practice, so give it a try. And keep in mind that starting a control inherited from System.Windows.Forms.Control doesn't mean changing the usability of the control (how it must be used), it's just changing the functionality (how it must act or perform). I.e. even if you start on your own risk a from-scratch user control that is to look like or act like an existing control, change the way it works but don't change the way it is used.

That’s it, thanks for reading this article, and if you find it useful, please give your feedback.

Enjoy! Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Noor Abuhamdeh
Team Leader
Jordan Jordan
NOOR ABUHAMDEH
Working as a Technical Team Leader in a software solutions company in Jordan, interested in .net technology, C++

Comments and Discussions

 
QuestionCan we use this in a commercial project Pinmembersuis16-Oct-08 5:43 
AnswerRe: Can we use this in a commercial project PinmemberNoor Abuhamdeh16-Oct-08 16:51 
Questionpaint on the progress bar Pinmemberxoerk10-Nov-07 22:17 
AnswerRe: paint on the progress bar PinmemberNoor Abuhamdeh12-Nov-07 20:18 
Generalbackground transparancy Pinmembergaliptopcu24-Jan-07 6:27 
Generalmultiple bars on a form PinmemberLarry Allen15-Sep-06 6:52 
Newsre: ProgressBar Pinmemberljscottiii16-Sep-05 5:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 17 Jun 2005
Article Copyright 2005 by Noor Abuhamdeh
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid