Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Progress-O-Doom

4.97/5 (251 votes)
21 Jan 2009CPOL15 min read 386.4K   13.6K  
A set of pluggable progress bar components
Image 1

Introduction

For reasons I can't explain, I have an irrational fascination with progress bars. Over the years, I'd see a progress bar I liked, and would try to recreate it in C# for my own use. This resulted in several concrete implementations that all shared a relatively small set of functionality in a single parent class. Then, not long ago, I saw several that I wanted to implement, but I wasn't interested in writing them from the ground up. However, I noticed that they all shared a lot of characteristics. Then, it finally dawned on me that I could build almost all of those examples and many more with a set of pluggable components that would paint certain parts of the progress bars. This project is the result of that revelation.

Component Structure

The idea, basically, is this... There are two concrete implementations for progress bars (plus, one specifically to recreate the WinRar dual progress bar), each building on the more general implementation. Those implementations use an instance of IProgressBackgroundPainter to paint a background, an instance of IProgressPainter, and an instance of IProgressBorderPainter to paint the border. Then, optionally, you can chain any number of IGlossPainter instances together to doctor up your progress and/or background painters. IProgressPainter can also use its own IProgressBorderPainter. IProgressPainter and IGlossPainter both have abstract implementations that provide some basic functionality. ChainedGlossPainter, the abstract implementation of IGlossPainter, is a decorator that allows you to chain IGlossPainters together for multiple effects.

By making all of these functional parts Components, you can drop them into your designer, and set their relationships and properties at design time. This makes it easy to experiment with different possibilities to find what best suits your needs and tastes.

Shown below is a diagram of the structure of the progress bar implementations, the painter interfaces, and their abstract implementations:

Image 2

All of the progress bar implementations have the basic properties you would expect to see (i.e., Maximum, Minimum, Value). They also have ShowPercentage, which is a boolean property that determines whether or not to paint the percentage text on the bar, and ProgressPadding which is an int representing the number of pixels to space between the progress painter and the borders. DualProgressBar adds MasterMaximum, MasterValue, MasterPainter, and PaintMasterFirst for controlling the master progress. The DualProgressBar was designed to function like the WinRar progress bar, which shows a silver progress for the overall progress, and a gold progress (always smaller) superimposed to represent the overall compression ratio. However, this bar can be used to show individual and overall progress, especially when the non-master progress has padding so that the master progress can be seen under it.

Background Painters

I currently have five implementations of IProgressBackgroundPainter, three of which were built for specific progress bars (WinRar, FruityLoops, and Candy Cane).

Image 3

The Plain Background Painter

PlainBackgroundPainter just paints the background with a single color. I use this background painter the most in combination with IGlossPainters. On its own, it's far from interesting, but it's the perfect base for adding glosses to.

Image 4

The Gradient Background Painter

GradientBackgroundPainter was built before the glosses were conceived of. It allows you to specify a top and bottom color, and fills the background with a gradient between those two colors. The same effect can be attained with the PlainBackgroundPainter and the GradientGlossPainter. So, this background painter is really just a convenient shortcut.

Image 5

The Candy Cane Background Painter

CandyCaneBackgroundPainter was created from an existing bar in my old collection of bars. It can probably also be recreated from the PlainBackgroundPainter and glosses. It does match the CandyCaneProgressPainter, but it also works well with virtually any other progress painter.

Image 6

The FruityLoops Background Painter

FruityLoopsBackgroundPainter was also built from an old bar I had that was based on the progress bars in the FruityLoops application. It has a matching progress painter. This background painter isn't very configurable, it was built for a specific set to emulate the FruityLoops bars.

Image 7

The WinRar Background Painter

RarBackgroundPainter was also built from an old bar based on the WinRar progress bars. It also has matching progress and border painters, which are used by the RarProgressBar. Because it was built specifically to emulate the WinRar bars, there isn't much that's configurable; you can't change the color of it, for instance.

Image 8

Progress Painters

There are nine implementations of IProgressPainter, most of which were based on my old collection of progress bars. They can all be decorated with glosses, but the plain implementation was designed to be the common canvas for glosses.

Image 9

The Plain Progress Painter

PlainProgressPainter, like the plain background painter, only fills the progress with a solid color, which makes it a great starting point for glosses. In addition to the Color property, you can set a color to the LeadingEdge property. Without setting a ProgressPadding value on the progress bar, you can get the same effect by setting the plain progress painter's ProgressBorderPainter property to an instance of IProgressBorderPainter.

Image 10

The Beveled Painter

BevelledProgressPainter also fills the progress with a single color, but it also bevels the progress with a two pixel border based on the base color set. Since the IProgressPainter interface defines it, all progress painters can have an IProgressBorderPainter set to its ProgressBorderPainter property.

Image 11

The Beveled Gradient Painter

BevelledGradientProgressPainter is basically the same as the BevelledProgressPainter except that it has the MinColor and MaxColor properties that it uses to fill the progress with a gradient.

Image 12

The Metal Progress Painter

MetalProgressPainter is a subtle but elegant custom design that builds from a single base color.

Image 13

The Barber Pole

BarberPoleProgressPainter is another custom design. This is one of the two progress painters that have a problem with the ProgressPadding property of all bars. When the control is resized, the painter rebuilds an image that fits the entire bar so that value changes only require the image to move. This is a limitation that I'm not overly concerned with at the moment.

Image 14

A Java Painter

JavaProgressPainter is a custom design based on a progress bar I saw in a Java app once upon a time. It's always padded, so setting padding will just alter it further, though you could also give it a negative padding to force it to fill the progress space.

Image 15

The Candy Cane

CandyCaneProgressPainter shares the same strategy, and therefore the same limitations, as the BarberPoleProgressPainter. It builds a partially transparent design based on a single base color.

Image 16

The FruityLoops Painter

FruityLoopsProgressPainter only has one configurable property of its own, the FruityType property which has two possible values, DoubleLayer and TripleLayer. It pads well, and can be glossed and/or bordered. But, it works best with the FruityLoopsBackgroundPainter it was designed to match.

Image 17

The WinRar Progress Painter

RarProgressPainter is a very simple progress painter. Though designed to work with the RarBackgroundPainter and the RarBorderPainter, it works well with other implementations because it is so simple. The ProgressType property corresponds to the two colors WinRar uses, Silver and Gold. The ShowEdge property draws a leading edge specific to the WinRar painter.

Image 18

Border Painters

Border painters can be used to border the entire progress bar, and they can also be used to border a progress painter. The IProgressBorderPainter interface requires implementations to expose a BorderWidth property so that the progress bars using it can determine what size the progress Rectangle should be. As with the other components, it has methods to paint and resize itself.

Image 19

The Plain Border Painter

With the PlainBorderPainter, you can specify a color for a flat border, or use its implementation of Sunken or Raised. The RoundedCorners property just makes the corner pixels partially transparent, which gives it a subtle rounding.

Image 20Image 21Image 22

The Styled Border Painter

The StyledBorderPainter uses the System.Windows.Forms.Border3DStyle values with the ControlPaint.DrawBorder3D() method to paint a border. As you can see with some of the examples below, some of these Border3DStyle borders have a single pixel border width on the left and two pixel widths for the other sides. Since the BorderWidth property on IProgressBorderPainter only allows you to specify a single border width, this border painter has some undesirable side effects, but they are subtle, and in most cases hardly noticeable.

Image 23Image 24Image 25

Image 26Image 27Image 28

Image 29Image 30Image 31

Image 32

The WinRar Border Painter

The RarBorderPainter was designed specifically for use with the RarProgressPainter and RarBackgroundPainter, but it does give a nice raised outer look to the bars.

Image 33

Gloss Painters

The glosses were designed to allow you to put various gradients with varying transparency on top of progress bars and backgrounds. There are four gloss implementations, all of which have a Successor property that allows you to chain them together for multiple effects.

Image 34

Flat Gloss

The FlatGlossPainter just blankets an area with a color. You can control that area by setting the Style (Top or Bottom) property and the PercentageCovered property. It allows you to specify a color and an alpha value for that color.

Image 35Image 36

Gradient Gloss

GradientGlossPainter gives you a gradient over a percentage of the top or bottom, or over the entire surface. It uses the base color combined with the AlphaHigh and AlphaLow properties for the gradient. You can also control the angle. The example uses an angle of 90; 270 would invert what's shown.

Image 37Image 38Image 39

Middle Gloss

The MiddleGlossPainter gradients from the middle out. Again, through the Style property, you can gloss the top, bottom, or full surface. Like the GradientGlossPainter, it uses one color and two alpha values for the gradient.

Image 40Image 41Image 42

Round Gloss

The RoundGlossPainter gradients off the top and/or bottom edges. Like the GradientGlossPainter and the MiddleGlossPainter, it uses one color and two alpha values for the gradient. Instead of a percentage, like the other glosses, this gloss has a TaperHeight property that allows you to set the number of pixels from the edge to the gradient. In the example below, the TaperHeight is set to 8.

Image 43Image 44Image 45

Some Code

Most of the magic is just simple GDI+ stuff. The real strength here is in the structure of how components are arranged and how they control the Rectangles passed to the other components. However, there are a couple of things that might be interesting to note.

The PropertiesChanged Events

The following was used to facilitate access to the PropertiesChanged event to avoid registering a listener more than once. Perhaps a little lazy on my part, but it avoids issues in the long run. With this many components talking to each other, I wanted to ensure I wasn't repeating redraws or other function calls unnecessarily.

C#
private EventHandler onPropertiesChanged;
/// <summary></summary>
public event EventHandler PropertiesChanged {
    add {
        if (onPropertiesChanged != null) {
            foreach (Delegate d in onPropertiesChanged.GetInvocationList()) {
                if (object.ReferenceEquals(d, value)) { return; }
            }
        }
        onPropertiesChanged = (EventHandler)Delegate.Combine(onPropertiesChanged, value);
    }
    remove { onPropertiesChanged = (EventHandler)Delegate.Remove
                (onPropertiesChanged, value); }
}

Using the DualProgressBar

The concept of the dual progress bar is somewhat different from what most people are used to working with. To help understand what it's doing, the following code is what was used in the DualTests form of the demo app. It sets the master max to 10,000, and the max to 2000 (because I knew ahead of time that I wanted five iterations). Then, as it loops five times, it resets the Value property, and increments both the Value and the MasterValue properties. This example shows how the dual progress bar can be used to display an overall progress with individual progresses.

C#
private bool go = false;
private void button1_Click(object sender, EventArgs e) {
    go = true;
    dualProgressBar1.Value = 0;
    dualProgressBar1.MasterValue = 0;
    dualProgressBar1.Maximum = 2000;
    dualProgressBar1.MasterMaximum = 10000;
    for (int i = 0; i < 5; i++) {
        if (!go) { break; }
        dualProgressBar1.Value = 0;
        for (int j = 0; j < 2000; j++) {
            if (!go) { break; }
            dualProgressBar1.Value = j;
            dualProgressBar1.MasterValue++;
            Application.DoEvents();
        }
    }
    dualProgressBar1.Value = 0;
    dualProgressBar1.MasterValue = 0;
}

Though the master progress shares the Minimum value of the non-master progress, through the MasterMaximum and MasterValue properties, it behaves just like any other progress.

Something to be Aware of

IGlossPainter's Successor property does test to ensure that you're not setting a gloss as its own successor, but it does not test for a larger reference loop. Since each gloss asks its successor (if not null) to repaint itself, a loop in gloss references will result in an infinite repaint loop. Since this happens at design time, Visual Studio will die if you do this. If anyone has a suggestion on testing for referential loops like this, I'd love to hear it.

C#
public abstract class ChainedGlossPainter : Component, IGlossPainter, IDisposable {
    private IGlossPainter successor = null;

    /// <summary></summary>

    public IGlossPainter Successor {
        get { return successor; }
        set {
            if (object.ReferenceEquals(this, value)) {
                throw new ArgumentException("Gloss cannot be it's own successor, 
                    an infinite loop will result");
            }
            successor = value;
            if (successor != null) {
                successor.PropertiesChanged += 
        new EventHandler(successor_PropertiesChanged);
            }
            FireChange();
        }
    }
    ...

Using the Components

First, you'll want to drop one of the progress bars on your form (ProgressBarEx, DualProgressBar, or RarProgressBar), then also add a background painter, border painter, and progress painter to your form, along with any gloss instances you might need.

Image 46

Once you've added the components, set the ProgressPainter, BorderPainter, and BackgroundPainter properties of your progress bar to the appropriate components.

Image 47

Then, if you've added glosses to the form, you can set the GlossPainter properties of your other painters. Each gloss painter can be used on more than one component too, so the background and progress painters could share a gloss.

Image 48

And, if you want to chain glosses together, set each of the Successor property of each one you wish to chain, to the next in the chain. Just remember that they are painted in the order they are chained. Also, remember that you must avoid having a chain that results in a gloss referencing itself anywhere in its chain, which will result in an infinite redraw loop that will kill Visual Studio.

Image 49

Conclusion

This project was done for fun, and I have very little doubt that there are still some bugs in there. I'm no expert in GDI+ or custom controls, so there's likely plenty of room for improvement here, and I would love to hear suggestions from those of you who have more expertise than me (or even from those who haven't :). But, it is functional, and it makes for some nice, versatile looking bars. Below are some examples created during development and testing. As you can see, there are a lot of possibilities with these components.

Image 50

All of the examples on the left were done with plain controls and gloss. The examples on the right were done with the specific painters, with the exception of the non-WinRar dual bars, which were all done with plain painters and gloss.

Future Plans

  • I'd like to add something to facilitate sectioned progress bars, like the standard Windows bars.
  • I'd also like to provide more options for rounding borders and bars.
  • And, of course, I'd also like to find solutions to the limitations mentioned above (variable width borders, the padding issue with image based bars, etc.).

History

  • 12/19/2008

    Initial release
  • 12/23/2008

    • As suggested by Acshi, the Successor accessor has been updated in ChainedGlossPainter to better detect circular references.
    • Also, this update includes functionality for Marquee bars. The AbstractProgressBar class has four new properties: ProgressType, MarqueeSpeed, MarqueeStep & MarqueePercentage (I'll try to update diagrams at a later time). ProgressType is an enum with the values Smooth (normal), MarqueeWrap (runs off frame to the right, then comes back in frame from left), MarqueeBounce (bounces between right and left) & MarqueeBounceDeep (also bounces, but goes out of frame before rebounding). Speed is an int for milliseconds between updates. Step is an int for the number of pixels to move between updates. And MarqueePercentage determines the width of the marquee bar.
    • AbstractProgressBar also includes three abstract methods for marquee functionality, which are implemented in ProgressBarEx: MarqueeStart(), MarqueePause() & MarqueeStop(). I've never liked how the default progress bar handles marquee operations, and have seen many people confused by and/or complaining about it. So I decided to use methods for starting and stopping animation.
    • Since DualProgressBar extends ProgressBarEx, it inherits this functionality. And you can also still use the master progress normally. This might come in handy if you have long operations where you want to show the actual progress, but still have animation during the wait. This scenation is simulated in the new Marquee Test form. Just be sure to set the PaintMasterFirst property to true.

      Image 51

      I'm not sure that I'm completely satisfied with this implementation, but it does establish a usable interface for this functionality, and it seems to work well enough (with the notable exceptions of the BarberPoleProgressPainter & the CandyCaneProgressPainter, which don't behave correctly due to previously mentioned limitations).

  • 01/18/2009

    Image 52
    • The ProgressType enum has a new value Animated.
    • The IAnimatedProgressPainter interface was added.
  • The ProgressBarEx.ProgressPainter property now does some validation when being set. The ProgressType.Animated value is only valid when the ProgressBarEx.ProgressPainter is an instance of IAnimatedProgressPainter. If an non-animated progress painter is assigned to ProgressBarEx.ProgressPainter, and it currently has the ProgressType.Animated value, the ProgressType will be changed to ProgressType.Smooth.
  • If the ProgressBarEx.ProgressType is set to ProgressType.Animated while ProgressBarEx.ProgressPainter is set to a non-animated progress painter, an ArgumentException will be thrown.
  • ProgressBarEx has two new methods, StartAnimation() and StopAnimation(). It manages an internal timer that it uses (with the MarqueeSpeed) to update animation. When animating, it sets the Animating property of it's animated progress painter, then it's timer callback method just invalidates the control to be repainted (the animated implementations update an internal variable to keep track of framing, if it's Animating property is false, it does not use the frame counter).

Added Classes

Image 53

There are two new progress painters that implement IAnimatedProgressPainter: StripedProgressPainter and WaveProgressPainter. The StripedProgressPainter is very similar in appearance to the BarberPoleProgressPainter, but it doesn't share it's padding limitations because it uses GraphicsPaths instead of painting an image that is moved. It also allows you to set both colors. The WaveProgressPainter is similar in functionality except that it uses bezier curves instead of stripes. (I'm actually a little disappointed in the wave painter, it's not quite as cool as I thought it would be. Maybe you can do more with it, and maybe you'll like it more than I did) The demo sets the progress bar's Value property to max to display the animation, but this is not required.

To use the animation, all you have to do use the ProgressBarEx.StartAnimation() and ProgressBarEx.StopAnimation() methods. If a non-animated progress painter is the current progress painter, these methods with do nothing.

Image 54

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)