Click here to Skip to main content
15,886,362 members
Articles / Multimedia / GDI+
Article

How to write a loading circle animation in .NET?

Rate me:
Please Sign up or sign in to vote.
4.92/5 (212 votes)
15 Feb 2007CPOL3 min read 761.1K   38.5K   480   173
A new kind of progress bar for .NET 2.0.

Sample Image - mrg_loadingcircle.jpg

Introduction

When it's time to wait, we are used to seeing the classic blue progress bar. It is everywhere in Windows and many other applications. However, animations are getting more and more popular.

For example, when Firefox loads a page, a small spinning circle appears and shows you that the page is loading. Moreover, Microsoft also uses this kind of animation in the Windows Media Center, Encarta 2006, SQL Server Management Studio Express, etc.

So, why don't we use this concept to show to our users that our application is working and/or loading? Let's begin by the presentation of the component I developed.

Points of interest

Rotation speed

The LoadingCircle uses a timer, and it has two responsibilities: to determine the color of the next spoke, and to redraw the circle at a specified number of milliseconds. When you use the property RotationSpeed, you modify the timer's property named Interval. Higher the value is, slower will be the rotation. The default rotation speed is 80, so at every 80 milliseconds, the circle will be redrawn.

How to draw a spoke?

First of all, we need coordinates for each spoke. We use the function DrawLine of GDI+, which needs two points, the beginning and the end of the line.

So, let's review some simple math notions. In order to draw a perfect circle, you have to know the following trigonometry concept: the cosines of an angle in degrees give us the X and the sine gives us the Y.

The method GetCoordinate computes, for a specified radius and angle, the coordinates of a point.

C#
private PointF GetCoordinate(PointF _objCircleCenter, 
               int _intRadius, double _dblAngle)
{
      PointF objPoint = new PointF();
      double dblAngle = Math.PI * _dblAngle / NUMBER_OF_DEGREES_HALF_CIRCLE;
      objPoint.X = _objCircleCenter.X + _intRadius * (float)Math.Cos(dblAngle);
      objPoint.Y = _objCircleCenter.Y + _intRadius * (float)Math.Sin(dblAngle);
      return objPoint;
}

The method DrawLine uses the coordinates computed by GetCoordinate, and draws a line with the two specified points and a color. Of course, we have to pass to this method the Graphics object of GDI+. As you can see, each line is rounded with the properties StartCap and EndCap of the Pen object.

C#
private void DrawLine(Graphics _objGraphics, PointF _objPointOne, 
                      PointF _objPointTwo, 
                      Color _objColor, int _intLineThickness)
{
      using(Pen objPen = new Pen(new SolidBrush(_objColor), _intLineThickness))
      {
            objPen.StartCap = LineCap.Round;
            objPen.EndCap = LineCap.Round;
            _objGraphics.DrawLine(objPen, _objPointOne, _objPointTwo);
      }
}

How to use this component?

This component is quite easy to use. Once added to your toolbox, drag it to your form, and you are in business! Now, all you have to do is to play with the properties which change the look of the circle. Those are under the category "LoadingCircle" in the Properties panel. Also, you can change the Active property to true or false to see the animation running or not.

The component has the following properties.

  • Color - The color of an inactive spoke.
  • OuterCircleRadius - This property is the radius of the outer circle. The value of this property is supposed to be higher than the InnerCircleRadius value.
  • InnerCircleRadius - This property is the radius of the inner circle.
  • NumberSpoke - This property is the number of spokes.
  • Active - Defines if the component is animated or not. So, if tour treatment is in progress, this property should be set to true, otherwise false.
  • SpokeThickness - Thickness of the line in pixel.
  • RotationSpeed - Animation speed. Lower is the value, faster is the animation.

Conclusion

Finally, I wish you will find this component useful. I had fun writing it, and wish you will use it. Thanks for reading this article.

Revision history

  • June 17, 2006 : Initial release.
  • February 05, 2007 : Better color management (alpha blending)
  • February 10, 2007 : Presets defined in the control (MacOS X, Internet Explorer 7 and FireFox). A new control has been added: LoadingCircleToolStripMenuItem. This control is compatible with the StatusStrip! Supports transparency. Some minors bug corrections.

License

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


Written By
Web Developer
Canada Canada
Looking for an online tool which allows you to sort the content of your text file, remove duplicate lines as well as empty lines. Go take a look at my new website: https://sortlines.com

Comments and Discussions

 
AnswerRe: memory leak? Pin
polishz311-Feb-14 1:04
polishz311-Feb-14 1:04 
GeneralPrinting animation Pin
UltraWhack8-May-08 3:51
UltraWhack8-May-08 3:51 
GeneralRe: Printing animation Pin
jm334-Jun-08 12:47
jm334-Jun-08 12:47 
GeneralRe: Printing animation Pin
UltraWhack13-Jun-08 5:06
UltraWhack13-Jun-08 5:06 
GeneralNot disposing all GDI+ resources: System.AccessViolationException Pin
bwaide22-Apr-08 19:52
bwaide22-Apr-08 19:52 
GeneralRe: Not disposing all GDI+ resources: System.AccessViolationException Pin
Martin Gagne23-Apr-08 3:42
Martin Gagne23-Apr-08 3:42 
GeneralRe: Not disposing all GDI+ resources: System.AccessViolationException Pin
Luc Pattyn27-Mar-10 21:18
sitebuilderLuc Pattyn27-Mar-10 21:18 
QuestionHow to put a Loading Circle in a datgridviewcolmun/cell ? Pin
platinum0730-Dec-07 23:08
platinum0730-Dec-07 23:08 
Hi Martin, hi developers,

first of all, I have to thank Martin for the great job he did. I usually use this feature in my applications, and it really rocks ! Wink | ;)

Now I'm trying to figure out how to implement a datagridviewCell/Column and a editing control to put the loading circle in the datagridview control.
I implemented three classes to do this, but the resulting behaviour is an empty unfashionned cell showing a white part and a piece of the form behind, but no loading circle at all...

could you please help find out why my cell behave that way ?

here is my code :


<br />
<br />
/////////////////////////////////LoadingCircleCell////////////////////////////////<br />
internal class LoadingCircleCell : DataGridViewCell<br />
    {<br />
        private Type validatingType;<br />
<br />
        public LoadingCircleCell() : base()<br />
        {<br />
            validatingType = typeof (LoadingCircle);<br />
        }<br />
<br />
        public override Type EditType<br />
        {<br />
            get { return typeof (LoadingCircleEditingControl); }<br />
        }<br />
<br />
        //  A Type object for the validating type.<br />
        public override Type ValueType<br />
        {<br />
            get { return typeof (LoadingCircle); }<br />
        }<br />
<br />
        public override object DefaultNewRowValue<br />
        {<br />
            get<br />
            {<br />
                // add a new loading circle<br />
                return new LoadingCircle();<br />
            }<br />
        }<br />
<br />
        public override void InitializeEditingControl(int rowIndex,<br />
                                                      object initialFormattedValue,<br />
                                                      DataGridViewCellStyle dataGridViewCellStyle)<br />
        {<br />
            base.InitializeEditingControl(rowIndex, initialFormattedValue,<br />
                                          dataGridViewCellStyle);<br />
<br />
            LoadingCircleEditingControl editingControl = DataGridView.EditingControl as LoadingCircleEditingControl;<br />
            if (editingControl != null)<br />
                editingControl.Active = (bool) Value;<br />
        }<br />
    }<br />
<br />
<br />
/////////////////////////////////LoadingCircleEditingControl////////////////////////////////<br />
public class LoadingCircleEditingControl: LoadingCircle, IDataGridViewEditingControl<br />
    {<br />
        protected int rowIndex;<br />
        protected DataGridView dataGridView;<br />
        protected bool valueChanged = false;<br />
<br />
        protected override void OnTextChanged(EventArgs e)<br />
        {<br />
            base.OnTextChanged(e);<br />
            // Let the DataGridView know about the value change<br />
            NotifyDataGridViewOfValueChange();<br />
        }<br />
<br />
        //  Notify DataGridView that the value has changed.<br />
        protected virtual void NotifyDataGridViewOfValueChange()<br />
        {<br />
            this.valueChanged = true;<br />
            if (this.dataGridView != null)<br />
            {<br />
                this.dataGridView.NotifyCurrentCellDirty(true);<br />
            }<br />
        }<br />
<br />
        #region IDataGridViewEditingControl Members<br />
<br />
        //  Indicates the cursor that should be shown when the user hovers their<br />
        //  mouse over this cell when the editing control is shown.<br />
        public Cursor EditingPanelCursor<br />
        {<br />
            get<br />
            {<br />
                return base.Cursor;<br />
            }<br />
        }<br />
<br />
<br />
        //  Returns or sets the parent DataGridView.<br />
        public DataGridView EditingControlDataGridView<br />
        {<br />
            get<br />
            {<br />
                return this.dataGridView;<br />
            }<br />
<br />
            set<br />
            {<br />
                this.dataGridView = value;<br />
            }<br />
        }<br />
<br />
<br />
        //  Sets/Gets the formatted value contents of this cell.<br />
        public object EditingControlFormattedValue<br />
        {<br />
            set<br />
            {<br />
                this.Active = (bool)value;<br />
                NotifyDataGridViewOfValueChange();<br />
            }<br />
            get <br />
            {<br />
                return this.Active;<br />
            }<br />
<br />
        }<br />
<br />
        //   Get the value of the editing control for formatting.<br />
        public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)<br />
        {<br />
            return EditingControlFormattedValue;<br />
        }<br />
<br />
        //  Process input key and determine if the key should be used for the editing control<br />
        //  or allowed to be processed by the grid. Handle cursor movement keys for the MaskedTextBox<br />
        //  control; otherwise if the DataGridView doesn't want the input key then let the editing control handle it.<br />
        public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)<br />
        {<br />
            switch (keyData & Keys.KeyCode)<br />
            {<br />
                case Keys.Right:<br />
                    <br />
                    break;<br />
<br />
                case Keys.Left:<br />
                    <br />
                    break;<br />
<br />
                case Keys.Home:<br />
                case Keys.End:<br />
                    <br />
<br />
                case Keys.Prior:<br />
                case Keys.Next:<br />
                    if (this.valueChanged)<br />
                    {<br />
                        return true;<br />
                    }<br />
                    break;<br />
<br />
                case Keys.Delete:<br />
                    <br />
                    break;<br />
            }<br />
<br />
            //<br />
            // defer to the DataGridView and see if it wants it.<br />
            //<br />
            return !dataGridViewWantsInputKey;<br />
        }<br />
<br />
<br />
        //  Prepare the editing control for edit.<br />
        public void PrepareEditingControlForEdit(bool selectAll)<br />
        {<br />
<br />
        }<br />
<br />
        //  Indicates whether or not the parent DataGridView control should<br />
        //  reposition the editing control every time value change is indicated.<br />
        //  There is no need to do this for the MaskedTextBox.<br />
        public bool RepositionEditingControlOnValueChange<br />
        {<br />
            get<br />
            {<br />
                return false;<br />
            }<br />
        }<br />
<br />
<br />
        //  Indicates the row index of this cell.  This is often -1 for the<br />
        //  template cell, but for other cells, might actually have a value<br />
        //  greater than or equal to zero.<br />
        public int EditingControlRowIndex<br />
        {<br />
            get<br />
            {<br />
                return this.rowIndex;<br />
            }<br />
<br />
            set<br />
            {<br />
                this.rowIndex = value;<br />
            }<br />
        }<br />
<br />
<br />
<br />
        //  Make the MaskedTextBox control match the style and colors of<br />
        //  the host DataGridView control and other editing controls <br />
        //  before showing the editing control.<br />
        public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)<br />
        {<br />
            this.Font = dataGridViewCellStyle.Font;<br />
            this.ForeColor = dataGridViewCellStyle.ForeColor;<br />
            this.BackColor = dataGridViewCellStyle.BackColor;<br />
            //this.TextAlign = translateAlignment(dataGridViewCellStyle.Alignment);<br />
        }<br />
<br />
<br />
        //  Gets or sets our flag indicating whether the value has changed.<br />
        public bool EditingControlValueChanged<br />
        {<br />
            get<br />
            {<br />
                return valueChanged;<br />
            }<br />
<br />
            set<br />
            {<br />
                this.valueChanged = value;<br />
            }<br />
        }<br />
    <br />
        #endregion // IDataGridViewEditingControl.<br />
	}<br />
<br />
<br />
/////////////////////////////////LoadingCircleColumn ////////////////////////////////<br />
public class LoadingCircleColumn : DataGridViewColumn<br />
    {<br />
        <br />
        //  Initializes a new instance of this class, making sure to pass<br />
        //  to its base constructor an instance of a MaskedTextBoxCell <br />
        //  class to use as the basic template.<br />
        public LoadingCircleColumn() : base(new LoadingCircleCell())<br />
        {<br />
        }<br />
<br />
        //  The template cell that will be used for this column by default,<br />
        //  unless a specific cell is set for a particular row.<br />
        //<br />
        //  A MaskedTextBoxCell cell which will serve as the template cell<br />
        //  for this column.<br />
        public override DataGridViewCell CellTemplate<br />
        {<br />
            get { return base.CellTemplate; }<br />
<br />
            set<br />
            {<br />
                //  Only cell types that derive from MaskedTextBoxCell are supported as the cell template.<br />
                if (value != null && !value.GetType().IsAssignableFrom(typeof (LoadingCircleCell)))<br />
                {<br />
                    string s = "Cell type is not based upon the LoadingCircleCell.";<br />
                    //CustomColumnMain.GetResourceManager().GetString("excNotMaskedTextBox");<br />
                    throw new InvalidCastException(s);<br />
                }<br />
<br />
                base.CellTemplate = value;<br />
            }<br />
        }<br />
    }<br />


These are, to my mind, as basic as they can be.

Thanks for you help.
AnswerRe: How to put a Loading Circle in a datgridviewcolmun/cell ? Pin
Martin Gagne31-Dec-07 3:03
Martin Gagne31-Dec-07 3:03 
GeneralRe: How to put a Loading Circle in a datgridviewcolmun/cell ? Pin
platinum071-Jan-08 8:28
platinum071-Jan-08 8:28 
GeneralRe: How to put a Loading Circle in a datgridviewcolmun/cell ? Pin
Patrick Etc.30-May-08 10:47
Patrick Etc.30-May-08 10:47 
GeneralWindows.Forms.TImer needs to be changed to System.Timers.Timer Pin
SalizarMarxx23-Dec-07 17:42
SalizarMarxx23-Dec-07 17:42 
QuestionHow to add this circle control to my toolbox component? Pin
CSharp200526-Nov-07 4:35
CSharp200526-Nov-07 4:35 
QuestionBug report Pin
cacerzard4-Sep-07 22:12
cacerzard4-Sep-07 22:12 
AnswerRe: Bug report Pin
Martin Gagne5-Sep-07 3:20
Martin Gagne5-Sep-07 3:20 
GeneralRe: Bug report Pin
Luc Pattyn27-Mar-10 21:21
sitebuilderLuc Pattyn27-Mar-10 21:21 
QuestionLoading Circle in statusStrip Pin
seang_sophorn19-Aug-07 17:03
seang_sophorn19-Aug-07 17:03 
QuestionHow can use this control in Visual Basic 6 Pin
sixtoja9-Aug-07 7:04
sixtoja9-Aug-07 7:04 
AnswerRe: How can use this control in Visual Basic 6 Pin
Martin Gagne9-Aug-07 7:46
Martin Gagne9-Aug-07 7:46 
GeneralRe: How can use this control in Visual Basic 6 Pin
sixtoja9-Aug-07 8:36
sixtoja9-Aug-07 8:36 
Generalanother animated progress bar Pin
OwfAdmin5-Aug-07 9:24
OwfAdmin5-Aug-07 9:24 
GeneralThreads and loading circle Pin
CostasAn17-Jul-07 22:17
CostasAn17-Jul-07 22:17 
GeneralRe: Threads and loading circle Pin
Martin Gagne18-Jul-07 4:06
Martin Gagne18-Jul-07 4:06 
GeneralRe: Threads and loading circle Pin
CostasAn18-Jul-07 20:34
CostasAn18-Jul-07 20:34 
GeneralRe: Threads and loading circle Pin
Martin Gagne19-Jul-07 5:45
Martin Gagne19-Jul-07 5: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.