Silverlight OverrideCursor

One of the really great things you can do with WPF is use Mouse.OverrideCursor to set the cursor for the entire application. This is useful, for instance, if you want your application to perform a lengthy operation, and have the cursor change to a wait cursor, for the duration of the operation. You’d normally do this like:

public void DoAReallyLongOperation()
{
  Cursor savedCursor = Mouse.OverrideCursor;
  try
  {
    PerformMyLengthyOperation();
  }
  finally
  {
    Mouse.OverrideCursor = savedCursor
  }
}

So far, so good. Now, of course, you’re going to run off to your Silverlight applications and do exactly the same for your lengthy operations, aren’t you? After all, Silverlight is a lightweight version of WPF, so surely this will be there for you to use.

Well no. Silverlight doesn’t support the OverrideCursor, but adding it shouldn’t be too hard should it? Well, it turns out that adding the OverrideCursor isn’t as easy as you’d think it would be. If you change the cursor on your page to a wait cursor, for instance, it will still be an IBeam when you move over a text box. This means that your code needs to traverse the visual tree looking for all of the child elements, and setting the cursor to the new cursor.

Edit: Since I posted this earlier today, an edge case was suggested that needed addressing. Basically, the code needs to be able to reset the text boxes back to the IBeam assuming that the OverrideCursor is set back.

The following class provides an attached property that should help greatly with this:

/// <summary>
/// Mouse handling class to simulate the OverrideCursor functionality in WPF.
/// </summary>
public class Mouse : DependencyObject
{
  #region Members
  private static Cursor _oldCursor;
  private static bool _isResetting = false;
  private static Cursor _overrideCursor;
  #endregion
  /// <summary>
  /// Recursively traverse the visual tree, setting the cursor as appropriate.
  /// </summary>
  ///
<param name="current">The element to iterate over.</param>
  ///
<param name="cursor">The cursor to set to.</param>
  /// <remarks>
  /// If the new cursor is the same as the stored original cursor,
  /// then we assume that this is a reset operation taking place.
  /// At this point, we reset the cursor to the original for the element,
  /// irrespective of what the Cursor states.
  /// </remarks>
  internal static void TraverseVisualTree(object current, Cursor cursor)
  {
    DependencyObject ob = current as DependencyObject;
    if (ob != null)
    {
      if (ob is FrameworkElement)
      {
        FrameworkElement element = ob as FrameworkElement;
        Cursor oldCursor = GetOldCursor(element);
        Cursor newCursor = cursor;
        // If this is a reset, then get the old cursor reference
        // back so that we can use this.
        if (_isResetting)
        {
          newCursor = oldCursor;
          SetOldCursor(element, null);
        }
        else
        {
          SetOldCursor(element, element.Cursor);
        }
        element.Cursor = newCursor;
      }
      int counter = VisualTreeHelper.GetChildrenCount(ob);
      for (int i = 0; i < counter; i++)
      {
        object depObj = VisualTreeHelper.GetChild(ob, i);
        TraverseVisualTree(depObj, cursor);
      }
    }
  }
  /// <summary>
  /// The OverrideCursor attached property.
  /// </summary>
  public static readonly DependencyProperty OverrideCursorProperty =
    DependencyProperty.RegisterAttached("OverrideCursor",
    typeof(Cursor),
    typeof(UserControl),
    new PropertyMetadata(null, OnCursorChanged));
  /// <summary>
  /// The OldCursor attached property.
  /// </summary>
  private static readonly DependencyProperty OldCursorProperty =
    DependencyProperty.RegisterAttached("OldCursor",
    typeof(Cursor),
    typeof(UserControl),
    new PropertyMetadata(null));
  /// <summary>
  /// Called when the cursor changes.
  /// </summary>
  private static void OnCursorChanged(DependencyObject source,
    DependencyPropertyChangedEventArgs e)
  {
    if (!(source is FrameworkElement))
      return;
    // If the "old" cursor is the same as the "new" cursor, the
    // code will reset each elements cursor back to its original value.
    _isResetting = (_oldCursor == e.NewValue);
    if (!_isResetting)
    {
      _oldCursor = e.OldValue as Cursor;
    }
    TraverseVisualTree(source, e.NewValue as Cursor);
  }
  /// <summary>
  /// Get the override cursor.
  /// </summary>
  ///
<param name="source">The object to get the override cursor for.</param>
  /// <returns>The populated override cursor.</returns>
  public static Cursor GetOverrideCursor(DependencyObject source)
  {
    return source.GetValue(OverrideCursorProperty) as Cursor;
  }
  /// <summary>
  /// Set the override cursor.
  /// </summary>
  ///
<param name="source">The object to set the cursor for.</param>
  ///
<param name="value">The cursor value to set.</param>
  public static void SetOverrideCursor(DependencyObject source, object value)
  {
    source.SetValue(OverrideCursorProperty, value);
  }
  /// <summary>
  /// Set the old cursor.
  /// </summary>
  ///
<param name="source">The object to set the cursor for.</param>
  ///
<param name="value">The cursor value to set.</param>
  private static void SetOldCursor(DependencyObject source, object value)
  {
    source.SetValue(OldCursorProperty, value);
  }
  /// <summary>
  /// Get the old cursor.
  /// </summary>
  ///
<param name="source">The object to get the old cursor for.</param>
  /// <returns>The populated old cursor.</returns>
  private static Cursor GetOldCursor(DependencyObject source)
  {
    return source.GetValue(OldCursorProperty) as Cursor;
  }
}
Advertisements

One thought on “Silverlight OverrideCursor

  1. Brent Hoskisson

    I have a problem: I have a treeview control, run the program, Open a node, Close the node, then set the mouse to Wait, then set the mouse back to Arrow. The node I opened and closed gets stuck on the Wait cursor. I can fix it by always setting _isResetting to false.

    What is the point of _isResetting? What is the danger of always having it set to false?

    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s