Login


Implementing a WaitCursor Class

By Jonathan Wood on 4/6/2014
Language: C#
Technology: .NETWinForms
Platform: Windows
License: CPOL
Views: 13,997
Frameworks & Libraries » WinForms » General » Implementing a WaitCursor Class

Introduction

For lengthy operations, it is good form to change the mouse cursor to a wait cursor so that the user knows they must wait until the current task is complete.

.NET makes it easy to set a wait cursor. But there are a couple of gotchas to be mindful of.

Setting a Wait Cursor

There are a number of ways to set a wait cursor. You can set a form's Cursor property, set the Cursor.Current property, set a form's UseWaitCursor property, or use the Application.UseWaitCursor property.

Using Application.UseWaitCursor is documented as the preferred way to set the wait cursor. However, this property seems fatally flawed. While this method conveniently sets the UseWaitCursor property of all open forms in your application, you may find that there is no visible change to the mouse pointer!

UseWaitCursor apparently works by sending a WM_SETCURSOR message to the window. The problem is that this message will not be processed until your code returns control back to Windows. And so if you set UseWaitCursor just before beginning a lengthy task, the message won't be processed--and the mouse cursor won't change--until after your task has completed.

So while you can make use of UseWaitCursor, you'll need to use one of the other methods to cause the mouse pointer to change in the scenario I just described.

Restoring the Wait Cursor

Of course, when the operation has completed, it's important to restore the default cursor so the user knows the program is responsive once again. While this is a very simple task, there are considerations here as well.

What happens if your code throws an exception that prevents the code that restores the cursor from ever running? What if your code has other exit points and you just forget to duplicate the code for each exit point? While these may be edge cases, you don't want an error or lesser used code path to leave the wait cursor permanently!

For these reasons, it's good practice to wrap your code in a try...catch block, and then restore the cursor in a finally block. But an easier approach is to use a simple class, such as the one I put together for this purpose.

A Simple WaitCursor Class

Listing 1 shows my WaitCursor class.

Listing 1: The WaitCursor Class

/// <summary>
/// Sets a wait cursor and implements IDisposable so that the cursor can be
/// restored at the end of a using statement.
/// </summary>
public class WaitCursor : IDisposable
{
    public WaitCursor()
    {
        IsWaitCursor = true;
    }

    public void Dispose()
    {
        IsWaitCursor = false;
    }

    public bool IsWaitCursor
    {
        get
        {
            return Application.UseWaitCursor;
        }
        set
        {
            if (Application.UseWaitCursor != value)
            {
                Application.UseWaitCursor = value;
                Cursor.Current = value ? Cursors.WaitCursor : Cursors.Default;
            }
        }
    }
}

You'll notice this class sets both the Application.UseWaitCursor and Cursor.Current properties. This should make the wait cursor work in all conditions, changing the mouse cursor immediately when entering a lengthy operation, and also keeping the wait cursor active if the lengthy operation includes creating other forms or other tasks that involve processing Windows messages.

In addition, the class implements IDisposable, making it easier to guarantee the wait cursor gets restored at the end of the lengthy operation. While you could simply create an instance of the WaitCursor class and call the Dispose() method when your operation is complete, you can also use the using keyword to ensure Dispose() is always called, as shown in Listing 2.

Listing 2: Using the WaitCursor Class

using (var wc = new WaitCursor())
{
    // Do stuff that takes a long time here.
}

The using statement will ensure that the Dispose() method gets called at the end of the using block even if an exception occurs.

If you do have code within the block that requires the cursor be restored, that code could simply call wc.Dispose() directly.

Conclusion

That's all there is too it: A very simply class that will handle any of the issues I've raised in this article. I hope some of you find it useful.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.