WPF Concepts—Routed Events in Action

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
.NET Articles

Important New WPF Concepts

© 2007 Sams Publishing

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>
    <StackPanel 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);
  }
}

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 right-clicking on the Label reveals Source set to the Label but OriginalSource set to its TextBlock visual child.


Image:WPFUnleashed3-7.jpg
Figure 3.7 The modified About dialog, after the first Label is right-clicked.


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.


Previous_Page_.gif Next_Page_.gif




Personal tools