Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/cache.php on line 36

Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/query.php on line 21

Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/theme.php on line 507
10 Minute Tutorial - Silverlight: Mouse button and scroll wheel event handling using managed code (C#) : Die, AJAX!

10 Minute Tutorial - Silverlight: Mouse button and scroll wheel event handling using managed code (C#)

Originally, I wanted this post to stand as the definitive tutorial on mouse handling in Silverlight. However, after researching all the different methods people use to handle the right mouse button and scroll wheel in Silverlight, I quickly gave up that pipe dream. Instead, this tutorial serves as merely a starting point for handling mouse events in Silverlight. If you can’t find what you need here, chances are, someone else has hacked together the solution you nedd. I provide links to a few of these solutions on my Silverlight Portal page.

So, for this tutorial, we will create a rectangle that you can drag using the left mouse button, resize using the scroll wheel and change its color using the right mouse button. Hopefully, this examle will give you a basic overview on how to handle mouse events in Silverlight.

With all that said, let’s get started.

Prerequisites

QuickStart

If you want to see the end result of this tutorial and you have installed all the prerequisites, then please download the ZIP file below, unzip it and open the mouse-events.html file in your browser. You should see a blue rectangle that you can drag using the left mouse button, resize using the scroll wheel and change its color using the right mouse button.

You can see it in action, here.

Lesson

Step 1: Create the HTML

Let’s go ahead and get the typical HTML and Javascript out the way. First the HTML:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   <head>
      <title>Mouse Events Application</title>
      <script type="text/javascript" src="Silverlight.js"></script>
      <script type="text/javascript" src="createSilverlight.js"></script>
    </head>
    <body>
      <div id="silverlightControlHost">
      </div>
      <script type="text/javascript">
          // Find the div by id
          var hostElement = document.getElementById("silverlightControlHost");
 
            // Create the Silverlight control
          createSilverlight(hostElement);
       </script>
    </body>
</html>

Next, the Javascript:


//creates the silverlight control within the tag specified by controlHostId
 
function createSilverlight( controlHost )
{
  Silverlight.createObjectEx({
    source: "ClientBin/MouseEventsApplication.xap",
    parentElement: controlHost,
    id: "silverlightControl",
    properties: {
      width: "350",
      height: "350",
      version: "2.0.31005.0",
      background: "white",
      isWindowless: "true",
      enableHtmlAccess: "true"
    },
    events: {}
  });
}

Nothing out of the ordinary here. Remember to grab the latest version of Microsoft’s Silverlight.js file.

Step 2: Create the XAML

Now, to draw the rectangle:


<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="MouseEventsApplication.MouseEventsControl">
  <Canvas x:Name="theCanvas" Width="640" Height="480">
    <Rectangle x:Name="theRectangle"
      Width="50"
      Height="50"
      Fill="Blue"
      Canvas.Left="50"
    Canvas.Top="50" />
  </Canvas>
</UserControl>

We will add more to this XAML througout the tutorial.

Step 3: Drag the rectangle

First, the class declaration:


namespace MouseEventsApplication
{
  using System;
  using System.Windows;
  using System.Windows.Browser;
  using System.Windows.Input;
  using System.Windows.Media;
  using System.Windows.Controls;
  using System.Windows.Shapes;
 
  public partial class MouseEventsControl : UserControl
  {
    public MouseEventsControl()
    {
                InitializeComponent();
    }    
 
  }
}

Simple enough. Now, dragging the rectangle requires tracking two pieces of state:

So, let’s add those now:


namespace MouseEventsApplication
{
  using System;
  using System.Windows;
  using System.Windows.Browser;
  using System.Windows.Input;
  using System.Windows.Media;
  using System.Windows.Controls;
  using System.Windows.Shapes;
 
  private bool isDragging = false;
  private Point dragOffset;

 
  public partial class MouseEventsControl : UserControl
  {
    public MouseEventsControl()
    {
                InitializeComponent();
    }    
 
  }
}

The isDragging variable will act as a flag signifying when the user drags the rectangle. The Point named dragOffset will hold the x and y coordinate offsets from the upper-left corner of the rectangle and the initial mouse position when the drag starts.

To track the initial offset and set the dragging flag, we’ll need to handle event when the user holds the mouse button down:


protected void MouseEventsControl_MouseLeftButtonDown( object sender, MouseButtonEventArgs e)
{
  if( e.OriginalSource == theRectangle )
  {
    this.isDragging = true;
 
    dragOffset.X = e.GetPosition(theCanvas).X - Canvas.GetLeft(theRectangle);
    dragOffset.Y = e.GetPosition(theCanvas).Y - Canvas.GetTop(theRectangle);
 
    CaptureMouse();
  }
}

This code should read pretty easily. If the theRectangle generates the mouse left button down event (meaning, the user clicked on the rectangle and not dead whitespace), then set the dragging flag to true, and set the offset to the distance starting from the source of the click to the upper left of the rectangle. I highlighted the theRectangle variable to remind the reader that because the Rectangle element defined in the XAML has an x:Name attribute, we can reference that name in our class as a variable.

The call to CaptureMouse ensures that while dragging the rectangle, only the rectangle will receive events. This comes in particularly handy because sometimes during a drag operation, if the user moves the mouse quickly enough, the mouse and the rectangle will go “out-of-sync” and the mouse pointer will actually leave the bounds the rectangle, cutting off all events. Since the user would still have the left mouse button down and the dragging flag would still equal true, this would cause all sorts of havoc and confusion.

So, if pressing the left button down turns on dragging, releasing it should turn dragging off. Let’s handle that now:


protected void MouseEventsControl_MouseLeftButtonUp( object sender, MouseButtonEventArgs e)
{
  if( this.isDragging )
  {
    this.isDragging = false;
 
    ReleaseMouseCapture();
  }
}

If the user is currently dragging the rectangle, we turn dragging off and release the mouse we captured earlier.

Now, we need to update the position of the rectangle as the mouse moves to create a dragging effect:


protected void MouseEventsControl_MouseMove(object sender, MouseEventArgs e)
{
  if( this.isDragging )
  {
    Canvas.SetLeft(theRectangle, e.GetPosition(theCanvas).X - dragOffset.X);
    Canvas.SetTop(theRectangle, e.GetPosition(theCanvas).Y - dragOffset.Y);
  }
}

So, if the user is currently dragging the rectangle, we set the Canvas.Left and Canvas.Top attached properties to the offset we saved during the MouseLeftButtonDown event. Not to difficult.

We still need to handle one final scenario: when the user drags the mouse pointer out of the bounds of our Silverlight control WHILE still dragging the rectangle. If this occurs, since we captured the mouse earlier, Silverlight will send a LostMouseCapture event. When we receive this event, we should set our dragging flag to false to avoid any undesired side effects:


protected void MouseEventsControl_LostMouseCapture( object sender, MouseEventArgs e )
{
  if( this.isDragging )
  {
    this.isDragging = false;
  }
}

Again, pretty simple.

Almost done! Now we need to finish it off by wiring up the event handlers in the XAML:


  <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="MouseEventsApplication.MouseEventsControl"
    MouseLeftButtonDown="MouseEventsControl_MouseLeftButtonDown"
    MouseLeftButtonUp="MouseEventsControl_MouseLeftButtonUp"
    MouseMove="MouseEventsControl_MouseMove"
    LostMouseCapture="MouseEventsControl_LostMouseCapture">

    <Canvas x:Name="theCanvas" Width="640" Height="480">
      <Rectangle x:Name="theRectangle"
        Width="50"
        Height="50"
        Fill="Blue"
        Canvas.Left="50"
      Canvas.Top="50" />
    </Canvas>
  </UserControl>

This should read pretty easily. Each event gets matched to its appropriate event handler.

Whew! A fair amount of code to walk through and lots of concepts to handle, but the end result covers all the basic mouse event handling scenarios.

Step 4: Resize the rectangle

Due to partial Silverlight-suckage, we can’t handle the mouse wheel event directly. So, we’ll need to handle the mouse wheel event generated by Javascript instead:


private void InitializeMouseWheel()
{
  HtmlPage.Window.AttachEvent( "DOMMouseScroll", this.OnMouseWheel ); // Mozilla
  HtmlPage.Window.AttachEvent( "onmousewheel", this.OnMouseWheel );
  HtmlPage.Document.AttachEvent( "onmousewheel", this.OnMouseWheel ); // IE
}

This code uses the Silverlight-HTML bridge to attach to different mouse wheel events sent by the different browsers. When it receives that event, it calls a function named OnMouseWheel which we will define later.

Don’t forget to add the InitializeMouseWheel function to our constructor, so it will attach on load:


public MouseEventsControl()
{
  InitializeComponent();
  InitializeMouseWheel();
}

To resize the rectangle we will use a ScaleTransform, which allows us to shrink or grow (i.e transform) an element. To do this, we’ll need a variable to track and manipulate our current transform:


public partial class MouseEventsControl : UserControl
{
  private bool isDragging = false;
  private Point dragOffset;
  private ScaleTransform scale = new ScaleTransform();
 
  public MouseEventsControl()
  {
        InitializeComponent();
    InitializeMouseWheel();
 
    scale.ScaleX = 1;
    scale.ScaleY = 1;
    theRectangle.RenderTransform = scale;

  }

Setting the initial scale factors (ScaleX and ScaleY) to 1, essentially tells Silverlight that the object shouldn’t be scaled. Any value higher than one will grow the object and any value lower than one will shrink it. For example, setting both scale factors to 2 will double the size of the object. Also, setting the RenderTransform property tells the rectangle to obey whatever scale factors we set.

With this, we can finally create the OnMouseWheel function, as promised:


protected void OnMouseWheel( object sender, HtmlEventArgs e )
{
  double mouseDelta = 0;
  ScriptObject eventObject = e.EventObject;
 
  // Mozilla and Safari
  if ( eventObject.GetProperty( "detail" ) != null )
    mouseDelta = ( ( double )eventObject.GetProperty( "detail" ) );
 
  // IE and Opera
  else if ( eventObject.GetProperty( "wheelDelta" ) != null )
    mouseDelta = ( ( double )eventObject.GetProperty( "wheelDelta" ) );
 
  mouseDelta = Math.Sign( mouseDelta );
 
  if( mouseDelta > 0 )
  {
    scale.ScaleX += .1;
 
    scale.ScaleY += .1;
  }
 
  if( mouseDelta < 0 )
  {
    scale.ScaleX -= .1;
    scale.ScaleY -= .1;
  }
 
  if( mouseDelta != 0 )
  {
    e.PreventDefault();
    eventObject.SetProperty("returnValue", false);
  }
}

I know this code looks daunting, so let’s tackle it step by step. The e.EventObject property represents an actual Javascript event object, which we assign a variable of the rather generic ScriptObject type. Using the GetProperty function of the ScriptObject, we pull the data that we need from the mouse wheel event (in the case of Mozilla and Safari, that data is stored in the “detail” property, in the case of IE and Opera, it’s stored int he “wheelDelta” property). The call to Math.Sign simply tells us whether or not the value of the wheel delta is positive or negative. If positive, we increase the scale of the object, if negative, we decrease it. And, if there was a change in the wheel delta (either positive or negative), we use the PreventDefault to tell the Javascript engine not to execute the default Javascript event handler for this event, in conjunction with setting the “returnValue” property of the script object to false, letting the Javascript engine know that this event should not be processed any further.

Step 5: Changing the rectangle color

Due to MAJOR Silverlight-suckage, handling right mouse button clicks in Silverlight remains somewhat of an inexact science. The method I present here will only partially work in some browsers, but should be enough for you to understand the concept. I have posted additional links to articles offering more complete, if more complicated, techniques for handling the scroll wheel and right button clicks that point on my Silverlight Portal page.

So, why the suckage? Well, it turns out that Microsoft, like Macromedia/Adobe, makes a context menu pop up when you click on a Silverlight control and they provide no method to override it within Silverlight. So, that leaves us handling Javascript events again. sigh

Like with the mouse wheel, let’s create an initialize function to attach to the event:


private void InitializeRightMouseButton()
{
  HtmlPage.Document.GetElementById("silverlightControl").AttachEvent("oncontextmenu", this.OnContextMenu);
}

This time, the event handler gets attached to an individual HTML element with the id of “silverlightControl”, rather than to HtmlPage.Document or HtmlPage.Window like the previous step. Hopefully, this id looks familiar: it belongs to the <div> that contains the Silverlight control. By attaching only to this <div>, the context menu will still show up on the rest of the page (allowing us to continue to use tools like FireBug ;) ). Finally, you can see that when Javascript sends the “oncontextmenu” event, we will call the OnContextMenu function.

Moving on, we will need to call InitializeRightMouseButton in the constructor:


public MouseEventsControl()
{
  InitializeComponent();
  InitializeMouseWheel();
  InitializeRightMouseButton();
 
  scale.ScaleX = 1;
  scale.ScaleY = 1;
  theRectangle.RenderTransform = scale;
}

Before writing the OnContextMenu event handler, we need somewhere to store the color of the rectangle. We’ll use a WPF SolidColorBrush for this:


public partial class MouseEventsControl : UserControl
{
  private bool isDragging = false;
  private Point dragOffset;
  private ScaleTransform scale = new ScaleTransform();
  private SolidColorBrush rectanglePaintBrush = new SolidColorBrush();
 
  public MouseEventsControl()
  {
        InitializeComponent();
    InitializeMouseWheel();
    InitializeRightMouseButton();
 
    scale.ScaleX = 1;
    scale.ScaleY = 1;
    theRectangle.RenderTransform = scale;
 
    rectanglePaintBrush.Color = Colors.Blue;
    theRectangle.Fill = rectanglePaintBrush;

  }

The variable rectanglePaintBrush will hold the current color of the rectangle. In the constructor, we initialize this value to Colors.Blue and then set the Fill property on theRectangle to this brush, so whenever we change the color of rectanglePaintBrush the rectangle will reflect it.

Finally, the context menu event handler:


protected void OnContextMenu(object sender, HtmlEventArgs e)
{
  ScriptObject eventObject = e.EventObject;
 
  if( rectanglePaintBrush.Color == Colors.Blue )
    rectanglePaintBrush.Color = Colors.Green;
  else
    rectanglePaintBrush.Color = Colors.Blue;
 
  e.PreventDefault();
  e.StopPropagation();
  eventObject.SetProperty("returnValue", false);
}

After looking at the mouse wheel code, nothing here should surprise you. When the current color of the rectangle is blue, we change it to green and vice versa. The only thing I’ve added new here is the call to StopPropagation. This supposedly stops the propagation of events to the rest of the Javascript objects in the DOM hierarchy.

But, inconsistencies in both in the Firefox 3.0.3 and Safari Silverlight plug-ins make this all moot. In Firefox 3.0.3 (and potentially other Firefox versions I didn’t test), the Silverlight context menu appears EVEN IF you override the oncontenxtmenu event. If you right click a second time (not on the menu), THEN the OnContextMenu code will execute. In Safari, right button click handling doesn’t appear to work at all using this method. Of course, this problem doesn’t show up at all in IE.

I hate browsers.

Step 6: Build it

The sample code bundle contains the project file and all the support files needed to build this sample. The build file looks similar to the build file in my Silverlight MSBuild tutorial with an added assemby reference:


<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<ItemGroup>
  <Reference Include="mscorlib" />
  <Reference Include="system" />
  <Reference Include="System.Windows" />
  <Reference Include="System.Windows.Browser" />
</ItemGroup>
...
</Project>

Including the highlighted assembly allows us to access the Silverlight-HTML bridge.

You can use this command line to build the project:


"C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" MouseEventsApplication.csproj
Step 7: Run it

When you open mouse-events.html in your browser, you should see a blue rectangle. You should be able to drag the rectangle using the left mouse button, resize it using the scroll wheel and change its color using the right mouse button.

Conclusion

So, now you have some basic code for handling mouse events in Silverlight. One a side note, I find it terribly disappointing, after seeing how people wrestled to make right button click and scroll wheel handling in Silverlight 1.1 and both 2.0 Betas work, when Microsoft officially released Silverlight 2.0, they deigned not relieve any developer pain. Very disappointing, indeed. We can only hope that they will fix these issues in future version. Till then, keep codin’!


Share and Enjoy:
These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • StumbleUpon
  • Reddit
  • del.icio.us
Related Posts:
Silverlight Portal
Introduction to game programming in Silverlight
10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #2 (C#)
10 Minute Tutorial - Silverlight: Using JavaScript to Call Scriptable Managed Code (C#)

Comments

Comments are closed.