BusyBar: An Advanced WinForms Progress Control

The only progress bar you will ever need.
By Nicholas Butler

profile for Nicholas Butler at Stack Overflow, Q&A for professional and enthusiast programmers
Publicly recommend on Google


Sample Image - BusyBar_2.png

Introduction

There are a lot of articles about progress bars (about a dozen on CodeProject [^] ) and they all have different properties. My aim in writing BusyBar was to make all the different styles available in one control. I have coded about twenty-five styles, but the real beauty of BusyBar is that it is easily extensible. I hope that people will give me new styles as they write them, to include in this project.

Using the Code

First, you must add BusyBar to your project. I have packaged BusyBar as a single C# source file (BusyBar.cs) and its associated RESX file (BusyBar.resx). Visual Studio is a bit temperamental, so please follow these instructions in order:

  • Copy the two files to your project directory.
  • Add a reference to System.Design.dll to your project.
  • Add BusyBar.cs to your project; BusyBar.resx will be added automatically.
  • Open BusyBar.cs in the designer; I have no idea why this is necessary.
  • Compile your project.
  • Add your assembly to your toolbox.

Here are detailed instructions for the "Add your assembly to your toolbox" step (it's not that obvious):

  • Display your toolbox.
  • Select the "My User Controls" tab on your toolbox.
  • Right-click and select "Add/Remove Items..."
  • The "Customize Toolbox" dialog appears.
  • Click "Browse..."
  • Browse to the (project)/bin/Release folder and select your *.exe.
  • Click "OK."

I have also now packaged it in a control library, as requested for VB.NET developers. You should now have the BusyBar control and some Painter components in your toolbox. Note that, they will only be enabled when you have a form open in design view. You are now ready to add a BusyBar control to your form. First, a quick diagram to help you to understand how BusyBar works:

The BusyBar class is a custom control, and handles the control stuff, like the CornerRadius, and the data properties, like the minimum, maximum and current values. It is also responsible for drawing the border, but it delegates the rest of the painting to an instance of IPainter. I have written some implementations of IPainter, each of which has various Preset settings.

You are now ready to add an instance of BusyBar to your form. This will draw the border, but you need to select an IPainter to draw the client area. You can do this in one of three ways:

  1. Add an instance of one of the Painters to your form ( they all derive from Component ), and set the PainterObject property of your BusyBar to point to your Painter. This is probably the easiest way. You can then set the properties of your Painter from within the form designer. Note that all the Painters have a Preset property. This is not persisted, but setting it will set the properties of your BusyBar and Painter to some predefined values.
  2. Set the value of the PainterPreset property of your BusyBar to one of the enumeration values. At runtime, the BusyBar will create an instance of the corresponding Painter. You can access this object through the PainterWorker property of your BusyBar, and set its properties in the code behind your form.
  3. Do it all in code. You can set either the PainterObject or PainterPreset properties of your BusyBar in code, and then set the Painter's properties through the PainterWorker property as above. This is the way to use a custom Painter if you write your own.

And that's it. :) You can use the demo to test various settings, and to see the different styles available so far.

Adding Design-time Bitmaps

You will notice that the BusyBar control and the Painter components have default bitmaps in your toolbox. If you want pretty bitmaps, you have to follow a few more steps:

  • Add the bitmaps to your project. Create a new folder called "Bitmaps" in your project and copy the bitmap files into it.
  • Set the Build action. Select the bitmap files in Solution Explorer and change the "Build Action" property to "Embedded Resource."
  • Set your default namespace. There is a class called ResFinder defined at the top of the BusyBar.cs file. Change the value of the DefaultNamespace constant to your default namespace. See "Points Of Interest" for an explanation.
  • Build your project. You may have to Rebuild your Solution.
  • Delete the old Toolbox items from your toolbox.
  • Add your assembly to your toolbox again.

You should now have shiny new bitmaps showing in your Toolbox :)

The Painters

This section will help you to choose a Painter, and explain the properties specific to each. I haven't included images of the different presets as there are too many. You can run the demo to see examples of what is possible.

PainterLine

This was the first painter I wrote, just to test the BusyBar control. So it's quite simple, but may be of some use. It only has one preset, "Bar," which sets the width of the line to 50, so it appears as a block of colour.

PainterXP

This is an attempt to replicate the .NET ProgressBar control. This Painter introduces the concept of "Blocks." When the BlockLineWidth property is greater than 0, a series of lines are drawn over the bar. When the BlockLineColor property is set to the BackgroundColor of the control, this splits the bar up into blocks.

It has two presets: "System," which sets the Bar.Pin property to BusyBar.Pins.Start, which anchors the bar on the left or top sides; and "Startup," which is a copy of the bar XP shows at startup.

PainterPathGradient

This is one of the most useful and configurable Painters. It uses a PathGradientBrush to paint the bar, which means you can set the colour gradient in two dimensions. Set the Shape property to one of the enumeration values to specify the base shape, and then set the Color properties to define the brush.

It has five presets: "Kitt," "Circle," "Startup," "Startup2003" and "Noise." These show the large range of effects that this Painter can produce. You get points if you know where the "Kitt" preset comes from :)

PainterClock

This Painter works best for a larger, square or slightly rectangular BusyBar control. It draws 12 marks round the edges, and two "hands" that rotate. It has two presets: "Watch" and "Circle." Note that the "Watch" preset is in CP colours!

PainterSillyscope

This Painter is supposed to imitate an oscilloscope. The line is drawn as a GraphicsPath. This is defined by the Shape and Points properties. If the Shape property is set to Line, then path.AddLines( points ) is called to create the path. This just results in straight lines connecting the points.

If the Shape property is set to Curve, then path.AddCurve( points, 1, points.Length - 3, tension / 100f ) is called to create the path. This results in curves connecting the middle points. The two end points are not drawn, they are just there to define the curvature at the start and end of the path. So this Shape requires at least four points.

If the Shape property is set to Bezier, then path.AddBeziers( points ) is called to create the path. This results in curves connecting the points. This Shape requires one point for the start position, and then three more points to describe each segment. So it must have 4, 7, 10, ... (1 + 3n) ... points.

Note that the "size" of the path described by the points array does not matter. The path is scaled according to the HorizontalScale and VerticalScale properties, which are percentages of the BusyBar control client area. Also, if the control is in Vertical mode, the path is rotated 90 degrees clockwise, so the points array must always be set horizontally.

There are a few presets in this Painter. "Triangle", "Square" and "Saw" are based on the Line shape; "Sine" is based on the Curve shape; and "Bezier" is based on the Bezier shape. The other two presets, "Circle" and "Heartbeat" are just for fun :)

PainterInstall

This Painter is a copy of the progress bar shown during an OS installation. I wrote it from memory; I think it's about right. It has two presets: "Install," which is the classic install bar, and "LED," which imitates a row of (blue) LEDs.

PainterFrustratoBar

This is a joke (I hope). It displays a bar that looks like the one in IE. It displays the log of the value, so it seems to go fast at first, and then slows down. Then it resets itself when it reaches a specified percentage, so it never gets to 100%. This was requested (honest)!

Writing Your Own Painter

All Painters must implement the IPainter interface :

    public interface IPainter : ICloneable
    {
        IPainter CreateCopy();
        BusyBar BusyBar { get; set; }
        void Reset();
        void Paint( Graphics g, Region r );
    }

You probably want to inherit from one of the existing classes. The derivation structure looks like this :

    IPainter
        PainterBase
            PainterLine
            PainterBlockBase
                PainterXP
                    PainterFrustratoBar
                PainterPathGradient
            PainterClock
            PainterSillyscope
            PainterInstall

If you derive from PainterBase, you just have to override the CreateCopy and Paint methods. PainterBase holds a reference to the BusyBar control, which it makes available through the protected Bar property. It also handles the ICloneable interface.

The Paint method is the most interesting. It is called from the OnPaint method of the BusyBar control, after it has drawn the border. The Graphics parameter enables you to do your drawing, and the Region parameter defines the client area available to you. The clip region of the Graphics object is already set to this Region.

The best way to understand this design is to have a look at the existing concrete Painter implementations.

Note: To test your Painter in the demo, alter the Painter property of Form1 to return an instance of your Painter (it's at the end of Form1.cs ). You will then be able to play with the settings from the PropertyGrids.

Have fun :)

Points of Interest

Getting the design-time bitmaps to work was a real pain. When Visual Studio embeds a resource, it prepends the default namespace to the name, and this is not configurable ( someone thought this was a good idea! ). Because BusyBar.cs is designed to be included in any project, the default namespace cannot be known. So I added a class called ResFinder in the default namespace, but you have to set the DefaultNamespace constant manually. I could not find a way around this :(

Bob Powell's website was a great help, in particular his article "How to find the elusive ToolboxBitmap icon" [^].

Conclusion

BusyBar provides a lot of functionality out of the box, but I hope that people will write more extensions, and email them to me to be included in updates of this article. Appropriate credit will of course be given.

I have had fun writing this, especially the "Kitt" and "Heartbeat" presets were quite gratifying. I hope you like what I have done :)

History

  • 19th May 2005 : Version 1
  • 26th May 2005 : Version 2
    • Fixed the threading bug in the demo.
    • Removed SCC links as requested.
    • Packaged in a strong-named library assembly as requested.
    • Added preset "Startup" to PainterXP.
    • Added preset "Startup2003" to PainterPathGradient.
    • Added PainterInstall with two presets.
  • 27th May 2005 : Version 3
    • Fixed bug when Minimum was not zero.
    • Added PainterFrustratoBar as requested.

License

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

Contact

If you have any feedback, please feel free to contact me.

Publicly recommend on Google: