Wednesday, November 01, 2006

WPF Routed Event Model - Part 1

WPF introduces a rich new event model for windows development. In windows forms development, an event is directed to a single object, usually the control that initiated the event. In WPF, events are routed through the Visual Tree, offering far greater flexibility in what we can do with the event and which element or elements can respond to it.

 

To understand the WPF Routed Event Model, we first need to understand how WPF views UI elements and the relationships between them. WPF introduces a new composition model in which UI elements contain other elements. WPF also introduces the the Visual Tree, which represents the hierarchy of UI elements which result from the new composition model. Let’s look at a sample element hierarchy:

 

--Window

---- Grid

------ Canvas

-------- TextBlock

 

As shown above, parent-child relationships are formed between the elements. The Window is a top-level element which contains a Grid, which contains a Canvas, which contains a TextBlock.

 

Let’s create a window in XAML with a few elements and wire-up a simple event which will show a MessageBox to the user when the TextBlock is clicked. Note that TextBlock does not have a Click event, so we’ll use a MouseDown event instead. Utilizing our element hierarchy above the simplified XAML and event wire-up looks like this:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1"

> 

 <Grid Name="grid1">

  <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100">

   <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40"   Canvas.Left="25" MouseDown="textblock1_MouseDown">Press Me!</TextBlock>

  </Canvas>

 </Grid>

</Window>

 

In our code behind we’d place the following event handler:

 

private void textblock1_MouseDown(object sender, RoutedEventArgs e)

{

    MessageBox.Show("TextBlock Clicked");

}

 

On the surface, this is very similar to wiring an event to a Windows Forms control. In our XAML code we have declared a MouseDown event attribute for our TextBlock element and in our code-behind we’ve defined the MouseDown event handler. When we run the example, the MessageBox is displayed as expected when we mouse down on the TextBlock. Not much to it and not very exciting so far but it gets better. Now that we understand how WPF views content (as a hierarchical element tree) and how we can wire an event to an element, we can talk about how WPF works with routed events.

 

The WPF routed event model allows us to take advantage of the fact that our window elements participate in a hierarchy or parent-child relationships. Since WPF knows the relationships, it can ‘route’ the event message up and down the Visual Tree, and all of our elements have an opportunity to handle the message. To clarify a bit, by “all elements”, I mean all elements from the top-level container element (usually Window or Page) down to the element that generated the event.

 

The WPF Event Model defines three types of event routing strategies:

 

Bubbling

The event message traverses the Visual Tree upwards from the element that originated the event to the top-level element (Window or Page)

Tunneling

The event message traverses the Visual Tree downwards from the top-level container (Window or Page) to the target element, which is the element that originated the event.

Direct

Only the element that raised the event can handle the event. Similar to Windows Forms event handling.

 

There is a naming convention for Bubbling and Tunneling event names defined in the WPF framework. Tunneling events are prefixed with ‘Preview”, so using MouseDown as an example, the Bubble event would be named MouseDown, and the tunneling event would be named PreviewMouseDown. Bubble and Tunnel event strategies usually are provided in pairs, when one exists you will usually find the other.

 

Let’s modify our code so we can see the event routing strategies in action. First the XAML:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler"

    >

       <Grid Name="grid1" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">

              <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">

                     <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40" Canvas.Left="25" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">Press Me!</TextBlock>

              </Canvas>

    </Grid>

</Window>

 

We’ve simply added PreviewMouseDown and MouseDown event declarations to each of our elements, and pointed them all at a generic event handler so we can watch what happens. The code for our generic event handler follows:

 

private void GenericRoutedEventHandler(object sender, RoutedEventArgs e)

{

string name = ((FrameworkElement)sender).Name.ToString();

Console.WriteLine(name + " " + e.RoutedEvent.Name.ToString() + " " + e.RoutedEvent.RoutingStrategy.ToString());

}

 

Our generic event handler simply writes to the console the name of the element, the name of the event, and the event strategy. Running the example yields the following results:

 

window1 PreviewMouseDown Tunnel

grid1 PreviewMouseDown Tunnel

canvas1 PreviewMouseDown Tunnel

textblock1 PreviewMouseDown Tunnel

textblock1 MouseDown Bubble

canvas1 MouseDown Bubble

grid1 MouseDown Bubble

window1 MouseDown Bubble

 

In our results we can see that although we clicked the TextBlock, event handlers fired for all of our elements. We ‘tunneled’ from our top level element (Window) down to our target element (TextBlock) and then ‘bubbled’ back up to our top level element again. What’s interesting about this is that all elements are notified of the event occurance and all elements have a chance to handle the event. This is an important concept in WPF because events are not tied to individual controls as in Windows Forms programming, but rather there is an event notification system that alerts all elements in a container that an event occurred, and any element may participate in handling that event.

 

How can we use this? Let’s assume we want our Window container element to show the MessageBox on behalf of any of its contained elements. The message box will show the name of the element that originated the event. (This is just a generic example, in real life we might create a control that contains multiple visual elements, like a border, text, and rectangle, but we want a click event for the entire control.) We can simply add a handler for the window elements MouseDown event and show the message box. The modified XAML follows:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1" MouseDown="window1_MouseDown"

    >

       <Grid Name="grid1">

              <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100">

                     <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40" Canvas.Left="25">Press Me!</TextBlock>

              </Canvas>

    </Grid>

</Window>

 

And our modified code-behind looks like so:

 

private void window1_MouseDown(object sender, RoutedEventArgs e)

{

  string name = ((FrameworkElement)e.OriginalSource).Name.ToString();

  MessageBox.Show(name);

}

 

And there you have it, a quick intro to WPF event routing. In the interest of keeping this post simple, I purposely avoided the use of a Button element as there are more details to the routing model base classes that have an effect on the Button element. I will address that soon in part 2.

 

You can read more about events on the  MSDN

 

 

 

 

 

 




Technorati Tags:
, , , , ,

1 comment:

Depora said...

thnx for this post. i want to create a WPF diagram designer and i find very hard to do it with routed events. your tut make clear some things about event handling in WPF.