Click here to Skip to main content
Click here to Skip to main content

Automatic Application Wait Cursor

By , 16 Mar 2005
 

Sample Screen Shot from Demo

Introduction

Making your WinForm applications User Friendly is important. One aspect of a good user experience is informing your user that your application is unresponsive during short periods of work. This article introduces an effective and simple way of adding application wide WaitCursors using one line of start-up code.

Background

Recently, I have been working on a WinForms project at home that occasionally performs some short (less than 5 seconds) running tasks. I wanted the user to be informed that during that short period the UI is unresponsive, so I chose to use the Cursors.WaitCursor to indicate this. This article shows how I came to my final WaitCursor library implementation which I believe is a completely re-usable library.

Using the code

From a developer's point of view, using the WaitCursor library could not get any simpler. Add a reference to the WaitCursor assembly and then add the following line to your application start-up code:

ApplicationWaitCursor.Cursor = Cursors.WaitCursor;

That’s it!

You can of course use any Cursor you like, you can use one of the predefined Cursors or you can create a new cursor and use that instead. You can also fine tune the amount of work time that will elapse before the Cursor is shown:

ApplicationWaitCursor.Delay = 
             new TimeSpan(0, 0, 0, 1, 0);  // Delay of 1 second

How the library came about

During development of a recent WinForms project at home, I had decided to use the cursor to indicate short running tasks to the user, like so:

private void DoShortRunningTask()
{
    Cursor.Current = Cursors.WaitCursor; 

    .. do some work .. 

    Cursor.Current = Cursors.Default; 
}

Now, before you say "Where’s the exception handling code?", I am trying to illustrate how I eventually came to my final cut of the WaitCursor library.

The above code works, or, at least it works most of the time. I, of course, found that without any exception handling I could end up with the WaitCursor on permanently, so I quickly came up with:

private void DoShortRunningTask()
{
  Cursor.Current = Cursors.WaitCursor;
  try
  {
    .. do some work ..
  }
  finally
  {
    Cursor.Current = Cursors.Default;
  }
}

Now that’s better, I've guaranteed that the Cursor will always be returned to the Default cursor even if an exception occurs. However, I found it arduous to wrap that code around every short running task that I was developing.

Then I remembered how I used to use C++ stack based destructors to perform tear down work upon exiting of a method:

void DoShortRunningTask()
{
StWaitCursor cursor = new StWaitCursor();

  .. do some work .. 
// Implicitly called ~StWaitCursor returns the Cursor to Default


}

But I couldn't use C# destructors the same way since you can't guarantee when a C# destructor is called since the Garbage Collector thread is responsible for that.

Instead, C# uses a different language feature, the using statement which implicitly calls the Dispose method of objects that implement IDisposable. Although (I find it's) not quite as easy to use as the C++ destructor, you can achieve the same result with the using statement:

private void DoShortRunningTask()
{
    using (new StWaitCursor())
    { 
        .. do some work .. 
    } 
}

This code, of course, requires the StWaitCursor class:

public class StWaitCursor : IDisposable
{
  public StWaitCursor()
  {
    Cursor.Current = Cursors.WaitCursor;
  }
  public void Dispose()
  {
    Cursor.Current = Cursors.Default;
  }
}

There, nice and simple. I found, however, that if my short running task was too short then the user sees a quick flickering of the Cursor to and from the WaitCursor, quite annoying. So I decided I needed a way of turning the WaitCursor on after a predefined amount of time during a task.

After quite a few iterations and refactorings, I came up with what I called the StDelayedCallback class (see source code) which is a generic class that once instantiated will wait for a specified amount of time before calling the Start method of an IDelayedCallbackHandler, and if Start was called that it was guaranteed to call the Finish method of the same interface. Once I had this, I could easily implement the interface such that the WaitCursor was turned on when Start was called and returned the Default cursor when Finish was called.

Some things I found

During development of the WaitCursor library, I discovered that I could not set Cursor.Current on any thread other than the GUI thread. This is where the Win32 AttachThreadInput method can be used to get around this problem effectively. So I developed the StThreadAttachedDelayedCallback class which wraps up the call to the AttachThreadInput.

So eventually I ended up with a generic StDelayedCallback class, a ThreadInput attached version of it called StThreadAttachedDelayedCallback and a specific Cursor implementation called StWaitCursor. Incidentally, I used the prefix St since I had originally intended on using these classes in a similar way to the C++ Stack based classes I had used in the past. So up until now, I had intended on using the following:

private void DoShortRunningTask()
{
  using (new StWaitCursor(new TimeSpan(0, 0, 0, 0, 500))
  {
    .. do some work ..
  }
}

However, it still meant I had to be explicit about where I wanted my WaitCursor to show.

Then I had an attack of brilliance and came up with the ApplicationWaitCursor singleton class which neatly wraps the StWaitCursor such that whenever the application is working, or rather, whenever it’s not regularly calling OnApplicationIdle, then the StWaitCursor kicks in and shows the WaitCursor.

The only caveat I found was when I dragged a window around which blocks the OnApplicationIdle call. So, I also intercept the WM_NCLBUTTONDOWN message (which is sent at the beginning of the window dragging) to temporarily disable the StWaitCursor.

That's where I am up to with the current WaitCursor implementation and in brief how I got there. You can easily derive from the StDelayedCallback class to create similar effects. Perhaps you would like to show an Indefinite progress bar during your long running tasks, or indicate "Saving Changes..." in a StatusBar control:

private void SaveChanges()
{
  using (new StStatusBar(stbMain, "Saving Changes...")
  {
    .. do some work ..
  }
  // StStatusBar.IDispose could return the Text
  // of the StatusBar to its original value 
}

Note that the source code does not contain the StStatusBar class, I am just using it as an example of other ways you could use the StDelayedCallback class.

History

Version 1.0.0.0 (12th March 2005)

  • Initial version.

Version 1.0.1.0 (16th March 2005)

  • Updated to be CLS compliant (thanks to C a r l and Mathew Hall for pointing this out).

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

About the Author

Steve Fillingham
Web Developer
Australia Australia
Member
I am a .NET Developer living in Melbourne, Australia. I am happily married with 2 gorgeous little girls, Georgia and Maddison.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 4memberRafique Sheikh18 Mar '13 - 14:52 
GeneralMy vote of 5memberfoxaxel17 Nov '11 - 0:39 
QuestionThank you, a small issue we are noticingmemberreddyp8213 Oct '11 - 12:14 
GeneralAny solutions to the problems belowmemberMatthew Goldman21 Feb '11 - 4:21 
GeneralMy vote of 5membercrocher6 Feb '11 - 15:08 
GeneralGood jobmemberCIDev22 Oct '10 - 6:46 
GeneralNice articlemvp d@nish 14 Jun '10 - 6:23 
GeneralCursors goes to wait cursor in windows Copy paste menu dialogmembererikgenwise18 Feb '10 - 2:15 
GeneralRe: Cursors goes to wait cursor in windows Copy paste menu dialogmemberMatthew Goldman4 Feb '11 - 11:43 
GeneralNice Job !!memberBorun8 Jun '09 - 8:08 
GeneralA Few More Bugsmemberchewmon3420 Mar '09 - 5:54 
GeneralWorks... mostlymemberJakub Janda4 Mar '09 - 1:10 
GeneralStill brilliant after 3 years!memberseankearon5 Feb '09 - 12:21 
GeneralBrilliant, thanks a lot!memberHerribert117 Oct '08 - 0:21 
GeneralIs it possible to use this in a VS2008 C++ Windows Forms applicationmemberbencbr16 Sep '08 - 8:52 
GeneralRe: Is it possible to use this in a VS2008 C++ Windows Forms applicationmemberSteve Fillingham19 Sep '08 - 1:08 
QuestionRe: Is it possible to use this in a VS2008 C++ Windows Forms applicationmemberTheBerk9 Jan '09 - 10:29 
AnswerRe: Is it possible to use this in a VS2008 C++ Windows Forms applicationmemberHowitZer2623 Feb '09 - 11:08 
QuestionCan this work with WPFmembersreeni7320 Jul '08 - 10:10 
GeneralWait cursor shows when holding mouse button down on a scroll barmemberQStarin9 Jul '08 - 7:03 
GeneralRe: Wait cursor shows when holding mouse button down on a scroll barmembergezanal16 Dec '08 - 7:58 
QuestionWhat version of the .NET Framework has this been tested on?memberMatt Christenson28 May '08 - 6:09 
AnswerRe: What version of the .NET Framework has this been tested on?memberQStarin9 Jul '08 - 6:50 
GeneralRe: What version of the .NET Framework has this been tested on?memberSteve Fillingham11 Jul '08 - 15:44 
Generalvery good.memberali_reza_zareian17 Apr '08 - 0:43 
GeneralBeyond what I neededadminChris Maunder16 Apr '08 - 2:11 
GeneralGreat work this was just what i was looking formembergshea1 Dec '07 - 3:39 
GeneralThanksmembermark.deraeve10 Oct '07 - 21:12 
GeneralRe: Thanksmembermark.deraeve11 Oct '07 - 1:53 
GeneralThanks! Awesome idea!!memberBit-Smacker31 Aug '07 - 10:05 
GeneralSo sweet, I call it ninja-code!memberBrad_Isbell27 Oct '06 - 12:26 
GeneralMinor Issuemembersecovel22 May '06 - 3:18 
In tight loops that effect how the GUI is displayed it is typical to call Application.DoEvents(); so that the GUI has some time to repaint. Otherwise the GUI appears to be completely locked up and you get strange paint artifacts. When I simulate this in your test project the WaitCursor turns off at the first DoEvents, and never turns back on.
 
private void button2_Click(object sender, System.EventArgs e)
{
// Simulate some really hard work ..
statusBar1.Text = "Working Really Hard...";
for (int i = 0; i < 6; i++)
{
Application.DoEvents();
Thread.Sleep(500);
}
 
statusBar1.Text = "Ready";
}
 
If I set the Sleep() time a little higher then the cursor flashes on and off.
 
Perhaps a delay timer for turning off the wait cursor would help?
 
Sean

GeneralRe: Minor IssuememberSteve Fillingham23 May '06 - 1:22 
GeneralBrilliant - thanks!memberseankearon4 Apr '06 - 14:27 
GeneralRe: Brilliant - thanks!memberSteve Fillingham6 Apr '06 - 0:25 
QuestionStStatusbar?memberHyperX18 Aug '05 - 11:49 
AnswerRe: StStatusbar?memberSteve Fillingham19 Aug '05 - 2:02 
GeneralInteresting, but has a few little bugs...memberspam2@vbusers.com12 Aug '05 - 6:05 
GeneralVery good job.memberHyperX11 Aug '05 - 12:35 
QuestionAm I missing anything?memberYaron Shkop6 Jun '05 - 1:36 
AnswerRe: Am I missing anything?memberSteve Fillingham6 Jun '05 - 4:30 
GeneralRe: Am I missing anything?memberYaron Sh14 Jun '05 - 0:15 
GeneralRe: Am I missing anything?memberSteve Fillingham14 Jun '05 - 4:07 
GeneralA little bugmemberNikolyv24 May '05 - 21:25 
GeneralSeparate threadsussnamsaray21 Apr '05 - 8:10 
GeneralVB versionmembermarcrobitaille29 Mar '05 - 3:01 
GeneralRe: VB versionmembermarcrobitaille30 Mar '05 - 4:18 
QuestionWhen to expect an updated version?memberC a r l28 Mar '05 - 9:01 
AnswerRe: When to expect an updated version?memberSteve Fillingham29 Mar '05 - 0:27 
GeneralRe: When to expect an updated version?memberC a r l29 Mar '05 - 5:05 

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 16 Mar 2005
Article Copyright 2005 by Steve Fillingham
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid