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 Cursor
s 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);
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 ..
}
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 ..
}
}
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)
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).