Adding right click to Silverlight

In an earlier post, I created a small extension library that added several mouse gestures to Silverlight. However, the right-click only worked in Internet Explorer, and the solution was a bit of a hack. So to continue down the road of hacks, I found that there is a way to prevent the Silverlight Plugin from getting the right click event. By inserting a <div> on top of the plugin when the right mousebutton down event fires, you can prevent the mozilla browsers from getting the rightclick event. The mouseup event is then fired on this overlay div, and we remove the overlay again when the button is released or the mouse moves. Ugly but it works... The approach still requires the plugin to run in windowless mode.

Of course whether you even should be preventing the user from getting to the silverlight context menu in the first place is a whole different discussion.

I've updated the demo page, source and binary:

Try a demo!

Download binary (7 kb)

Download source (25 kb)

Using surrogate binders in Silverlight

In WPF you can bind values to any property on a DependencyObject, however in Silverlight, you can only bind to FrameworkElements. Most often when I have hit this roadblock is when I want to bind the rotation or scale of an element to a value. For instance a compass direction bounded to the current heading.

In WPF this would look like this:

<Image>
<Image.RenderTransform >
<RotateTransform Angle="{Binding Path=Heading}" />
</Image.RenderTransform>
</Image>

Unfortunately, since RotateTransform isn’t a FrameworkElement, this won’t work in Silverlight.

Enter: Attached Properties.

Attached properties are really neat when you start getting to know them, and you can do some pretty cool stuff, and still stick to all the MVC glory that the above case prevents you from. Using an custom attached property that manages rotation, I could for instance write:

<Image local:SurrogateBinder.Angle="{Binding Path=Heading}">
<Image.RenderTransform >
<RotateTransform  />
</Image.RenderTransform>
</Image>

So what does this binder look like? The first step is to declare the actual attached property, as well as a get and set method. Note that the naming of the property and the two get and set methods are important for this to work.
 
public static class SurrogateBinder
{
public static readonly DependencyProperty AngleProperty =
DependencyProperty.RegisterAttached("Angle", typeof(double),
typeof(SurrogateBinder),
new PropertyMetadata(OnAngleChanged));
public static double GetAngle(DependencyObject d)
{
return (double)d.GetValue(AngleProperty);
}
public static void SetAngle(DependencyObject d, double value)
{
d.SetValue(AngleProperty, value);
}
}

The next step is to react to when this value is being set/changed. The property declaration above references an OnAngleChanged method to call when the property changes. The idea is that when the value changes, we grab the rotate transform and set the value.
private static void OnAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is UIElement)
    {
        UIElement b = d as UIElement;
        if (e.NewValue is double)
        {
            double c = (double)e.NewValue;
            if (!double.IsNaN(c))
            {
                if (b.RenderTransform is RotateTransform)
                    (b.RenderTransform as RotateTransform).Angle = c;
                else 
                    b.RenderTransform = new RotateTransform() { Angle = c };
            }
        }
    }
}

In this case, the property binder is only made to work with a UIElement that wants a rotation applied. But by using a little reflection magic, we can create a completely generic binder that can set any property.
Instead of using one attached property, we use two. One for the value to bind, and another for the property to bind to. This is very similar to when you are creating animations and you both set the target you are animating and the property you are animating on. Apart from the Reflection magic, the code is pretty much the same:
 
public static class SurrogateBind
{
    public static readonly DependencyProperty TargetProperty =
        DependencyProperty.RegisterAttached("Target", typeof(string), typeof(SurrogateBind), null);
 
    public static string GetTarget(DependencyObject d)
    {
        return (string)d.GetValue(TargetProperty);
    }
 
    public static void SetTarget(DependencyObject d, string value)
    {
        d.SetValue(TargetProperty, value);
    }
 
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.RegisterAttached("Value", typeof(object), typeof(SurrogateBind),
        new PropertyMetadata(OnValueChanged));
 
    public static object GetValue(DependencyObject d)
    {
        return (object)d.GetValue(ValueProperty);
    }
 
    public static void SetValue(DependencyObject d, object value)
    {
        d.SetValue(ValueProperty, value);
    }
 
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        string path = GetTarget(d);
        if (String.IsNullOrEmpty(path)) return;
        string[] pathElements = path.Split(new char[] { '.' });
        PropertyInfo propertyInfo = null;
        object o = d;
        for (int i = 0; i < pathElements.Length; i++)
        {
            if (o == null) break;
            string s = pathElements[i];
            int begin = s.LastIndexOf('[');
            bool isIndexed = s.EndsWith("]") && begin >= 0;
            propertyInfo = o.GetType().GetProperty(isIndexed ? s.Substring(0, s.LastIndexOf('[')) : s);
            if (propertyInfo == null) break;
            if (i < pathElements.Length - 1)
            {
                object[] index = null;
                if (isIndexed)
                {
                    index = new object[] { int.Parse(s.Substring(begin + 1, s.LastIndexOf(']') - begin - 1)) };
                }
                o = propertyInfo.GetValue(o, index);
            }
        }
        if (propertyInfo != null && propertyInfo.PropertyType == e.NewValue.GetType())
        {
            propertyInfo.SetValue(o, e.NewValue, null);
        }
    }
}

This allows us to to the same thing in XAML:
<TextBox Text="Hello World"
binders:SurrogateBind.Value="{Binding Path=Heading}" 
binders:SurrogateBind.Target="RenderTransform.Angle" >
<TextBox.RenderTransform>
<RotateTransform />
</TextBox.RenderTransform>
</TextBox>

Or if we have nested controls:
<TextBox RenderTransformOrigin="0.5,0.5"
Text="Hello Universe!"
binders:SurrogateBind.Value="{Binding Path=MoreValues.Heading}" 
binders:SurrogateBind.Target="RenderTransform.Children.Item[1].Angle" >
<TextBox.RenderTransform>
<TransformGroup>
<ScaleTransform />
<RotateTransform />
</TransformGroup>
</TextBox.RenderTransform>
</TextBox>

This approach can actually be extended to invoke methods on your control. For instance when a value changes, you can trigger a storyboard to start playing etc. This is partly something we will get with Triggers in Silverlight 3.0, but if you can’t wait, this is one way to do it. Maybe I’ll cover this in a later blogpost. but for now you can download the source-code and example here:
 

Extending Silverlight’s mouse events

If you just want the extension method library or source, jump to the bottom. If not, read on…

Silverlight is pretty limited when it comes to mouse events. Out of the box, you only have five events you can subscribe to on a given element:

Notice that there is no such thing as click, double-click, drag, right-click and mouse-wheel.

Click and double click you can create from the up/down events. If the mouse doesn’t move between up and down, its a click, and if it happens two times within a timespan, it’s a double-click. Similarly, a drag is a click with mouse movement in-between down and up. Using extension methods, we can easily extent the UIElement class with two new methods for each event: Attach[eventname] and Detach[eventname], that takes care of this tracking. I earlier described this approach for defining a double click extension. These are all types of gestures we often need, so creating a reusable library with these extensions is a no-brainer.

When it comes to mouse wheel, it gets slightly trickier. We can’t do this using existing Silverlight events. Instead we can use the JavaScript bridge to detect wheel events in the browser, and bubble them into the plugin. First we listen to the JavaScript event on the entire plugin, and then inside Silverlight we use the Enter/Leave events to track whether the mouse is over the element you are listening to events on. However there are cases where this wouldn’t work:

  • The application is running in full screen.
  • The Silverlight plugin is running with HTML access explicitly disabled.
  • The .xap file is hosted on a different domain than the page hosting it.
  • You are running the application in Silverlight 3’s Out-of-browser mode.

All of them are cases where the JavaScript bridge is disabled.

Lastly right-click. This is a whole different story. When you right-click a Silverlight page, it will should you a small context menu with a link to the Silverlight settings. This can’t be overridden, but using the same approach as for Wheel, you can intercept the event using JavaScript and prevent the bubbling to the plugin. However this only works reliably in Internet Explorer, and most secondly only if you run the application in windowless mode.

UPDATE: Right-click now also work in Mozilla browsers. See here

I took all these events, and wrapped the, into a little set of extension method, so you don’t have to write the code over and over again.

To use it, download the library (link at the bottom) and add a reference to the DLL. Then at the top of the class where you want to use the event extensions, add the following to your using statements:

using SharpGIS.MouseExtensions;

When you have done this, you will instantly get new intellisense on any object that inherits from UIElement:

image

You will see seven new Attach* and Detach* methods:

Click Mouse/Down without moving the mouse
DoubleClick Two quick clicks
Drag Mouse down, move, mouse up. Event will fire for each mouse movement.
Hover If the mouse stops over an element and stays in the same place for a short time.
KeyClick Click while the user is holding down a key.
RightClick Windowless only!
Wheel Mouse wheel

You can see a live sample of it in action here.

Assembly Library that you can reference in your project: Download binary (7.14 kb)

Sourcecode and sample application is available here:Download source (24.79 kb)