Click here to Skip to main content
11,412,526 members (70,059 online)
Click here to Skip to main content

Creating a 'Progress Cursor'

, 2 Jul 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Utility to display a circular progressbar as cursor.



This article explains how we can customize the cursor to display a circular progress bar.

Because I often get questions about extending functionality of this utility, it has now entered the world of OSS at github. You can fork the repo here.  

Class diagram


Using the code

Using the code is pretty simple, as you can see in 1-1.

var progressCursor = Van.Parys.Windows.Forms.CursorHelper.StartProgressCursor(100);

for (int i = 0; i < 100; i++)

 //do some work

1-1 Basic usage of ProgressCursor

The library also has some points of extensibility, by handling the 'EventHandler<CursorPaintEventArgs> CustomDrawCursor' event. By handling this event, the developer can choose to extend the default behaviour by running the DrawDefault method on the CursorPaintEventArgs instance (1-2).

progressCursor.CustomDrawCursor += progressCursor_CustomDrawCursor;

void progressCursor_CustomDrawCursor(object sender, 
                    ProgressCursor.CursorPaintEventArgs e)
	//add text to the default drawn cursor
	           SystemFonts.DefaultFont, Brushes.Black, 0,0);
	//set Handled to true, or else nothing will happen,
	//and default painting is done
	e.Handled = true;
1-2 ProgressCursor extension using events

IProgressCursor also implements IDisposable, which makes the 'using' statement valid on this interface. The advantage is that no custom exception handling has to be done to ensure the End() method is called on the ProgressCursor. An example of the usage is found in 1-3.

using (var progressCursor = CursorHelper.StartProgressCursor(100))
    for (int i = 0; i < 100; i++)

        //simulate some work
1-3 ProgressCursor implements IDisposable

Why implement IDisposable 

A classic usage of the default cursor classes would be like this:

private void DoStuff()
    Cursor.Current = Cursors.WaitCursor;

        //do heavy duty stuff here...
        Cursor.Current = Cursors.Default;

If one wouldn't implement the cursor change like this, the cursor could 'hang' and stay 'WaitCursor'. To avoid this Try Finally coding style, I implemented IDisposable on the IProgressCursor like this (2-2):

public ProgressCursor(Cursor originalCursor)
    OriginalCursor = originalCursor;


public void Dispose()

public void End()
    Cursor.Current = OriginalCursor;
2-2 Classic sample of Cursor usage

How it works

Creating a custom cursor 

Basically, all the 'heavy lifting' is done by two imported user32.dll methods (1-3). These can be found in the class UnManagedMethodWrapper (what would be the right name for this class?).

public sealed class UnManagedMethodWrapper
	public static extern IntPtr CreateIconIndirect(ref IconInfo iconInfo);

	[return: MarshalAs(UnmanagedType.Bool)]
	public static extern bool GetIconInfo(IntPtr iconHandle, ref IconInfo iconInfo);
1-3 P/Invoke methods

These methods are called in CreateCursor (1-4):

private Cursor CreateCursor(Bitmap bmp, Point hotSpot)
	//gets the 'icon-handle' of the bitmap
	//( equivalent of bmp as Icon)
	IntPtr iconHandle = bmp.GetHicon();
	IconInfo iconInfo = new IconInfo();
	//fill the IconInfo structure with data from the iconHandle
	UnManagedMethodWrapper.GetIconInfo(iconHandle, ref iconInfo);
	//set hotspot coordinates
	iconInfo.xHotspot = hotSpot.X;
	iconInfo.yHotspot = hotSpot.Y;
	//indicate that this is a cursor, not an icon
	iconInfo.fIcon = false;
	//actually create the cursor
	iconHandle = 
	  UnManagedMethodWrapper.CreateIconIndirect(ref iconInfo);
	//return managed Cursor object
	return new Cursor(iconHandle);
1-4 Cursor magic!

MSDN documentation:

Circular progress cursor drawing

int fontEmSize = 7;

var totalWidth = (int) Graphics.VisibleClipBounds.Width;
var totalHeight = (int) Graphics.VisibleClipBounds.Height;
int margin_all = 2;
var band_width = (int) (totalWidth*0.1887);

int workspaceWidth = totalWidth - (margin_all*2);
int workspaceHeight = totalHeight - (margin_all*2);
var workspaceSize = new Size(workspaceWidth, workspaceHeight);

var upperLeftWorkspacePoint = new Point(margin_all, margin_all);
var upperLeftInnerEllipsePoint = new Point(upperLeftWorkspacePoint.X + band_width, 
                                 upperLeftWorkspacePoint.Y + band_width);

var innerEllipseSize = new Size(((totalWidth/2) - upperLeftInnerEllipsePoint.X)*2, 
            ((totalWidth/2) - upperLeftInnerEllipsePoint.Y)*2);

var outerEllipseRectangle = 
    new Rectangle(upperLeftWorkspacePoint, workspaceSize);
var innerEllipseRectangle = 
    new Rectangle(upperLeftInnerEllipsePoint, innerEllipseSize);

double valueMaxRatio = (Value/Max);
var sweepAngle = (int) (valueMaxRatio*360);

var defaultFont = new Font(SystemFonts.DefaultFont.FontFamily, 
                           fontEmSize, FontStyle.Regular);
string format = string.Format("{0:00}", (int) (valueMaxRatio*100));
SizeF measureString = Graphics.MeasureString(format, defaultFont);
var textPoint = new PointF(upperLeftInnerEllipsePoint.X + 
  ((innerEllipseSize.Width - measureString.Width)/2), 
    upperLeftInnerEllipsePoint.Y + 
    ((innerEllipseSize.Height - measureString.Height)/2));


Graphics.DrawEllipse(BorderPen, outerEllipseRectangle);
Graphics.FillPie(FillPen, outerEllipseRectangle, 0, sweepAngle);

Graphics.FillEllipse(new SolidBrush(Color.White), innerEllipseRectangle);
Graphics.DrawEllipse(BorderPen, innerEllipseRectangle);

Graphics.DrawString(format, defaultFont, FillPen, textPoint); 

What does it (try to) solve

End users tend to have the impression to be waiting longer on a process with no progress visualization, then a process with progress indication. 


  • 2011-08-30: Initial version.


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


About the Author

Pieter Van Parys
Software Developer
Belgium Belgium
LinkedIn Profile

I maintain a blog at
Follow on   Twitter

Comments and Discussions

GeneralMy vote of 5 Pin
Michael Grünwaldt at 12-Jul-12 0:48
memberMichael Grünwaldt12-Jul-12 0:48 
GeneralGDI objects aren't managed and need to be disposed Pin
jeffb42 at 3-Jul-12 18:31
memberjeffb423-Jul-12 18:31 
GeneralRe: GDI objects aren't managed and need to be disposed Pin
Pieter Van Parys at 3-Jul-12 20:49
memberPieter Van Parys3-Jul-12 20:49 
GeneralRe: GDI objects aren't managed and need to be disposed Pin
jeffb42 at 4-Jul-12 11:34
memberjeffb424-Jul-12 11:34 
Questionnice Pin
CIDev at 2-Jul-12 4:31
memberCIDev2-Jul-12 4:31 
GeneralMy vote of 5 Pin
Md. Marufuzzaman at 2-Jul-12 3:41
mentorMd. Marufuzzaman2-Jul-12 3:41 
QuestionVote of 5 Pin
GanesanSenthilvel at 2-Jul-12 1:37
memberGanesanSenthilvel2-Jul-12 1:37 
GeneralMy vote of 5 Pin
manoj kumar choubey at 20-Feb-12 22:36
membermanoj kumar choubey20-Feb-12 22:36 
QuestionWin32 handle passed to Cursor is not valid or is the wrong type. Pin
Member 4078958 at 9-Dec-11 6:05
memberMember 40789589-Dec-11 6:05 
AnswerRe: Win32 handle passed to Cursor is not valid or is the wrong type. Pin
sapatag at 5-Jul-12 3:15
membersapatag5-Jul-12 3:15 
GeneralGreat idea! Pin
danlobo at 13-Oct-11 9:16
memberdanlobo13-Oct-11 9:16 
Questionvery nice Pin
CIDev at 11-Oct-11 7:06
memberCIDev11-Oct-11 7:06 
QuestionNice One Pin
Gandalf - The White at 10-Oct-11 3:15
memberGandalf - The White10-Oct-11 3:15 
GeneralMy vote of 5 Pin
Oshtri Deka at 9-Oct-11 1:28
memberOshtri Deka9-Oct-11 1:28 
GeneralRe: My vote of 5 Pin
Pieter Van Parys at 12-Oct-11 6:08
memberPieter Van Parys12-Oct-11 6:08 
QuestionVery good Pin
marc ochsenmeier at 19-Sep-11 10:35
membermarc ochsenmeier19-Sep-11 10:35 
AnswerRe: Very good Pin
Pieter Van Parys at 20-Sep-11 1:23
memberPieter Van Parys20-Sep-11 1:23 
QuestionLove It! Pin
NickPace at 8-Sep-11 14:05
memberNickPace8-Sep-11 14:05 
AnswerRe: Love It! Pin
Pieter Van Parys at 8-Sep-11 23:50
memberPieter Van Parys8-Sep-11 23:50 
GeneralAwesome! Pin
abdurahman ibn hattab at 7-Sep-11 3:00
memberabdurahman ibn hattab7-Sep-11 3:00 
GeneralRe: Awesome! Pin
Pieter Van Parys at 7-Sep-11 7:13
memberPieter Van Parys7-Sep-11 7:13 
QuestionGreat stuff Pin
KDME at 7-Sep-11 2:23
memberKDME7-Sep-11 2:23 
AnswerRe: Great stuff Pin
Pieter Van Parys at 7-Sep-11 5:26
memberPieter Van Parys7-Sep-11 5:26 
QuestionMy vote of 5 Pin
Filip D'haene at 7-Sep-11 0:36
memberFilip D'haene7-Sep-11 0:36 
AnswerRe: My vote of 5 Pin
Pieter Van Parys at 7-Sep-11 7:14
memberPieter Van Parys7-Sep-11 7:14 
GeneralMy vote of 5 Pin
Ankush Bansal at 7-Sep-11 0:33
memberAnkush Bansal7-Sep-11 0:33 
GeneralRe: My vote of 5 Pin
Pieter Van Parys at 7-Sep-11 7:14
memberPieter Van Parys7-Sep-11 7:14 
GeneralMy vote of 5 Pin
ScruffyDuck at 6-Sep-11 21:24
memberScruffyDuck6-Sep-11 21:24 
GeneralRe: My vote of 5 Pin
Pieter Van Parys at 7-Sep-11 8:40
memberPieter Van Parys7-Sep-11 8:40 
QuestionGreat Pin
Wrangly at 6-Sep-11 9:36
memberWrangly6-Sep-11 9:36 
AnswerRe: Great Pin
Pieter Van Parys at 6-Sep-11 21:35
memberPieter Van Parys6-Sep-11 21:35 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150427.1 | Last Updated 2 Jul 2012
Article Copyright 2011 by Pieter Van Parys
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid