You've seen how you to set values for simple and complex properties, and how to use attached properties. This section turns to the topic of events.
While they were redefining properties, the WPF developers also decided to redefine events. In Windows Forms, a control raises an event when something interesting happens to it. For example, when the user clicks a button, the button raises a Click
This system works well when controls are simple and separate, but becomes difficult when controls contain many other controls arranged in complex ways. For example, in Windows Forms a Button can contain text and an image but that's about it. A user click on the button raises the Click
event (and heads off for virtual coffee satisfied at a job well done).
In contrast, many WPF controls can contain just about anything. For example, you can create a Button that contains a Grid that, in turn, contains a set of Image and Label controls. You probably want this conglomeration to behave just like a normal button—raising a single Button Click
event no matter which contained control the mouse is over when it's clicked. And you probably don't want to have to catch the mouse down and mouse up events for each of the Labels and Images and update the appearance of the button accordingly.
Fortunately, routed events handle this situation for you automatically. If you don't do anything foolish to mess things up (which is a lot easier than you might wish), the Button will fire its Click
event no matter which child control the user clicks.
To understand this sleight-of-hand, you need to understand routed events
. WPF sends routed events to all the controls involved with the initiating action (a mouse click in this example). When the action occurs, WPF typically generates two sequences of events: tunneling events
and bubbling events
Tunneling events start at the root of the control hierarchy and tunnel down through the tree toward the control where the event originated. These events typically have names that begin with "Preview," so you can think of them as previews to the actual events if you like. (In fact, "preview events" would probably be a less confusing name than "tunneling events.") Any control along the path from the root to the source control can catch tunneling events and take action.
Next, when the tunneling events finish, WPF raises a series of bubbling events. These events start at the control that caused the original event (the one at the end of the tunneling event chain) and bubble back up to the root of the control hierarchy. Again, any control along the path can catch these events and take action.
Along either the tunneling or bubbling route, if an event handler sets the routed event parameter's Handled
property to true, the event stops right there, with no further tunneling or bubbling. For example, if a control near the top of the control hierarchy sets Handled
to true during the tunneling event, that prevents controls further down the hierarchy from receiving the event. Similarly, if the initiating control handles a bubbling event and
sets Handled to true, no other control will receive the event notification. It's worth noting that controls can take action based on an event without
without interrupting the event chain.
|Figure 6. Exciting Events: The program FollowRoutedEvents displays information about left mouse events when you click on it.|
The sample program FollowRoutedEvents
(see Figure 6
) displays a red Window containing a green Grid with two columns. The right column holds a Label in which it displays output. The left column holds a blue StackPanel containing a white Rectangle.
If you left-click any control in the Grid's left column, the program displays the tunneling and bubbling MouseLeftButtonDown
events in the Label. In Figure 6
, you can see output that occurs when you click on the white Rectangle. The chain is easier to visualize if you recall that the events that start with "Preview" are the tunneling events, while those that don't are the bubbling events.
So, when you press the mouse button down on the Rectangle, the PreviewMouseLeftButtonDown
tunneling events start at the Window and move down to the Rectangle. Then the MouseLeftButtonDown
bubbling events work their way back up from the Rectangle to the Window. In each line of output, the word "Rectangle" indicates the event's originator, in this case the Rectangle that started it all.
After you release the mouse button, the whole sequence starts again for the tunneling PreviewMouseLeftButtonUp
and bubbling MouseLeftButtonUp
If you look in the program's code (which isn't shown here because it's long and boring), you'll find two commented statements that make the program mark tunneling or bubbling events as handled. You can uncomment those statements to see how the chain of events gets interrupted.
Why should you care about all of this? I'm glad you asked. Go back to the hypothetical example of a Button containing a Grid that contains Labels and Images. Using old-style events, you would probably need to catch the Click
events generated by the Button, Grid, Labels, and Images and handle them all separately. With routed events, this is much easier.
When you press the mouse on a control within the Button, the PreviewMouseLeftButtonDown
events begin as before. They tunnel down from the root to the Label or Image that you clicked. As the bubbling events start climbing back up the tree, the Button sees the MouseLeftButtonDown
event. It decides that you have officially pressed the Button down so it captures future mouse events and stops the bubbling event so it travels no further up the tree.
Next when you release the mouse, tunneling events start at the root and work their way down toward the Button. When the Button sees the PreviewMouseLeftButtonUp
event, it decides that you have officially released the button. It marks the event as handled so no further tunneling or bubbling events occur and raises its Click
In other words, no matter what child control gets clicked, the Button can intercept the tunneling and bubbling events to raise its Click
event without you having to handle the Click
event for each child.
|Figure 7. Arrested Events: The Button control intercepts tunneling and bubbling events to provide its own Click event.|
A similar sample program, FollowButtonEvents
has a yellow Button inside its Grid that contains its StackPanel. Again, Figure 7
shows the program displaying the events generated by left-clicking the program's Rectangle, but here it's easy to see how the Button stops the tunneling and bubbling events.
The result is that a WPF Button containing simple objects still behaves as a Button; you don't need to deal with the events raised by the interior objects.
Routed Events in XAML
Connecting a XAML control to the code behind it is easy. If you're already editing the WPF project in Visual Studio, then you're all set.
If you're editing the project in Expression Blend, you should open the code file in Visual Studio. Click the Project tab, right-click on the window for which you want to write code, and select "Edit in Visual Studio."
If you don’t have Visual Studio, you can add the code in an editor such as NotePad#8212;but that's much harder, because NotePad doesn’t provide IntelliSense, syntax highlighting, useful error messages, debugging features, or any of the other tools developers have come to expect in a programming environment. Given that the Visual Studio Express Editions are free, I highly recommend that you install one of them. For more information and to download them, go to the Visual Studio Express Editions Web page
With the project open in VS, you can add events to the project's controls the same way you would for a Windows Forms project. Double-click a control to create an event handler for the control's default event. To code other event handlers, select a control, click the event handler button in the Properties window (the little lightning bolt), and double-click the event you want to create.
When you do this, Visual Studio creates the stub event handler code for the selected event. It also modifies the control's XAML code, adding an attribute that defines the control's event handler. For example, the following code shows how program ChangeBackground
(see Figure 3
) defines its yellow button. The Click=btnYellow_Click
part means that the control's Click
event should be handled by the method named btnYellow_Click
<Button Name="btnYellow" Content="Yellow" Click="btnYellow_Click"
Width="75" Height="23" Margin="12,12,0,0"
VerticalAlignment="Top" HorizontalAlignment="Left" />
Visual Basic handles this a little differently. Rather than adding a Click
attribute to the XAML code, Visual Basic uses the WithEvents
keyword to mark the event handler as handling the event in the Visual Basic code. You can use the Click
attribute instead if you want, but you’ll have to write the XAML code yourself.