WPF Concepts—Routed Events in Action
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
| CSharp-Online.NET:Articles |
| .NET Articles |
| © 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.

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:
-
Windownever receives theMouseRightButtonDownevent when you right-click on eitherListBoxItem. That's becauseListBoxIteminternally handles this event as well as theMouseLeftButtonDownevent (halting the bubbling) to implement item selection.
-
-
Windowreceives theMouseRightButtonDownevent when you right-click on aButton, but settingButton'sBorderproperty has no visual effect. This is due toButton's default visual tree, which was shown back in Figure 3.3. UnlikeWindow,Label,ListBox,ListBoxItem, andStatusBar, the visual tree forButtonhas noBorderelement.
-
| 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 |
| 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 public AboutDialog() { InitializeComponent(); this.AddHandler(Window.MouseRightButtonDownEvent, new MouseButtonEventHandler(AboutDialog_MouseRightButtonDown), true); } With true passed as a third parameter, 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 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. |
|

