Testing and debugging UI code, I am often faced with such a situation, when the mouse cursor is changed to Hourglass, but not changed back.
Sometimes, it happened to my own code as well.
The mouse cursor is supposed to be changed back to its original or default image after the procedure which could cause a delay if UI response is already completed (or failed). Sometimes it is not changed back because this procedure fails due to exception or some other reason. Sometimes the code used to set mouse cursor to its original image is simply forgotten.
As a result, I came to a simple pattern which helps to avoid such situation in all cases. I used it in several different UI systems. Now I'll show how it is implemented with .NET. The idea is using helper class implementing
IDisposable
.
This is a
WPF
implementation written in C#:
namespace SA.Universal.UI {
using IDisposable = System.IDisposable;
using FrameworkElement = System.Windows.FrameworkElement;
using Cursor = System.Windows.Input.Cursor;
using Cursors = System.Windows.Input.Cursors;
using Debug = System.Diagnostics.Debug;
public class WaitCursorIndicator : IDisposable {
public WaitCursorIndicator(FrameworkElement owner) {
this.Onwer = owner;
Debug.Assert(
owner != null,
"WaitCursorIndicator expects non-null argument");
if (owner == null) return;
Previous = owner.Cursor;
owner.Cursor = Cursors.Wait;
}
void IDisposable.Dispose() {
if (this.Onwer == null) return;
this.Onwer.Cursor = Previous;
}
FrameworkElement Onwer;
Cursor Previous;
}
}
With
System.Windows.Forms
, it is implemented in a very similar way:
namespace SA.Universal.UI {
using System.Windows.Forms;
using IDisposable = System.IDisposable;
using Debug = System.Diagnostics.Debug;
public class WaitCursorIndicator : System.IDisposable {
public WaitCursorIndicator(Control owner) {
this.Owner = owner;
Debug.Assert(
owner != null,
"WaitCursorIndicator expects non-null argument");
if (owner == null) return;
Previous = this.Owner.Cursor;
owner.Cursor = Cursors.WaitCursor;
}
void IDisposable.Dispose() {
if (Owner == null) return;
Owner.Cursor = Previous;
}
Control Owner;
Cursor Previous;
}
}
The usage looks identical for
WPF
and
System.Windows.Forms
:
using(new WaitCursorIndicator(owner)) {
}
In this code snippet,
owner
is most typically a form or a window.
This way, mouse cursor is changed back no matter what, even if an exception is thrown.
On exit from the "
using
" block,
WaitCursorIndicator.Dispose
is always called. In fact, using this "
using
" block is just
syntactic sugar functionally strictly equivalent to the try-finally block with
Dispose
called in the
finally
section.