Routing Events in WPF

In this third part of a four-part series on the concepts you need to understand to work with Windows Presentation Foundation, you'll learn all about routing events. This article is excerpted from chapter three of the book Windows Presentation Foundation Unleashed, written by Adam Nathan (Sam's Publishing; ISBN: 0672328917).

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 2
January 25, 2010
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

A Routed Event Implementation

In most cases, routed events don’t look very different from normal .NET events. As with dependency properties, no .NET languages (other than XAML) have an intrinsic understanding of the routed designation. The extra support is based on a handful of WPF APIs.

Listing 3.6 demonstrates how Button effectively implements its Click routed event. (Click is actually implemented by Button’s base class, but that’s not important for this discussion.)

Just as dependency properties are represented as public static DependencyProperty fields with a conventional Property suffix, routed events are represented as public static RoutedEvent fields with a conventional Event suffix. The routed event is registered much like a dependency property in the static constructor, and a normal .NET event—or event wrapper—is defined to enable more familiar use from procedural code and adding a handler in XAML with event attribute syntax. As with a property wrapper, an event wrapper must not do anything in its accessors other than call AddHandler and RemoveHandler.

LISTING 3.6   A Standard Routed Event Implementation

public class Button : ButtonBase
{
  // The routed event
 
public static readonly RoutedEvent ClickEvent;

  static Button()
  {
   
// Register the event
    Button.ClickEvent = EventManager.RegisterRoutedEvent("Click",
      RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button));
    ...
  }

  // A .NET event wrapper (optional)
  public event RoutedEventHandler Click
  {
    add { AddHandler(Button.ClickEvent, value); }
    remove { RemoveHandler(Button.ClickEvent, value); }
  }

  protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  {
    ...
    // Raise the event
    RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
    …
  }
  …
}

These AddHandler and RemoveHandler methods are not inherited from DependencyObject, but rather System.Windows.UIElement, a higher-level base class of elements such as Button. (This class hierarchy is examined in more depth at the end of this chapter.) These methods attach and remove a delegate to the appropriate routed event. Inside OnMouseLeftButtonDown, RaiseEvent (also defined on the base UIElement class) is called with the appropriate RoutedEvent field to raise the Click event. The current Button instance (this) is passed as the source element of the event. It’s not shown in this listing, but Button’s Click event is also raised in response to a KeyDown event to support clicking with the spacebar or sometimes the Enter key.

Routing Strategies and Event Handlers

 

When registered, every routed event chooses one of three routing strategies—the way in which the event raising travels through the element tree. These strategies are exposed as values of a RoutingStrategy enumeration:

  • Tunneling—The event is first raised on the root, then on each element down the tree until the source element is reached (or until a handler halts the tunneling by marking the event as handled). 
  • Bubbling—The event is first raised on the source element, then on each element up the tree until the root is reached (or until a handler halts the bubbling by marking the event as handled). 
  • Direct—The event is only raised on the source element. This is the same behavior as a plain .NET event, except that such events can still participate in mechanisms specific to routed events such as event triggers.

Handlers for routed events have a signature matching the pattern for general .NET event handlers: The first parameter is a System.Object typically named sender, and the second parameter (typically named e) is a class that derives from System.EventArgs. The sender parameter passed to a handler is always the element to which the handler was attached. The e parameter is (or derives from) an instance of RoutedEventArgs, a subclass of EventArgs that exposes four useful properties:

  • Source—The element in the logical tree that originally raised the event. 
  • OriginalSource—The element in the visual tree that originally raised the event (for example, the TextBlock or ButtonChrome child of a standard Button). 
  • Handled—A Boolean that can be set to true to mark the event as handled. This is precisely what halts any tunneling or bubbling. 
  • RoutedEvent—The actual routed event object (such as Button.ClickEvent), which can be helpful for identifying the raised event when the same handler is used for multiple routed events.

The presence of both Source and OriginalSource enable you to work with the higher-level logical tree or the lower-level visual tree. This distinction only applies to physical events like mouse events, however. For more abstract events that don’t necessarily have a direct relationship with an element in the visual tree (like Click due to its keyboard support), the same object is passed for both Source and OriginalSource.

Routed Events in Action

 

The UIElement class defines many routed events for keyboard, mouse, and stylus input. Most of these are bubbling events, but many of them are paired with a tunneling event. Tunneling events can be easily identified because, by convention, they are named with a Preview prefix. These events, also by convention, are raised immediately before their bubbling counterpart. For example, PreviewMouseMove is a tunneling event raised before the MouseMove bubbling event.


DIGGING DEEPER

Using Stylus Events

A stylus--the pen-like device used by Tablet PCs--acts like a mouse by default. In other words, its use raises events such as MouseMove, MouseDown, and MouseUp. This behavior is essential for a stylus to be usable with programs that aren’t designed specifically for a Tablet PC. However, if you want to provide an experience that is optimized for a stylus, you can handle stylus-specific events such as StylusMove, StylusDown, and StylusUp. A stylus can do more "tricks" than a mouse, as evidenced by some of its events that have no mouse counterpart, such as StylusInAirMove,
StylusSystemGesture, StylusInRange, and StylusOutOfRange. There are other ways to exploit a stylus without handling these events directly, however. The next chapter, "Introducing WPF’s Controls," shows how this can be done with a powerful InkCanvas element.


The idea behind having a pair of events for various activities is to give elements a chance to effectively cancel or otherwise modify an event that’s about to occur. By convention, WPF’s built-in elements only take action in response to a bubbling event (when a bubbling and tunneling pair is defined), ensuring that the tunneling event lives up to its “preview” name. For example, imagine you want to implement a TextBox that restricts its input to a certain pattern or regular expression (such as a phone number or zip code). If you handle TextBox’s KeyDown event, the best you can do is remove text that has already been displayed inside the TextBox. But if you handle TextBox’s PreviewKeyDown event instead, you can mark it as “handled” to not only stop the tunneling but also stop the bubbling KeyDown event from being raised. In this case, the TextBox will never receive the KeyDown notification and the current character will not get displayed.

To demonstrate the use of a simple bubbling event, Listing 3.7 updates the original About dialog from Listing 3.1 by attaching an event handler to Window’s MouseRightButtonDown event. Listing 3.8 contains the C# code-behind file with the event handler implementation.

LISTING 3.7  The About Dialog with an Event Handler on the Root Window

<Window  
xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation
xmlns:x="http://schemas.microsoft.com/ winfx/2006/xaml"
 
x:Class="AboutDialog" MouseRightButtonDown= "AboutDialog_MouseRightButtonDown"
  Title="About WPF Unleashed" SizeToContent="WidthAndHeight"
  Background="OrangeRed">
  <StackPanel>
    <Label FontWeight="Bold" FontSize="20" Foreground="White">
      WPF Unleashed (Version 3.0)
   
</Label>
    <Label>© 2006 SAMS Publishing</Label>
    <Label>Installed Chapters:</Label>
    <ListBox>
      <ListBoxItem>Chapter 1</ListBoxItem>
      <ListBoxItem>Chapter 2</ListBoxItem>
    </ListBox>
    <StackPanell Orientation="Horizontal" HorizontalAlignment="Center">
      <Button MinWidth="75" Margin="10">Help</Button>
      <Button MinWidth="75" Margin="10">OK</Button>
    </StackPanel>
   <StatusBar>You have successfully registered this product.</StatusBar>
  </StackPanel>
</Window>

LISTING 3.8  The Code-Behind File for Listing 3.7

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;

public partial class AboutDialog : Window
{
  public AboutDialog()
  {
    InitializeComponent();
  }

  void AboutDialog_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
  {
    // Display information about this event
    this.Title = "Source = " + e.Source.GetType().Name + ", OriginalSource = " +
     
e.OriginalSource.GetType().Name + " @ “ + e.Timestamp;

    // In this example, all possible sources derive from Control
    Control source = e.Source as Control;

    // Toggle the border on the source control
    if (source.BorderThickness != new Thickness(5))
    {
      source.BorderThickness = new Thickness(5);
      source.BorderBrush = Brushes.Black;
    }
    else
     
source.BorderThickness = new Thickness(0);
  }
}

Routed Events continued

 

The AboutDialog_MouseRightButtonDown handler performs two actions whenever a right-click bubbles up to the Window: It prints information about the event to the Window’s title bar, and it adds (then subsequently removes) a thick black border around the specific element in the logical tree that was right-clicked. Figure 3.7 shows the result. Notice that


Figure 3.7.   The modified About dialog, after the first Label is right-clicked.

right-clicking on the Label reveals Source set to the Label but OriginalSource set to its TextBlock visual child.

If you run this example and right-click on everything, you’ll notice two interesting behaviors:

  • Window never receives the MouseRightButtonDown event when you right-click on either ListBoxItem. That’s because ListBoxItem internally handles this event as well as the MouseLeftButtonDown event (halting the bubbling) to implement item selection. 
  • Window receives the MouseRightButtonDown event when you right-click on a Button, but setting Button’s Border property has no visual effect. This is due to Button’s default visual tree, which was shown back in Figure 3.3. Unlike Window, Label, ListBox, ListBoxItem, and StatusBar, the visual tree for Button has no Border element.

 


 

FAQ

?  Where is the event for handling the pressing of a mouse's middle button?

If you browse through the various mouse events exposed by UIElement or ContentElement, you’ll find events for MouseLeftButtonDown, MouseLeftButtonUp, MouseRightButtonDown, and MouseRightButtonUp (as well as the tunneling Preview version of each event). But what about the additional buttons present on some mice?

This information can be retrieved via the more generic MouseDown and MouseUp events (which also have Preview counterparts). The arguments passed to such event handlers include a MouseButton enumeration that indicates which button’s state just changed: Left, Right, Middle, XButton1, or XButton2. A corresponding MouseButtonState enumeration indicates whether that button is Pressed or Released.


DIGGING DEEPER

Halting a Routed Event Is an Illusion

Although setting the RoutedEventArgs parameter’s Handled property to true in a routed event handler appears to stop the tunneling or bubbling, individual handlers further up or down the tree can opt to receive the events anyway! This can only be done from procedural code, using an overload of AddHandler that adds a Boolean handledEventsToo parameter.

For example, the event attribute could be removed from Listing 3.7 and replaced with the following AddHandler call in AboutDialog’s constructor:

public AboutDialog()
{
  InitializeComponent();
  this.AddHandler(Window.MouseRightButtonDownEvent,
    new MouseButtonEventHandler(AboutDialog_MouseRightButtonDown), true);
}

With true passed as a third parameter, AboutDialog_MouseRightButtonDown now receives events when you right-click on a ListBoxItem, and can add the black border!

You should avoid processing handled events whenever possible, because there is likely a reason the event is handled in the first place. Attaching a handler to the Preview version of an event is the preferred alternative.

The bottom line, however, is that the halting of tunneling or bubbling is really just an illusion. It’s more correct to say that tunneling and bubbling still continue when a routed event is marked as handled, but that event handlers only see unhandled events by default.


 

blog comments powered by Disqus
BRAINDUMP ARTICLES

- Microsoft Windows 8 Committed to Cloud Compu...
- Independent Developers Favor Windows Phone 7
- Dell Introduces VMware-based Cloud
- Microsoft and Skype Agree to Acquisition Deal
- Transfer Contacts in Microsoft Outlook
- Zune`s Next Steps
- Safari Books Online Review
- Does Microsoft Get Touch Screens Now?
- Microsoft`s Record Quarterly Earnings Not En...
- Basic Operations and Registers in Assembly
- Assembly Coding within Visual C/C++ IDE
- New Microsoft Office Coming with a Twist
- Microsoft`s FUSE Labs Unveils Spindex Social...
- HP Slate with Windows 7: Dead or Alive?
- Windows Phone 7 Mobile OS to Rival Android a...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 3 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials