Hidden Object: Episode 12 – Custom Mouse Cursor Behavior

October 27, 2009 in Hidden Object Game, Silverlight

This is episode 12 of Creating a Hidden Object Game is Silverlight 3. In this episode, we will create a behavior that allows us to set the shape of the mouse cursor to any Image or Path we desire.

Let start this tutorial in Expression Design. Create a new document that is 25×25 pixels and on the single layer add the shapes shown:

 

102709_1407_HiddenObjec1.png 

Export the document as a PNG making sure that both Transparency and Antialias are checked.

 

If you don’t have a copy of Expression Design, you can use a vector graphics program like Inkscape.

 

Add the image to the Visual Studio project and drag it onto the Canvas. Let’s position the cursor image off screen at: Top = -50 and Left = -5. The -5 positions the cursor so the tip of the arrow is right in the corner and the -50 just to get it off screen. Since this image needs to be over all other objects including the screen Canvas objects, let’s set the ZIndex property to 1000.

We will compensate for the Top value with the OffsetY value in the MouseCursorBehavior that we will create:

 

The two offset values are dependency properties of type Double whereas CursorName is a string. You will notice that the cursor name has the artboard element picker (target symbol) that allows you to pick an object from the artboard.

Under the Interactivity folder, create a folder called MouseCursor and add three class files: MouseCursorBehavior, NameResolvedEventArgs, and NameResolver.

To get NameResolver and NameResolvedEventArgs, I admit that I used .NET Reflector to understand how the TargetedTriggerAction class was able to get a reference to an instance of the Target class using the string dependency property, TargetName.

Here is a sample usage of the NameResolver class as it relates to the MouseCursorBehavior:

NameResolver cursorResolver = new NameResolver();
cursorResolver.NameScopeReferenceElement = AssociatedObject;
cursorResolver.Name = "cursorArrow";
DependencyObject cursor = cursorResolver.Object;
 

After creating an instance of the NameResolver, we must assign an object as the NameScopeReferenceElement and set the name of the element that will be our cursor. Calling the Object property on the resolver will return a reference to the object specified by the Name property. The key method in NameResolver is UpdateObjectFromName:

private void UpdateObjectFromName(DependencyObject oldObject)
{
    DependencyObject resolvedObject = null;
    this.ResolvedObject = null;
 
    if (this.NameScopeReferenceElement != null)
    {
        if (!IsElementLoaded(this.NameScopeReferenceElement))
        {
            this.NameScopeReferenceElement.Loaded += new RoutedEventHandler(this.OnNameScopeReferenceLoaded);
            this.PendingReferenceElementLoad = true;
            return;
        }
        if (!string.IsNullOrEmpty(this.Name))
        {
            FrameworkElement actualNameScopeReferenceElement = this.ActualNameScopeReferenceElement;
            if (actualNameScopeReferenceElement != null)
            {
                resolvedObject = actualNameScopeReferenceElement.FindName(this.Name) as DependencyObject;
            }
        }
    }
    this.HasAttempedResolve = true;
    this.ResolvedObject = resolvedObject;
    if (oldObject != this.Object)
    {
        this.OnObjectChanged(oldObject, this.Object);
    }
}

 

Line 19 shows how NameScopeReferenceElement and Name are used to resolve the actual object.

The MouseCursorBehavior class defines three dependency properties with their corresponding .NET properties:

public static readonly DependencyProperty CursorNameProperty =
     DependencyProperty.Register("CursorName", typeof(string), typeof(MouseCursorBehavior),
     new PropertyMetadata(new PropertyChangedCallback(OnCursorNameChanged)));
 
public static readonly DependencyProperty OffsetXProperty =
    DependencyProperty.Register("OffsetX", typeof(double), typeof(MouseCursorBehavior), null);
    
public static readonly DependencyProperty OffsetYProperty =
    DependencyProperty.Register("OffsetY", typeof(double), typeof(MouseCursorBehavior), null);
 

In the constructor of the behavior, a NameResolver called cursorResolver is created. In the OnAttached method, the resolver’s NameScopeReferenceElement is set to the AssociatedObject. And in the OnCursorChanged method, the Name property is set to the name of the object to be used as the cursor:

private static void OnCursorNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    MouseCursorBehavior behavior = (MouseCursorBehavior)obj;
    behavior.CursorResolver.Name = (string)args.NewValue;
}

To get the artboard element picker to show in the Properties panel for the CursorName, add the CustomPropertyValueEditor attribute to the CursorName property and specify the editor for an Element:

[CustomPropertyValueEditor(CustomPropertyValueEditor.Element)]
public string CursorName
{
    get
    {
        return (string)base.GetValue(CursorNameProperty);
    }
    set
    {
        base.SetValue(CursorNameProperty, value);
    }
}
 

The Cursor property is where the CursorName value is resolved by the NameResolver and the object reference is returned.

Now the behavior has a reference to the image that is being used as a cursor, now it needs to use that instead of the default cursor. In the OnAttached method, the behavior registers to handle the MouseEnter and MouseLeave events of the AssociatedObject. In our case, the MouseCursorBehavior will be attached to the MainPage UserControl which becomes the AssociatedObject. So whenever the mouse enters the area of the UserControl, it will be changed to the arrow:

private void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
{
    if (!this.IsCursorNameSet)
        return;
 
    FrameworkElement cursor = Cursor as FrameworkElement;
 
    cursor.Visibility = Visibility.Visible;
    AssociatedObject.Cursor = Cursors.None;
    cursor.IsHitTestVisible = false;
 
    this.AssociatedObject.MouseMove += new MouseEventHandler(AssociatedObject_MouseMove);
}

The cursor variable has a reference to the resolved object which is the Image named cursorArrow. We make the image visible and hide the real cursor. We must make the image “invisible” to mouse events so we set the IsHitTestVisible property to false. Finally, we register for the MouseMove event which is responsible for positioning the image wherever the mouse should be:

 

private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
    if (!this.IsCursorNameSet)
        return;
 
    FrameworkElement cursor = Cursor as FrameworkElement;
 
    Point mousePosition = e.GetPosition(null);
    cursor.Margin = new Thickness(mousePosition.X + OffsetX, mousePosition.Y + OffsetY, 0, 0);
 
}

To position the image, we use the trick of specifying the top and left values of its Margin based on the current mouse postion and the OffsetX and OffsetY dependency properties we defined.

 

In the MouseLeave event handler, we simply undo what we set in the MouseEnter handler.

 

 

I’ve seen a similar approach to custom cursors various places on the Internet. One requirement that I had was the need to specify a cursor at the UserControl level as shown, but then to have a child (or grandchild, etc.) object of that UserControl also have a unique cursor. If you move the mouse over a hidden rectangle on the left side of the screen the cursor will change to a left-facing arrow. Clicking the arrow will take you to another screen which has a hidden rectangle on the right that displays a right-facing arrow to get you pack to the original screen.

 

 

In the code, I created two path objects for the arrows just to show that any element could be used for a cursor. If I wanted, I could even have a cursor with multiple objects grouped in a Canvas that use animation storyboards.

 

When you look at the complete source for this project, you will notice that I use a Stack data structure to keep track of the nested cursors. I am not completely satisfied with the code as it stands, but it works for the present situation. Two things to note. First, the hidden rectangle must have space all around it so that the cursor properly changes from the left arrow to the cursorArrow. Second, I had to do a workaround because the shapes generated from the ParticlesBehavior was interfering with the custom cursor as sometimes a MouseEnter event was being fired with the star shapes but no MouseLevent event.

 
Check out the code and let me know if you come up with a better solution.

Zip Source Code

silverlight Demo

In the next episode, we will add a hint feature that allows the player to see an area of the screen that still has an object to find.

Hidden Object: Episode 12 – Custom Mouse Cursor Behavior

1 Comments

    1. [...] Mark Tucker is presenting you his Hidden Object Game with the customization of mouse cursor. A very impressive sample in making use of cursor to enhance the game experience. [...]

    2. Hello~ You article looks great and easy to follow.

      I have downloaded your sample and I found that some assemblies are missing. It takes me a while to allocate those assemblies in the website (http://expressionblend.codeplex.com/)

      Anyway, I am looking forward for you upcoming articles.
      Anyway, I am looking forward to your upcoming articles!
      I think it will be great if you could includes those assemblies in the project or link it to the website.

    3. [...] HintBehavior uses two NameResolver instances (see episode 12). The first NameResolver changes the HintOverlayName into a reference to the overlay image control. [...]

Leave a Reply

Hidden Object: Episode 12 – Custom Mouse Cursor Behavior

2 Trackbacks