hen Microsoft’s developers invented WPF, they made some fundamental changes in the way things work. Two of those changes redefine the fundamental nature of properties and events.
To replace the existing properties and events that we all know and love, WPF introduced dependency properties and routed events. This article provides an introduction to dependency properties and routed events, and explains how you can set properties and intercept events in XAML.
But first things first. Before you can set a dependency property’s value, you need to know what one is.
Dependency Properties
A dependency property is a special kind of property that is registered with the WPF property system. Dependency properties support features that normal properties do not, such as default values, inheritance, WPF-style data binding, and property change notification.
For example, the InheritedProperties sample program shown in Figure 1 (available in the downloadable code in both C# and Visual Basic versions) uses the following code to display a StackPanel that holds three Labels, a ListBox, and a Button:
? | |
Figure 1. Contested Inheritance: A control generally inherits properties from its container, although some (such as ListBox and Button) do not inherit properties such as Background. |
The Window in Figure 1 defines font properties and a background color (orange). The StackPanel and the first Label control (“Inherited Font”) do not define any font or background properties so they inherit both from their container?the Window. The second Label (“Overridden Font”) does set FontFamily and FontSize properties, so these override the inherited values, but it keeps the inherited background color.
The third label in Figure 1 (“Overridden Background”) does the opposite; it overrides the inherited Background property , but keeps the inherited font properties.
Some controls don’t follow the normal inheritance mechanism, however. The ListBox control in Figure 1 does not honor its parent’s Background property setting, although it does inherit font properties.
Author’s Note: I think the ListBox doesn’t inherit the Background property because it gives selected items a special highlighted background that might not look good with an inherited Background. However, you can still alter the default ListBox background color by setting its Background property explicitly so the problem is still possible; you just have to take an extra step to get into trouble. |
In Figure 1, The ListBox’s first two items inherit font and background properties from the ListBox that contains them, although the third item overrides its background value.
Like the ListBox, the Button does not inherit its Background setting from its parent, although it does inherit font properties.
Dependency properties provide for considerably more complex inheritance than this example demonstrates. For example, if a control has a Style that sets its Background value, the Style background value takes precedence over an inherited value, but can be superseded by any value set explicitly on the control.
All this inheritance, with exceptions and style overrides, is provided by dependency properties. You’ll see more about dependency properties in future articles, particularly about using them for data binding and change notification. For now, you need to know how to set simple and complex property values in program code or XAML.
Simple Properties
You can set a WPF object’s properties using code at run time much as you set any other object’s properties. To do that, simply build a value of the appropriate type and assign it to the property.
For example, the following C# code sets the Background property of the Grid control named grdMain to a solid yellow brush.
// Give the window a solid yellow background. private void btnYellow_Click(object sender, RoutedEventArgs e) { grdMain.Background = Brushes.Yellow; }
From a coding perspective, this all looks perfectly normal (after you figure out what kinds of objects serve what purposes), but from a XAML perspective it’s a bit problematic. XAML files are text files: How do you set a property to an object such as a solid brush?
The answer is that WPF provides type converters that can translate certain textual values into objects for use with some properties. For example, the following XAML code sets the grdMain control’s Background attribute to the string “Yellow.” The type converter for the Background attribute automatically converts the string “Yellow” into a solid yellow brush and assigns it to the control’s Background property:
...
This technique, where you set a property’s value in an XAML attribute, is called attribute syntax or property attribute syntax. It makes good intuitive sense and it’s easy to use to set simple property values such as this one.
WPF provides many other type converters to turn textual attributes into values such as doubles, colors, point coordinates, enumerated values, and sometimes even arrays of numbers.
? | |
Figure 2. Practical Properties: XAML uses type converters to convert simple text values (such as “Blue” and “5”) into the appropriate data types (a Color and a double, respectively, in this case). |
The sample program SampleProperties uses the following XAML fragment to draw a Polygon object:
The various attributes set the object’s StrokeThickness (a double, the line’s thickness), Stroke (the line’s color), Points (an array of coordinate pairs, which is converted into an array of Points), StrokeDashArray (an array of drawn and skipped lengths for dashed lines), and StrokeDashCap (an enumerated value that can be Flat, Square, Round, or Triangle, which determines how WPF draws the ends of dashes). Figure 2 shows the result.
Complex Properties
The simple properties you’ve seen are easy enough to use in XAML but how do you handle something more exotic? For example, suppose you want to use a radial gradient brush similar to the one shown in Figure 3 for a background.
? | |
Figure 3. Baffling Backgrounds: Setting the Background property to a radial gradient brush is easy at run time but messy in XAML code. |
It’s easy enough to specify this background in code at run time. The following C# code creates a GradientStopCollection to store the values where the gradient should reach different colors. It uses that collection to create a new RadialGradientBrush and then assigns it to the Grid’s Background property.
// Give the window a blue gradient background. private void btnBlue_Click(object sender, RoutedEventArgs e) { GradientStopCollection stops = new GradientStopCollection { new GradientStop(Colors.White, 0.00), new GradientStop(Colors.Blue, 0.25), new GradientStop(Colors.White, 0.50), new GradientStop(Colors.Blue, 0.75), new GradientStop(Colors.White, 1.00) }; RadialGradientBrush bg = new RadialGradientBrush(stops); grdMain.Background = bg; }
As you can see, this example is a little more complicated than the earlier one that set the Grid’s Background property to a solid yellow brush, but the idea’s the same: You create an appropriate brush object and assign it to the Grid’s Background property.
But how do you do this in XAML? The type converter for the Background attribute only handles simple solid brushes with names such as “Blue,” “Pink,” or “PapayaWhip.” (Hey, I didn’t name it!) You might be able to dream up a string-based syntax to let you specify radial gradient brushes, and have those convert to gradient brush objects at run time, but it would probably be pretty messy and hard to type correctly. You’d also probably want to handle linear gradient brushes and image brushes, as well as any properties that those objects have.
Building such a type converter is possible but Microsoft took a different approach. Instead of using a complex string converter, a XAML file can use property element syntax to represent properties that are themselves complex objects. Using this method, the property is represented as an XML element in the file.
To give a control such a property, don’t use the property attribute syntax. Instead, after the control’s start token, create an element whose name is a concatenation of the control type, and the property’s name, separated by a period, for example,
The following XAML code gives the Grid grdMain the background shown in Figure 3. The
...
Because this technique uses a XAML element to describe the property’s value, it is called property element syntax.
Attached Properties
Using property attribute syntax and property element syntax, you can assign either simple or complex values to an object’s properties. XAML also includes a third kind of property, called an attached property. Classes provide an attached properties for use by other classes.
Author’s Note: Attached properties are somewhat similar to the extender provider properties provided by Windows Forms components such as ErrorProvider, HelpProvider, and ToolTip. In Windows Forms, if you add a ToolTip named ToolTip1 to a form, every control on the form (and the form itself) magically acquires a “ToolTip on ToolTip1” property (courtesy of ToolTip1). |
A common use of attached properties is for communication purposes?to allow a class’s children to tell it something. For example, the Grid control can define rows and columns to hold controls. When you place an Image inside the Grid, you might want to set its Row and Column properties. But the Image control doesn’t have those properties. (It would be silly to give every kind of control those properties on the off chance that it was placed inside a Grid.) To handle this problem, the Grid control defines Row and Column attached properties for the children it contains.
You can set attached property values by using property attribute syntax, where the name of the attached property is the name of the class that defines it, followed by a dot, followed by the property name. For example, the following code defines an Image control positioned in row 1 column 2 of its containing Grid.
Figure 4 shows a Grid control containing nine Images, each with different Grid.Row and Grid.Column values.
|
|
Note that the Grid class defines Row and Column properties for a control whether or not it is contained in a Grid. The following XAML code defines a WrapPanel that contains an Image. The WrapPanel arranges its children in a single row, wrapping to a new row when necessary. The Grid.Row and Grid.Column properties don’t really make sense to the WrapPanel so it simply ignores them (see Figure 5).
Routed Events
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 event.
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 setting Handled to true 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 and MouseLeftButtonUp 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 events.
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 event.
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:
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.
Attached Events
The last topic in this article deals with attached events. The routed event is a WPF concept. When an action occurs, tunneling and bubbling events start rushing up and down the control hierarchy between the root element and the control that caused the event.
Sometimes it might be handy to intercept an event at some arbitrary point within the sequence of events. In code, this is easy. You simply place an event handler on the control where you want to catch the event. For example, if you want to catch a Button’s Click event in the Grid that contains the Button, you can make the Grid catch the PreviewMouseLeftButtonUp event. Using XAML, you use an attached event to catch an event in a control other than the one that started the process.
The syntax for an attached event is similar to the syntax for an attached property. First, find the control that should catch the event, and give it an attribute named after the control that will raise the event, followed by a dot, followed by the event name.
For example, the sample program ButtonAttachedEvent uses the following code to make a StackPanel that holds two Buttons. The StackPanel’s attribute Button.Click=”SP_ButtonClick” indicates that the StackPanel should capture any Button.Click events raised inside it and send them to the SP_ButtonClick event handler. Each button also has a Click attribute that indicates the event handler that should catch that button’s Click event.
If you run this program and click a Button, that Button’s event handler catches the event. After that event handler finishes, the Grid_ButtonClick routine catches the event.
Attached events are probably most useful when you want one central event handler to catch events raised from several locations (such as having several buttons trigger the same action), but as this example shows, more than one event handler can catch the same event at different places in the control hierarchy.
Change Your Mind
WPF’s dependency properties and routed events require big changes to the way you think about programming. In XAML, you can use property attribute syntax to set simple property values. Type converters translate the textual values (for example, “Red”) into the appropriate data type (a Color in this case).
To set a property to a more complex value (such as a radial gradient brush), you can use property element syntax. After the control’s start tag, create an element that defines the property’s value.
Routed events make it possible to handle events wherever you like along the path between the control hierarchy’s root and the object that started an event sequence. That has some immediate advantages, such as allowing a Button to fire its Click event even if you click on a control inside the Button instead of the button itself. It also allows you to catch and handle events in a more centralized global location by using attached events.
WPF properties and routed events are complicated, so you should expect to spend a little time to really get to know them. Until then, however, you can use Visual Studio and Expression Blend to set most property values and wire up event handlers. The information in this article will help you fill in gaps (for example, Visual Studio can make solid backgrounds but won’t create gradient backgrounds for you) and help clarify the XAML that those tools build for you.