# Circular Progress Indicator

By , 1 Jul 2010

## Introduction

I've always liked the little circular progress indicator on Firefox top right corner when loading a page but I couldn't find any like it to use with my projects, so I've made one myself.

## Using the Code

Using the control is very simple, there are only four properties and two methods that you need to be aware of:

• `CircleColor` - Changes the base color of the circles
• `CircleSize` - Changes the diameter of the circles, this value is relative and proportional to the control size and ranges between 0.1 and 1.0
• `AutoStart` - Sets if the animation should start automatically
• `AnimationSpeed` - Sets the animation speed
• `NumberOfCircles` - Sets the maximum number of circles that can be drawn in the control, this value must be greater than 0
• `NumberOfVisibleCircles` - Sets the number of circles that are visible in the control, must always be less or equal than the `NumberOfCircles`
• `Percentage` - Sets a percentage value that can be shown in the center of the control, this value must be between 0 and 100
• `ShowPercentage` - Sets a value that allows to show or hide the percentage value
• `ShowText` - Sets a value that allows to show the control text
• `TextDisplay` - This property is just a helper property, it allows to enable or disable both text and percentage with one property
• `Rotation` - Sets the direction in which the control will rotate

To start the animation, just call `Start()`:

`progressIndicator.Start(); `

And to stop it, just call `Stop()`:

`progresIndicator.Stop();`

### The Control Code

#### Drawing the Circles

The circles are drawn around a circumference with the diameter of the control. Since the number of circles is variable, we need to calculate the rotation angle for each circle. The minimum number of circles must be 1 with no upper limit control. Drawing the circles is done using a call to `RotateTransform` that rotates the drawing position to the angle passed as a parameter. Drawing the circles in sequence, decreasing the amount of alpha in the circle color gives the fading effect of the control. To animate, the control is redrawn at time intervals always changing the position of the darker circle by one place forward, giving the illusion of motion. The control can be rotated clockwise or counter-clockwise and direction of rotation is achieved by multiplying the angle by 1 or -1. Checking the code, this line is found:

`e.Graphics.RotateTransform(angle * _value * (int)_rotation);`

This piece of code sets the start position of each rotation phase multiplying the section angle by a value that is incremented by an internal timer. The maximum number of circles shown at a given time is defined by the `NumberOfVisibleCircles` property. This property determines the effective number of circles that are drawn in the control, this is different from the `NumberOfCircles` property. This last property sets the number of circles that can be drawn, is like reserving spaces for the circles even if we don't use them all. This gives the effect of tightening the circles together when the number of visible circles decreases and the opposite effect when it increases.

```float angle = 360.0F / _numberOfCircles;

GraphicsState oldState = e.Graphics.Save();

e.Graphics.TranslateTransform(Width / 2.0F, Height / 2.0F);
e.Graphics.RotateTransform(angle * _value * (int)_rotation);
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

for (int i = 1; i <= _numberOfCircles; i++)
{
int alphaValue = (255.0F * (i / (float)_numberOfVisibleCircles)) > 255.0 ? 0 :
(int)(255.0F * (i / (float)_numberOfVisibleCircles));
int alpha = _stopped ? (int)(255.0F * (1.0F / 8.0F)) : alphaValue;

Color drawColor = Color.FromArgb(alpha, _circleColor);

using (SolidBrush brush = new SolidBrush(drawColor))
{
float sizeRate = 4.5F / _circleSize;
float size = Width / sizeRate;

float diff = (Width / 4.5F) - size;

float x = (Width / 9.0F) + diff;
float y = (Height / 9.0F) + diff;
e.Graphics.FillEllipse(brush, x, y, size, size);
e.Graphics.RotateTransform(angle * (int)_rotation);
}
}

e.Graphics.Restore(oldState);```

#### Drawing the Percentage and the Text

The percentage is an optional feature, since the primary purpose isn't to show a progress value but that something is going on. The percentage text isn't shown by default and wasn't even part of the original idea. Anyway I have been reading some comments from people requesting new features and giving some nice ideas that I've decided to update the control and implement some new features. One of them was this one. The percentage is set through the `Percentage` property and can be enabled and disabled from the `ShowPercentage` property. The `Text` property follows the same reasoning. It wasn't part of the original plan, but as SohjSolwin suggested, it can be useful in some cases, we never know when or where this control will be used. So, from this version on, the control has the ability of drawing both of them like shown below:

```string percent = GetDrawText();

if (!string.IsNullOrEmpty(percent))
{
SizeF textSize = e.Graphics.MeasureString(percent, Font);
float textX = (Width / 2.0F) - (textSize.Width / 2.0F);
float textY = (Height / 2.0F) - (textSize.Height / 2.0F);
StringFormat format = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};

RectangleF rectangle = new RectangleF(textX, textY, textSize.Width, textSize.Height);

using (SolidBrush textBrush = new SolidBrush(ForeColor))
{
e.Graphics.DrawString(percent, Font, textBrush, rectangle, format);
}
}```

#### Why both Percentage and the Text?

When the control is being used to show progress, it is possible to show a message about what is going on and at the same time the progress percentage. Both of them are independent and have different purposes. Even if they are drawn at the same time internally, if I just need to change the value of the percentage, it's easier to do this:

`progressIndicator.Percentage = 50.25F;`

And let the control add the "`%`" sign and do the rest of the formatting. It's much more intuitive and simple than this:

`progressIndicator.Text = string.Format("{0:0:##} %}, 50.25F);`

On the other hand, the percentage property doesn't allow text, so it's needs another field to set the text to draw. The final string that is displayed is built using the `Text` and the `Percentage` property in the method displayed below, the method checks the values of the `_showText` and `_showPercentage` fields and constructs a `string `based on their values, returning an empty `string `if both fields are set to `false`.

```private string GetDrawText()
{
string percent = string.Format(CultureInfo.CurrentCulture, "{0:0.##} %", _percentage);

if (_showText && _showPercentage)
return string.Format("{0}{1}{2}", percent, Environment.NewLine, Text);

if (_showText)
return Text;

if (_showPercentage)
return percent;

return string.Empty;
}```

The rest of the control code is short and simple to understand, so I've decided to remove it from the article leaving only the pieces that matter. If you have any questions, please leave a comment.

## Points of Interest

There's nothing more to it. It is particularly useful in situations where the progress bar hasn't made any progress for a long time and we want to pass the message that something is being done. Mainly, it has been fun to program. :)

## Credits

I would like to thank SohjSolwin for his contribution with the rotation and number of visible circles part of the code that I've included in this release with minor modifications, and also for the text drawing suggestion instead of only showing the percentage.

## History

• 2nd November, 2008: Initial post
• 26th June, 2010: Update to version 1.1, new features are:
• Variable number of circles (version 1 has a fixed number of 8)
• It is possible to draw a percentage value
• 1st July, 2010: Update to version 1.2, new features are:
• Rotation can be clockwise or counter-clockwise
• It is possible to draw a text string in addition to the percentage value
• Variable number of visible circles (version 1.1 has a fixed number of 8)

 Nitoc3 Software Developer Portugal Member
No Biography provided

Votes of 3 or less require a comment

 Search this forum Profile popups    Spacing RelaxedCompactTight   Noise Very HighHighMediumLowVery Low   Layout Open AllThread ViewNo JavascriptPreview   Per page 102550
 First PrevNext
 My vote of 4 Prasyee 5 Mar '13 - 17:33
 My vote of 5 IFFI 11 Dec '12 - 4:55
 Thank you! Ignacioooooo 21 Nov '12 - 7:39
 Problem while launching external app alex__b 6 Aug '12 - 19:15
 Hi I try to use this progress indicator while waiting for an external app. ```this.progressIndicator1.Start(); Activator.CreateInstance(...);//launch external app this.progressIndicator1.Stop(); ``` and the indicator does not start (in fact it waits for the CreateInstance() call to return and then starts.) Any solutions? Thanks alex alex   'Architecture is music frozen in space.' Sign In·View Thread·Permalink
 Excellent. PHS241 23 Apr '12 - 22:57
 My vote of 5 Vano Maisuradze 26 Mar '12 - 0:49
 Permission xivSolutions 19 Feb '12 - 13:00
 Re: Permission Nitoc3 20 Feb '12 - 6:39
 Nice! xivSolutions 31 Jan '12 - 16:40
 Its awesome. Thanks. pkkalra1978 26 Sep '11 - 21:16
 Like !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! raananv 31 Aug '11 - 12:48
 My vote of 5 TheF®eeSurfer 6 Apr '11 - 0:34
 Another new vote apiegoku 22 Sep '10 - 11:08
 My vote of 5 VUnreal 21 Sep '10 - 11:00
 Very usefull progress indicator redhatee2006 11 Aug '10 - 5:44
 Thanks for this progress indicator, excelent work Question: cannot make backcolor transparent, options transparent is on, but color returns color of backcolor main form. Sign In·View Thread·Permalink
 Re: Very usefull progress indicator Nitoc3 17 Aug '10 - 7:11
 Hi redhatee2006, that is the behaviour that is supposed to have. From the MSDN documentation:   "Windows Forms controls do not support true transparency. The background of a transparent Windows Forms control is painted by its parent."   How to: Give Your Control a Transparent Background[^] Sign In·View Thread·Permalink
 My vote of 5 kobymeir 17 Jul '10 - 4:40
 My vote of 5 Huseyin OZCAKIR 9 Jul '10 - 23:14
 My vote of 5 garycoggins 8 Jul '10 - 4:12
 use of control Member 3471983 30 Jun '10 - 8:48
 SohjSolwin,   You have made a very nice control. Is there a way to use it with a backgroundworker because when i use it while filling a datagridview is stops working until the grid is filled. Sign In·View Thread·Permalink
 Re: use of control SohjSolwin 1 Jul '10 - 11:12
 A few updates and request for permission SohjSolwin 29 Jun '10 - 11:17
 I've made a few enhancements to your control (which I'm submitting back to the community here) and would like to ask your permission to look at adding the control to a project I'm working with, fyireporting[^]. The rotation is now able to go either clockwise or counter-clockwise, the number of visible circles is adjustable, in addition to the already adjustable number of circles, and any text can be placed over the control instead of just the percentage complete (this way the current action can be drawn over the control too).   ``` /// /// An enum used to indicate the rotational direction of the control. /// public enum RotationType { /// /// Indicates that the rotation should move clockwise. /// Clockwise = 1, /// /// Indicates that the rotation should move counter-clockwise. /// Counter_Clockwise = -1, } ```   Added with the Private Fields (The percentage fields were removed and replaced with the text fields). ``` private RotationType _rotation = RotationType.Clockwise; private int _numberOfVisibleCircles = 8; private string _text; private bool _showText; ```   Added to Public Properties ``` /// /// Gets or sets the number of visible circles used in the animation. /// /// NumberOfVisibleCircles is out of range. [DefaultValue(8)] [Description("Gets or sets the number of visible circles used in the animation.")] [Category("Behavior")] public int NumberOfVisibleCircles { get { return _numberOfVisibleCircles; } set { if (value <= 0 || value > _numberOfCircles) throw new ArgumentOutOfRangeException("value", "Number of visible circles must be a positive integer and less than or equal to the number of circles.");   _numberOfVisibleCircles = value; Invalidate(); } }   /// /// Gets or sets the text to show on the control. /// [DefaultValue("")] [Description("Gets or sets the text to show on the control.")] [Category("Appearance")] public override string Text { get { return _text; } set { _text = value; } }   /// /// Gets or sets a value indicating if the percentage value should be shown. /// [DefaultValue(false)] [Description("Gets or sets a value indicating if the text value should be shown.")] [Category("Behavior")] public bool ShowText { get { return _showText; } set { _showText = value; Invalidate(); } }   /// /// Gets or sets a value indicating if the rotation should be clockwise or counter-clockwise. /// [DefaultValue(RotationType.Clockwise)] [Description("Gets or sets a value indicating if the rotation should be clockwise or counter-clockwise.")] [Category("Behavior")] public RotationType Rotation { get { return _rotation; } set { _rotation = value; } } ```   The code within the e.Graphics.Save/Restore calls in the OnPaint method ``` e.Graphics.TranslateTransform(Width / 2.0F, Height / 2.0F); e.Graphics.RotateTransform(angle * _value * (int)_rotation); e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;   for (int i = 1; i <= _numberOfCircles; i++) { int alphaValue = (255.0F * (i / (float)_numberOfVisibleCircles)) > 255.0 ? 0 : (int)(255.0F * (i / (float)_numberOfVisibleCircles)); int alpha = _stopped ? (int)(255.0F * (1.0F / (float)_numberOfVisibleCircles)) : alphaValue;   Color drawColor = Color.FromArgb(alpha, _circleColor);   using (SolidBrush brush = new SolidBrush(drawColor)) { float sizeRate = 4.5F / _circleSize; float size = Width / sizeRate;   float diff = (Width / 4.5F) - size;   float x = (Width / 9.0F) + diff; float y = (Height / 9.0F) + diff; e.Graphics.FillEllipse(brush, x, y, size, size); e.Graphics.RotateTransform(angle * (int)_rotation); } } ``` Sign In·View Thread·Permalink
 Re: A few updates and request for permission Nitoc3 30 Jun '10 - 1:13
 Re: A few updates and request for permission SohjSolwin 30 Jun '10 - 7:52
 No problem, feel free. And thanks. I'll be sure to abide by the CPOL, I just feel more comfortable checking with the author before I use it too. I'm going to play with the control a little more and see if I can get it to rotate in both directions at once as well (mainly because I want to see what it would look like) and If I get it to work I'll post the changes for that as well. Sign In·View Thread·Permalink
 Re: A few updates and request for permission Nitoc3 30 Jun '10 - 8:28
 I think the rotation can be done in both directions if you draw it in one direction first and then in the other since you have to rotate transform the Graphics object. It seems to me the easyest way. Draw it clockwise, restore the graphics and then draw it counterclockwise. It could work that way. Try it. Sign In·View Thread·Permalink
 Last Visit: 31 Dec '99 - 18:00     Last Update: 25 May '13 - 11:55 Refresh 123 Next »