Saturday, July 12, 2008

Silverlight 2.0 Controls Bubbling Issues

Recently I was working on some Silverlight 2.0 code examples following the “Controls” tutorial from the silverlight site. First of all, the tutorial was really messy, finding a lot of copy-paste code and a lot of differences between the text and the code that what was actually showed on the tutorial. However those are minor mistakes and the tutorial was easy to follow. The tutorial was about Silverlight 2.0 controls. I was coding using Visual Studio 2008 with the Silverlight 2.0 Beta 2 tools.

Silverlight 2.0 extends the CLR event handling mechanism with RoutedEvents. The defining characteristic of Routed Events is that each event is passed up the Interface Tree in a process known as bubbling (the events are like tiny bubbles rising up from the bottom). However when using Silverlight 2.0 controls, the event is "handled" at the control level and then the bubbling stops. This presents a problem when you have complex UI elements for a control (for example a custom control with different visual behavior, meaning that you have canvas, borders and other UI elements and a Silverlight 2.0 button inside) and you want to capture the “MouseLeftButtonDown” event at the higher level to implement different behaviors, since this event will always be trapped by the button control itself. Let me show you an example (Note: I’m not going to enter into details on the code structure of the example since you can follow the tutorial by yourself and do all the steps and understand how to implement each peace).

Let’s say that we want to have a dragable button on our application. We are going to use a Silverlight 2.0 common button inside a canvas:





In order to attach the event handlers we need to make that button dragable, we need to create and attach the event handlers for the “MouseLeftButtonDown”, “MouseLeftButtonUp” and “MouseMove” in the code behind of our XAML, as follows:


compositeButton.MouseLeftButtonDown += new MouseButtonEventHandler(CompositeButton_MouseLeftButtonDown);
compositeButton.MouseLeftButtonUp += new MouseButtonEventHandler(CompositeButton_MouseLeftButtonUp);
compositeButton.MouseMove += new MouseEventHandler(CompositeButton_MouseMove);

Now that we have our events attached to the canvas UI element (“CompositeButton”), we can implement the drag and drop functionality on those event handlers as follows:


void CompositeButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
beginX = e.GetPosition(null).X;
beginY = e.GetPosition(null).Y;
trackingMouseMove = true;
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
fe.CaptureMouse();
}

void CompositeButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
{
fe.ReleaseMouseCapture();
trackingMouseMove = false;
}
}

void CompositeButton_MouseMove(object sender, MouseEventArgs e)
{
if (trackingMouseMove)
{
double currentX = e.GetPosition(null).X;
double currentY = e.GetPosition(null).Y;
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
{
double canvLeft = Convert.ToDouble(fe.GetValue(Canvas.LeftProperty));
double canvTop = Convert.ToDouble(fe.GetValue(Canvas.TopProperty));

double newLeft = canvLeft + currentX - beginX;
double newTop = canvTop + currentY - beginY;

fe.SetValue(Canvas.LeftProperty, newLeft);
fe.SetValue(Canvas.TopProperty, newTop);

beginX = currentX;
beginY = currentY;
}
}
}

If you run this example, you will notice that the button is not dragable. In fact you will see the button “MouseOver” state and the “MousePressed” state, but it won’t be dragable. The problem is not on the code behind, the issue is that the button is capturing the mouse events and handling them by itself stopping the mouse events to bubble to the canvas element that contains it (the “CompositeButton” canvas element). The solution I was able to find is to place a transparent canvas on top of the button, with the same size, so that when you click the button you are actually clicking the canvas that is on top. Let me show you how the XAML looks like:




As you can see I have declared a canvas after the button with the same width and height and same position as the button itself, but with a transparent background so is not noticeable. When you click on the button, you are actually clicking the canvas, and that’s why now the mouse events are bubbled to the containing canvas (“CompositeButton”). Now the button is dragable!!

This might seem very simple, but the technique can be applied to complex code and UI elements to make sure you are capturing the events and being able to do whatever you need to do without having issues because of the controls blocking or hiding the events from you, even if you are using the Silverlight 2.0 controls.

Happy Silver-coding!!!

-arbbot

No comments:

Post a Comment