Click here to Skip to main content
12,753,418 members (37,106 online)
Click here to Skip to main content
Add your own
alternative version


51 bookmarked
Posted 10 Apr 2013

Circular Indeterminate Progress Indicator Control

, 10 Apr 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
This article introduces the reader to the steps taken to create a user control that displays a circular indeterminate progress control.
Desired Control


This short paper introduces its readers to the steps taken to create a user control that displays a circular indeterminate progress control.

Table of Contents

The symbol [^] returns to the Table of Contents.


In preparing a tool to help me organize Visual Studio solutions (.sln), projects (.csproj, .vbproj, etc.), and source files on my computer and innumerable USB drives, I found I needed a simple circular indeterminate progress indicator. I had written a progress indicator with an annulus and a rotating indicator, but the tool that I was building required something simpler.

We have all seen that ring of colored circles, rotating around a central point, indicating that an unseen, and usually long running, process is executing in the background. It is simple and met my needs. This article is a discussion of my implementation of the control.

Control Components [^]

Control Components

The ring of colored circles (hereafter called "indicator circles") can be viewed as a group of circles that travel along an imaginary circle that is centered within the control's boundaries.

In the diagram, the control's boundary is drawn in black and the imaginary circle upon which the indicator circles travel is drawn in dashed red.

Three values are needed before the control may be drawn:

  1. The width of the control. Note that the control's width and height are equal.
  2. The diameter of the indicator circles.
  3. The angle subtended by an indicator circle from the center of the control.

What may not be readily apparent is that the control, with the exception of its appearance properties (e.g., color, number of indicator circles, rotation rate, etc.), is completely defined by the Control Width/Height and the Indicator Diameter. Later sections will discuss how these two properties are used.

The Two Radii [^]

The Two Radii

The value R is the distance from the center of the control to the center of an indicator circle. The value r is the radius of an indicator circle. These values are the same for all indicator circles.

The observant reader might notice that, given R and r, we can use simple trigonometry to solve for the remaining value.

Linear Components [^]

Linear Components

Two linear components are used to define the control.

r = id / 2.0
R = cwh / 2.0 - r

r is the radius of the indicator circles (simply the Indicator Diameter (shown as id in the diagram) divided by two). Once we have r, we can compute R as the difference after subtracting r from the Control Width/Height (shown as cwh in the diagram) divided by two.

This holds because the outer edges of the indicator circles may not extent beyond the Control Width/Height. Note too that it is easier to compute r before computing R.

Angular Components [^]

Angular Components

One angular component positions the initial indicator circle within the control; a second angular component is used to position the remaining indicator circles.

theta (shown as θ in the diagram) is the angle subtended by an indicator circle from the center of the control. phi (shown as φ in the diagram) is one-half theta and is the angle from the center of an indicator circle to the bottom of the indicator circle. It is easier to compute phi before computing theta.

phi = arctan ( r / R )
theta = 2.0 * phi

phi positions the first indicator circle; theta positions the remaining indicator circles.

Implementation [^]

Properties [^]

A number of properties make the control more useful for programming in the wild. The control's properties are enumerated in the following table.

PropertyDescriptionProperty TypeDefaultMin/Max
AnimateStart/stops indicator animationBooleantrue 
BackgroundColorControl background colorSystemColorsControl 
CirclesCountNumber of indicator circlesInteger55/10
ControlWidthHeightWidth and height of controlPixels3020/400
IndicatorColorColor of first rotating indicatorColorBlack 
IndicatorDiameterDiameter of the indicator circlesPixels84/100
IndicatorTypeSpecifies if control is animated or pulsedEnumerationANIMATED 
RefreshRateInterval between indicator movementsMilliseconds10050/300

Methods [^]

There is only one method, Pulse that may be invoked in the control. This method is used to cause the indicator circles to move one position clockwise. It is activated when the IndicatorType property is set to PULSED. Once that property is so set, the rotation of the indicator circles stops until either the Pulse method is invoked or the IndicatorType property is set to ANIMATED.

Revise Control Geometry [^]

Modifying certain of the control's properties require that the control be redrawn. When the control's CirclesCount, ControlWidthHeight, or IndicatorDiameter properties change, the control must be redrawn. Prior to redrawing the control, the four values that influence the control's appearance must be recalculated. This recalculation is performed in the update_indicator_geometry method.

// ********************************* update_indicator_geometry

/// <summary>
/// phi is one-half the angle subtended by one indicator
/// circle as measured from the center of the control; phi is
/// dependent upon the control and indicator diameters; theta
/// is two times phi and is the angular shift from center to
/// center of two adjacent indicator circles
/// the centers of the indicator circle are at
///     ( R, phi + i * theta ) | i = 0, n;
///                              n = number of circles
/// invoke this method whenever the circles count, control
//  width/height, or the indicator diameter are changed
/// </summary>
/// <remarks>
/// note that phi is negative because when drawing the
/// indicator circles, we are move counterclockwise; likewise
/// because the indicator moves clockwise, we must flip the
/// sign of theta
/// </remarks>
void update_indicator_geometry ( )

    r = ( float ) IndicatorDiameter / 2.0F;
    R = ( ( float ) ControlWidthHeight / 2.0F ) - r;
    phi = -( float ) Math.Atan2 ( ( double ) r,
                                  ( double ) R );
    theta = 2.0F * phi;
    indicator_angular_advance = -theta;

After invoking update_indicator_geometry, the control can be redrawn.

Redraw Control [^]

As with all Windows Forms, we override the OnPaint event in order to take control of the redrawing process.

// ****************************************** OnPaint override

/// <summary>
/// take over the event handling for the control's OnPaint
/// event
/// </summary>
/// <param name="e">
/// The PaintEventArgs class contains data for the Paint
/// event; of particular interest here is e.Graphics that has
/// methods to draw points, strings, lines, arcs, ellipses,
/// and other shapes
/// </param>
protected override void OnPaint ( PaintEventArgs e )

    base.OnPaint ( e );

    if ( control_graphic == null )
        create_control_graphic ( );
    control_graphic.RenderGraphicsBuffer ( e.Graphics );

    create_indicator_graphic ( );
    indicator_graphic.RenderGraphicsBuffer ( e.Graphics );
                                // revise rotation angle and
                                // avoid overflow
    indicator_angle += indicator_angular_advance;
    if ( indicator_angle > ( float ) ( 2.0 * Math.PI ) )
        indicator_angle -= ( float ) ( 2.0 * Math.PI );

As seen from this source code, the control is made up of two graphic objects: a control graphic and an indicator graphic. The control graphic is simply the control background; the indicator graphic is the indicator circles.

// ************************************ create_control_graphic

/// <summary>
/// deletes any existing control graphic and then creates a
/// new one
/// </summary>
void create_control_graphic ( )
    Rectangle   bounding_rectangle;

    if ( control_graphic != null )
        control_graphic =
            control_graphic.DeleteGraphicsBuffer ( );

    control_graphic = new GraphicsBuffer ( );

    if ( control_graphic.CreateGraphicsBuffer (
                    this.CreateGraphics ( ),
                    ControlWidthHeight ) )
        control_graphic.g.SmoothingMode =
        bounding_rectangle = this.ClientRectangle;
        bounding_rectangle.Inflate ( 1, 1 );
        control_graphic.g.FillRectangle (
            new SolidBrush ( BackgroundColor ),
            bounding_rectangle );
        bounding_rectangle.Inflate ( -1, -1 );

The indicator graphic is somewhat more complex. It draws the number of indicator circles specified by CirclesCount. It colors the first indicator circle with the color specified by IndicatorColor and thereafter it "lightens" each following indicator circle.

// ********************************** create_indicator_graphic

/// <summary>
/// this method creates a new indicator graphic that is the
/// size of the control graphic; it rotates clockwise around
/// the center of the control graphic; the indicator graphic
/// initially has its leading edge at the x-axis; any existing
/// indicator graphic will be deleted
/// </summary>
void create_indicator_graphic ( )
                                // effectively erases the
                                // background
    if ( control_graphic == null )
        create_control_graphic ( );

    if ( indicator_graphic != null )
        indicator_graphic =
            indicator_graphic.DeleteGraphicsBuffer ( );

    indicator_graphic = new GraphicsBuffer ( );

    update_indicator_geometry ( );

    if ( indicator_graphic.CreateGraphicsBuffer (
                    this.CreateGraphics ( ),
                    ControlWidthHeight ) )
        Color       color = IndicatorColor;
        Graphics    graphics = indicator_graphic.g;
        Rectangle   indicator_bounding_rectangle;
        Size        size = new Size ( ( int ) ( 2.0F * r ),
                                      ( int ) ( 2.0F * r ) );

        indicator_graphic.g.SmoothingMode =
        indicator_bounding_rectangle = this.ClientRectangle;
        indicator_graphic.g.FillRectangle (
            new SolidBrush ( Color.Transparent ),
            indicator_bounding_rectangle );
        for ( int i = 0; ( i  < CirclesCount ); i++ )
            float       angle;
            Rectangle   bounding_rectangle;
            Brush       brush = new SolidBrush ( color );
            Point       top_left = new Point ( );
            int         x;
            int         y;

            angle = ( phi + ( float ) i * theta ) +
            polar_to_cartesian (     R,
                                 out x,
                                 out y );
            top_left.X = ( int ) ( ( float ) x - r ) +
                         this.Width / 2;
            top_left.Y = ( int ) ( ( float ) y - r ) +
                         this.Height / 2;

            bounding_rectangle = new Rectangle ( top_left,
                                                 size );
            graphics.FillEllipse ( brush,
                                   bounding_rectangle );

            brush.Dispose ( );

            color = lighter_color ( color, 0.25F );

The two helper functions polar_to_cartesian and lighter_color are:

// **************************************** polar_to_cartesian


public void polar_to_cartesian (     float  radius,
                                     float  theta,  // radians
                                 out int    x,
                                 out int    y )
    double  r = ( double ) radius;
    double  t = ( double ) theta;

    x = ( int ) ( r * Math.Cos ( t ) );
    y = ( int ) ( r * Math.Sin ( t ) );

// ********************************************* lighter_color

//     c-create-a-lighter-darker-color-based-on-a-system-color

Color lighter_color ( Color  color,
                      float  factor )
    Color  new_color = Color.Black;

        float red = ( 255 - color.R ) * factor + color.R;
        float green = ( 255 - color.G ) * factor + color.G;
        float blue = ( 255 - color.B ) * factor + color.B;

        new_color = Color.FromArgb ( color.A,
                                     ( int ) red,
                                     ( int ) green,
                                     ( int ) blue );
    catch ( Exception ex )
        new_color = Color.Black;

    return ( new_color );

Animate or Pulse [^]

The tool that I am building performs a search for all .sln files that are beneath a topmost directory. The tool has no way of knowing how long the search will take. So I plan to set the control's IndicatorType property to PULSED and, upon finding an .sln file, invoke the control's Pulse method.

Conclusion [^]

This brief article has shown how a simple control can be implemented.

References [^]

History [^]

04/10/2013   Original Article


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


About the Author

Software Developer (Senior)
United States United States
I started programming more than 42 years ago using AutoCoder and RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround between submitting a job for compilation and execution was about 3 hours. So much for the "good old days!" Today, I particularly enjoy programming real-time software. I consider myself capable in WinForms, Mobile Apps, and C# although there are occasions that I yearn to return to C and the Win32 API.

You may also be interested in...


Comments and Discussions

QuestionMessage Automatically Removed Pin
27-Jan-16 21:45
groupyyyyyyyya27-Jan-16 21:45 
QuestionDraw the hours by drawing a arc. Pin
xNetDeveloper16-Nov-14 11:13
memberxNetDeveloper16-Nov-14 11:13 
AnswerRe: Draw the hours by drawing a arc. Pin
gggustafson16-Nov-14 12:34
professionalgggustafson16-Nov-14 12:34 
QuestionThis explanation is very good, made by a true master Pin
powertam16-Oct-13 12:10
memberpowertam16-Oct-13 12:10 
AnswerRe: This explanation is very good, made by a true master Pin
gggustafson16-Oct-13 12:54
professionalgggustafson16-Oct-13 12:54 
GeneralGood article and rock solid explanation of the algorithms Pin
Manfred R. Bihy30-Sep-13 12:55
professionalManfred R. Bihy30-Sep-13 12:55 
GeneralRe: Good article and rock solid explanation of the algorithms Pin
gggustafson30-Sep-13 13:24
membergggustafson30-Sep-13 13:24 
GeneralRe: Good article and rock solid explanation of the algorithms Pin
Manfred R. Bihy30-Sep-13 13:38
professionalManfred R. Bihy30-Sep-13 13:38 
GeneralRe: Good article and rock solid explanation of the algorithms Pin
gggustafson30-Sep-13 14:46
membergggustafson30-Sep-13 14:46 
GeneralMy vote of 5 Pin
roscler15-Apr-13 7:00
memberroscler15-Apr-13 7:00 
GeneralRe: My vote of 5 Pin
gggustafson15-Apr-13 7:17
membergggustafson15-Apr-13 7:17 
GeneralMy vote of 5 Pin
Mike Meinz15-Apr-13 6:27
memberMike Meinz15-Apr-13 6:27 
GeneralRe: My vote of 5 Pin
gggustafson15-Apr-13 7:16
membergggustafson15-Apr-13 7:16 
GeneralMy vote of 5 Pin
Prasad Khandekar10-Apr-13 22:54
memberPrasad Khandekar10-Apr-13 22:54 
GeneralRe: My vote of 5 Pin
gggustafson11-Apr-13 3:45
membergggustafson11-Apr-13 3:45 

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

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

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170217.1 | Last Updated 10 Apr 2013
Article Copyright 2013 by gggustafson
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid