Routed Events, WPF Tree Structures, Event Sequence for Beginners

Introduction

As you perhaps know, a native CLR event is fired whenever a user action is initiated on a Control object and the specified control’s event handler only gets triggered. No other controls are affected by this event. E.g., any event that you can think of in WPF is essentially a CLR event.

On the contrary, a routed event is a type of event that can invoke handlers on multiple listeners in an element tree rather than just the object that raised the event. It is basically a CLR event that is supported by an instance of the Routed Event class. It is registered with the WPF event system.

To explain routed events in details, let us take an example of a WPF UI window, where there are a few nested controls.

  • Window
    • StackPanel 1
      • StackPanel 2
        • Textbox 1
        • Textbox 2
      • Button

I hope the above structure is clear to you. Please keep this UI structure in your mind, we will refer to it as and when required.

Now, we will introduce WPF tree structures briefly for your benefit.

Tree Structures

To create and identify the relationship between UI elements in a WPF application, there are two object tree structures.

  1. Logical Tree Structure
    The structure of the UI elements in XAML represents the logical tree structure. In the above WPF UI window, if you look at the XAML code, you will see the logical tree structure.

    WPF

  2. Visual Tree Structure
    Describes the structure of visual objects, as represented by the Visual Base Class. It signifies all the UI elements which are rendered to the output screen. It is used to render the visual objects and the layouts. The routed events mostly travel along the visual tree, not the logical tree. If you compile and run the XAML, you will see the Live Visual Tree in the Visual Studio. The visual tree is typically a superset of the logical tree with additional information on visual formatting options.

    WPF

Keeping this in mind, we will dig deep into the routing strategies.

Routed Events

Routed Events have three main routing strategies which are as follows,

  • Direct Event
  • Bubbling Event
  • Tunneling Event

Direct Event

A direct event is similar to events in Windows forms which are raised by the element in which the event is originated.

Unlike a standard CLR event, direct routed events support class handling and they can be used in Event Setters and Event Triggers within your style of your Custom Control.

A good example of a direct event would be the MouseEnter event.

Bubbling Event

A bubbling event begins with the element where the event is originated. Then it travels up the visual tree to the topmost element in the visual tree. As you probably have guessed, in WPF, the topmost element is most likely a window.

Now coming to the example that I set earlier, if you click the Button, then the Bubbling event shall move in this direction,

Button > Stackpanel 1 > Window

If you click any of the Textboxes, then the event route will be,

Textbox 1 or 2 > Stackpanel 2 > Stackpanel 1> Window

Tunnel Event

The direction of Tunneling event is opposite to the Bubbling event. Here the event handlers on the element tree root are invoked and then the event travels down the visual tree to all the children nodes until it reaches the element in which the event originated.

The difference between a bubbling and a tunneling event (apart from the direction) is that a tunneling event name will always start with a ‘preview’.

In a WPF application, events are often implemented as a tunneling/bubbling pair. So, you'll have a previewMouseDown and then a MouseDown event.

Event Sequence

Now let’s clear your concept a bit more with the proper event chain.

A standard Button control is derived from ButtonBase, and so it inherits a Click event that fires when a user clicks on the button. As ButtonBase inherits from UIElement, a Button will also have access to all of the mouse button events (like MouseLeftButtonDown and MouseDown) defined for UIElement. But as the Button does something in response to button presses, it swallows the bubbling events (e.g. MouseLeftButtonDown and MouseDown). In general, controls that do something as the result of a user pressing a mouse button will swallow the related events. Clicking on a TextBox gives it focus. Clicking on a Button or ComboBox also results in the control responding to the click, so these controls also swallow the non-preview events. Remember, these types of controls only fire the Tunneling events.

E.g. When a user clicks on a button, there are a series of preview events (tunneling) and actual events (bubbling) that travel up and down the logical tree.

For a left-click on a Button, you’ll normally see click-related events in the following order,

  • PreviewMouseLeftButtonDown (Tunnel)
  • PreviewMouseDown (Tunnel)
  • PreviewMouseLeftButtonUp (Tunnel)
  • PreviewMouseUp (Tunnel)
  • Click (Bubble)

But for a Label contained in a StackPanel, which is contained in a Window, the full sequence of events for a left mouse button click on the Label is,

  • PreviewMouseLeftButtonDown for Window (Tunnel)
  • PreviewMouseDown for Window (Tunnel)
  • PreviewMouseLeftButtonDown for StackPanel (Tunnel)
  • PreviewMouseDown for StackPanel (Tunnel)
  • PreviewMouseLeftButtonDown for Label (Tunnel)
  • PreviewMouseDown for Label (Tunnel)
  • MouseLeftButtonDown for Label (Bubble)
  • MouseDown for Label (Bubble)
  • MouseLeftButtonDown for StackPanel (Bubble)
  • MouseDown for StackPanel (Bubble)
  • MouseLeftButtonDown for Window (Bubble)
  • MouseDown for Window (Bubble)
  • PreviewMouseLeftButtonUp for Window (Tunnel)
  • PreviewMouseUp for Window (Tunnel)
  • PreviewMouseLeftButtonUp for StackPanel (Tunnel)
  • PreviewMouseUp for StackPanel (Tunnel)
  • PreviewMouseLeftButtonUp for Label (Tunnel)
  • PreviewMouseUp for Label (Tunnel)
  • MouseLeftButtonUp for Label (Bubble)
  • MouseUp for Label (Bubble)
  • MouseLeftButtonUp for Stackpanel (Bubble)
  • MouseUp for StackPanel (Bubble)
  • MouseLeftButtonUp for Window (Bubble)
  • MouseUp for Window (Bubble)

Now get ready for some hands-on demo of this beautiful feature of routed events. In this example I will show you only Bubble events, but don’t worry! Very soon I will post few more examples on Tunneling events as well.

Many of the easy tutorials available on the internet provide examples based on buttons. It is very basic and easy and it hides the important aspects of using the Routed events. So, I have decided to use controls other than buttons (more specifically TextBlock!) in order to properly indicate the features of routed events.

Hands-on Demo

Here I will create a custom routed event. You need to follow the steps given below to define a custom routed event in C#.

  • Declare and register your routed event with system call RegisterRoutedEvent.
  • Specify the Routing Strategy, i.e. Bubble, Tunnel, or Direct.
  • Provide the event handler.

Follow the steps given below,

  • Create a new WPF project called WpfCustomRoutedEventTutorial.
  • Right-click on your solution and select Add > New Item... In the following dialog, select Custom Control (WPF) and name it MyCustomControl.
  • Click the Add button and you will see that two new files (Themes/Generic.xaml and MyCustomControl.cs) will be added to your solution.

The following XAML code sets the style for the custom control in Generic.xaml file.

  1. <ResourceDictionary  
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.     xmlns:local="clr-namespace:WpfCustomRoutedEventTutorial">  
  5.   
  6.   
  7.     <Style TargetType="{x:Type local:MyCustomControl}">  
  8.         <Setter Property="Template">  
  9.             <Setter.Value>  
  10.                 <ControlTemplate TargetType="{x:Type local:MyCustomControl}">  
  11.                     <Border Background="{TemplateBinding Background}"  
  12.                             BorderBrush="{TemplateBinding BorderBrush}"  
  13.                             BorderThickness="{TemplateBinding BorderThickness}">  
  14.                         <TextBlock x:Name = "tbCustomControl" Text = "I am Custom Textblock. Click or Double-click Me!" />  
  15.                     </Border>  
  16.                 </ControlTemplate>  
  17.             </Setter.Value>  
  18.         </Setter>  
  19.     </Style>  
  20. </ResourceDictionary>  

Given below is the C# code for the MyCustomControl class which inherits from the Control class in which a custom routed event Click is created for the custom control. The code is heavily commented in order to spoon-feed you with the logic. Don’t worry.

  1. using System.Windows;  
  2. using System.Windows.Controls;  
  3.   
  4. namespace WpfCustomRoutedEventTutorial  
  5. {  
  6.     public class MyCustomControl : Control  
  7.     {  
  8.         static MyCustomControl()  
  9.         {  
  10.             DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));  
  11.         }  
  12.   
  13.         /* Assign event handler to the Routed events here. Templates are the section of an element's  
  14.         * completed visual tree that comes from the Template property of a Style that is applied to the element.  
  15.         */  
  16.         public override void OnApplyTemplate()  
  17.         {  
  18.             base.OnApplyTemplate();  
  19.   
  20.             var custextblock = GetTemplateChild("tbCustomControl") as TextBlock;  
  21.             if (custextblock != null)  
  22.             {  
  23.                 /* Assign mouse wheel event handler to the mouse wheel event. This is fired 
  24.                  * when the user rotates the mouse wheel while the cursor is on the control. 
  25.                  * */  
  26.                 custextblock.MouseWheel += Custom_MouseWheel;  
  27.                 /* Assign mouse down event handler to the mouse down event. This is fired 
  28.                  * when the user clicks any mouse button while the cursor is on the control. 
  29.                  * */  
  30.                 custextblock.MouseDown += Custom_MouseClick;  
  31.             }  
  32.         }  
  33.   
  34.         /* Event handler for mouse click */  
  35.         private void Custom_MouseClick(object sender, System.Windows.Input.MouseButtonEventArgs e)  
  36.         {  
  37.             RaiseMouseClickEvent();  
  38.         }  
  39.   
  40.         /* Event handler for mouse wheel rotate */  
  41.         void Custom_MouseWheel(object sender, RoutedEventArgs e)  
  42.         {  
  43.             RaiseMouseWheelEvent();  
  44.         }  
  45.   
  46.   
  47.         /***************** 1ST ROUTED EVENT ******************/  
  48.         /* Now we will create a custom routed event called CustomWheelEvent. The name of the event is MyCustomWheelRotate 
  49.          * Use bubbling strategy, handler type is RoutedEventHandler, Owner type is MyCustomControl. 
  50.          */  
  51.         public static readonly RoutedEvent CustomWheelEvent =  
  52.          EventManager.RegisterRoutedEvent("MyCustomWheelRotate", RoutingStrategy.Bubble,  
  53.          typeof(RoutedEventHandler), typeof(MyCustomControl));  
  54.   
  55.         /* Just like Dependency Properties, routed events are also like wrapper over underlying RoutedEvent instance 
  56.          * and they wrap through a set of getter-setter methods. 
  57.          */  
  58.         public event RoutedEventHandler MyCustomWheelRotate  
  59.         {  
  60.             add { AddHandler(CustomWheelEvent, value); }  
  61.             remove { RemoveHandler(CustomWheelEvent, value); }  
  62.         }  
  63.   
  64.   
  65.         /***************** 2ND ROUTED EVENT ******************/  
  66.         /* Now we will create another custom routed event called CustomClickEvent. The name of the event is MyCustomClick 
  67.          * Use bubbling strategy, handler type is RoutedEventHandler, Owner type is MyCustomControl. 
  68.          */  
  69.         public static readonly RoutedEvent CustomClickEvent =  
  70.          EventManager.RegisterRoutedEvent("MyCustomClick", RoutingStrategy.Bubble,  
  71.          typeof(RoutedEventHandler), typeof(MyCustomControl));  
  72.   
  73.         /* Just like Dependency Properties, routed events are also like wrapper over underlying RoutedEvent instance 
  74.          * and they wrap through a set of getter-setter methods. 
  75.          */  
  76.         public event RoutedEventHandler MyCustomClick  
  77.         {  
  78.             add { AddHandler(CustomClickEvent, value); }  
  79.             remove { RemoveHandler(CustomClickEvent, value); }  
  80.         }  
  81.   
  82.   
  83.         /* Raise the Mouse wheel routed event that travels through the element tree. 
  84.          */  
  85.         protected virtual void RaiseMouseWheelEvent()  
  86.         {  
  87.             RoutedEventArgs args = new RoutedEventArgs(CustomWheelEvent);  
  88.             RaiseEvent(args);  
  89.         }  
  90.         /* Raise the Mouse click routed event that travels through the element tree. 
  91.          */  
  92.         protected virtual void RaiseMouseClickEvent()  
  93.         {  
  94.             RoutedEventArgs args = new RoutedEventArgs(CustomClickEvent);  
  95.             RaiseEvent(args);  
  96.         }  
  97.     }  
  98. }  

If you are clear up to here, you are more or less through. Now, only the client-side logic remains, that is, after you create your own custom routed event on a custom control, you want to use this custom control in a WPF application, right?

So, here it is.

Here is the implementation in MainWindow.xaml to add your own custom control with two routed events.

  1. <Window x:Class="WpfCustomRoutedEventTutorial.MainWindow"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
  6.         xmlns:local="clr-namespace:WpfCustomRoutedEventTutorial"  
  7.         mc:Ignorable="d"  
  8.         Title="MainWindow" Height="350" Width="525" MouseDown="Window_Click">  
  9.     <Grid>  
  10.         <StackPanel Margin = "20" MouseDown="StackPanel_Click">  
  11.             <StackPanel Margin = "10">  
  12.                 <TextBlock Name = "txt1" FontSize = "18" Margin = "5" Text = "This is a TextBlock 1" />  
  13.                 <TextBlock Name = "txt2" FontSize = "18" Margin = "5" Text = "This is a TextBlock 2" />  
  14.                 <TextBlock Name = "txt3" FontSize = "18" Margin = "5" Text = "This is a TextBlock 3" />  
  15.             </StackPanel>  
  16.             <local:MyCustomControl MyCustomWheelRotate="MyCustomControl_MouseWheel" MyCustomClick="MyCustomControl_MouseClick" />  
  17.         </StackPanel>          
  18.     </Grid>  
  19. </Window>  

Here is the custom routed event implementation in C# which will change the text of the three textboxes depending upon which event you fired.

  1. using System.Windows;  
  2.   
  3. namespace WpfCustomRoutedEventTutorial  
  4. {  
  5.     /// <summary>  
  6.     /// Interaction logic for MainWindow.xaml  
  7.     /// </summary>  
  8.     public partial class MainWindow : Window  
  9.     {  
  10.         public MainWindow()  
  11.         {  
  12.             InitializeComponent();  
  13.         }  
  14.   
  15.         /* The custom routed event implementation which will display a message box when the user clicks it. 
  16.          */  
  17.         private void MyCustomControl_MouseWheel(object sender, RoutedEventArgs e)  
  18.         {  
  19.             txt1.Text = "Wheel rotated! It is the custom routed event of your custom control";  
  20.         }  
  21.   
  22.         private void MyCustomControl_MouseClick(object sender, RoutedEventArgs e)  
  23.         {  
  24.             txt1.Text = "Clicked! It is the custom routed event of your custom control";  
  25.         }  
  26.   
  27.         /* The event will be routed to its parent which is a stackpanel. 
  28.          */  
  29.         private void StackPanel_Click(object sender, RoutedEventArgs e)  
  30.         {  
  31.             txt2.Text = "Only the Click event is bubbled to Stack Panel";  
  32.         }  
  33.   
  34.         /* The event will then again be routed to its parent which is the window. 
  35.          */  
  36.         private void Window_Click(object sender, RoutedEventArgs e)  
  37.         {  
  38.             txt3.Text = "Only the Click event is bubbled to Window";  
  39.         }  
  40.     }  
  41. }  

When the above code is compiled and executed, it will produce the following window which contains a custom control.

WPF

Now you point to the Textblock and rotate your mouse wheel. You will see this. The red colored text is only to indicate (it's not part of the output).

WPF

Now, if you single-click on the Textblock, you will see the following.

WPF

Interesting! Isn’t it?

When you single-click on the Textblock, the MouseEnter event is fired. In this case, the Routed event originated from the TextBlock and moved in the direction,

TextBlock > StackPanel > Window.

So, each of these control’s event handlers are triggered and the textboxes text are changed. So far so good.

But why doesn't the Routed event bubble up when Mouse Wheel is rotated??

To understand this, you need to know why Routed event bubbled up when MouseEnter event fired in the first case.

If you notice in MainWindow.xaml file, you will see that I have set the MouseDown event in both the parent controls i.e. Window and StackPanel. The MyCustomClick event of the MyCustomControl is the Routed Event which is raised when Custom_MouseClick() handler is triggered, which is again when the MouseDown event is raised. So, it is evident that the same event is routed up along the Visual tree. If it’s a MouseDown event, then only the MouseDown event will bubble up and hence, only the MouseDown handlers of all the parent Controls will be triggered.

In the Mouse Wheel event case, you will notice that it’s the MouseWheel event that is getting fired in the Custom Control, but no MouseWheel event handler is set in the parent controls like StackPanel and Window. Hence this MouseWheel event is not handled anywhere except the originating control which is TextBlock because it already has defined Mouse Wheel event handler.

You can in fact set the Mouse wheel event handler in both StackPanel and Window controls and check for yourself the real fun. It's your homework folks!!! ๐Ÿ˜Š

If at any point in time, you want to stop the routed event at any particular level, then you will need to set the e.Handled = true;

Let’s change the StackPanel_Click event as shown below,

  1. private void StackPanel_Click(object sender, RoutedEventArgs e)  
  2.         {  
  3.             txt2.Text = "Only the Click event is bubbled to Stack Panel";  
  4.             e.Handled = true;  
  5.         }  

When you click on the TextBlock, you will observe that the click event will not be routed to the Window and will stop at the StackPanel and the third text block will not be updated.

WPF

Do write to me if you have any questions.