- A Tour of the Class Hierarchy
- Logical and Visual Trees
- Dependency Properties
- Summary
Dependency Properties
WPF introduces a type of property called a dependency property that is used throughout the platform to enable styling, automatic data binding, animation, and more. You might first meet this concept with skepticism, as it complicates the picture of .NET types having simple fields, properties, methods, and events. But when you understand the problems that dependency properties solve, you will likely accept them as a welcome addition.
A dependency property depends on multiple providers for determining its value at any point in time. These providers could be an animation continuously changing its value, a parent element whose property value propagates down to its children, and so on. Arguably the biggest feature of a dependency property is its built-in ability to provide change notification.
The motivation for adding such intelligence to properties is to enable rich functionality directly from declarative markup. The key to WPF’s declarative-friendly design is its heavy use of properties. Button, for example, has more than 100 public properties (most of which are inherited from Control and its base classes)! Properties can be easily set in XAML (directly or by using a design tool) without any procedural code. But without the extra plumbing in dependency properties, it would be hard for the simple action of setting properties to get the desired results without the need to write additional code.
In this section, we briefly look at the implementation of a dependency property to make this discussion more concrete, and then we dig deeper into some of the ways that dependency properties add value on top of plain .NET properties:
- Change notification
- Property value inheritance
- Support for multiple providers
Understanding most of the nuances of dependency properties is usually important only for custom control authors. However, even casual users of WPF need to be aware of what dependency properties are and how they work. For example, you can only style and animate dependency properties. After working with WPF for a while, you might find yourself wishing that all properties would be dependency properties!
A Dependency Property Implementation
In practice, dependency properties are just normal .NET properties hooked into some extra WPF infrastructure. This is all accomplished via WPF APIs; no .NET languages (other than XAML) have an intrinsic understanding of a dependency property.
Listing 3.3 demonstrates how Button effectively implements one of its dependency properties, called IsDefault.
LISTING 3.3. A Standard Dependency Property Implementation
public
class
Button
:ButtonBase
{// The dependency property
public
static
readonly
DependencyProperty
IsDefaultProperty;static
Button() {// Register the property
Button
.IsDefaultProperty =DependencyProperty
.Register("IsDefault"
,typeof
(bool
),typeof
(Button
),new
FrameworkPropertyMetadata
(false
,new
PropertyChangedCallback
(OnIsDefaultChanged))); ... }// A .NET property wrapper (optional)
public bool
IsDefault {get
{return
(bool
)GetValue(Button
.IsDefaultProperty); }set
{ SetValue(Button
.IsDefaultProperty,value
); } }// A property changed callback (optional)
private static void
OnIsDefaultChanged(DependencyObject
o,DependencyPropertyChangedEventArgs
e) { ... } ... }
The static IsDefaultProperty field is the actual dependency property, represented by the System.Windows.DependencyProperty class. By convention, all DependencyProperty fields are public, static, and have a Property suffix. Several pieces of infrastructure require that you follow this convention: localization tools, XAML loading, and more.
Dependency properties are usually created by calling the static DependencyProperty.Register method, which requires a name (IsDefault), a property type (bool), and the type of the class claiming to own the property (Button). Optionally (via different overloads of Register), you can pass metadata that customizes how the property is treated by WPF, as well as callbacks for handling property value changes, coercing values, and validating values. Button calls an overload of Register in its static constructor to give the dependency property a default value of false and to attach a delegate for change notifications.
Finally, the traditional .NET property called IsDefault implements its accessors by calling GetValue and SetValue methods inherited from System.Windows.DependencyObject, the low-level base class from which all classes with dependency properties must derive. GetValue returns the last value passed to SetValue or, if SetValue has never been called, the default value registered with the property. The IsDefault .NET property (sometimes called a property wrapper in this context) is not strictly necessary; consumers of Button could directly call the GetValue/SetValue methods because they are exposed publicly. But the .NET property makes programmatic reading and writing of the property much more natural for consumers, and it enables the property to be set via XAML. WPF should, but does not, provide generic overloads of GetValue and SetValue. This is primarily because dependency properties were invented before .NET generics were widely used.
On the surface, Listing 3.3 looks like an overly verbose way of representing a simple Boolean property. However, because GetValue and SetValue internally use an efficient sparse storage system and because IsDefaultProperty is a static field (rather than an instance field), the dependency property implementation saves per-instance memory compared to a typical .NET property. If all the properties on WPF controls were wrappers around instance fields (as most .NET properties are), they would consume a significant amount of memory because of all the local data attached to each instance. Having more than 100 fields for each Button, more than 100 fields for each Label, and so forth would add up quickly! Instead, almost all of Button’s and Label’s properties are dependency properties.
The benefits of the dependency property implementation extend to more than just memory usage, however. The implementation centralizes and standardizes a fair amount of code that property implementers would have to write to check thread access, prompt the containing element to be re-rendered, and so on. For example, if a property requires its element to be re-rendered when its value changes (such as Button’s Background property), it can simply pass the FrameworkPropertyMetadataOptions.AffectsRender flag to an overload of DependencyProperty.Register. In addition, this implementation enables the three features listed earlier that we’ll now examine one-by-one, starting with change notification.
Change Notification
Whenever the value of a dependency property changes, WPF can automatically trigger a number of actions, depending on the property’s metadata. These actions can be re-rendering the appropriate elements, updating the current layout, refreshing data bindings, and much more. One of the interesting features enabled by this built-in change notification is property triggers, which enable you to perform your own custom actions when a property value changes, without writing any procedural code.
For example, imagine that you want the text in each Button from the About dialog in Listing 3.1 to turn blue when the mouse pointer hovers over it. Without property triggers, you can attach two event handlers to each Button, one for its MouseEnter event and one for its MouseLeave event:
<Button
MouseEnter=
"Button_MouseEnter
"MouseLeave=
"Button_MouseLeave
"MinWidth=
"75
"Margin=
"10
">
Help</Button>
<Button
MouseEnter=
"Button_MouseEnter
"MouseLeave=
"Button_MouseLeave
"MinWidth=
"75
"Margin=
"10
">
OK</Button>
These two handlers could be implemented in a C# code-behind file as follows:
// Change the foreground to blue when the mouse enters the button
void
Button_MouseEnter(object
sender,MouseEventArgs
e) {Button
b = senderas
Button
;if
(b !=null
) b.Foreground =Brushes
.Blue; }// Restore the foreground to black when the mouse exits the button
void
Button_MouseLeave(object
sender,MouseEventArgs
e) {Button
b = senderas
Button
;if
(b !=null
) b.Foreground =Brushes
.Black; }
With a property trigger, however, you can accomplish this same behavior purely in XAML. The following concise Trigger object is just about all you need:
<Trigger
Property=
"IsMouseOver
"Value=
"True
">
<Setter
Property=
"Foreground
"Value=
"Blue
"/>
</Trigger>
This trigger can act on Button’s IsMouseOver property, which becomes true at the same time the MouseEnter event is raised and false at the same time the MouseLeave event is raised. Note that you don’t have to worry about reverting Foreground to black when IsMouseOver changes to false. This is automatically done by WPF!
The only trick is assigning this Trigger to each Button. Unfortunately, because of a confusing limitation, you can’t apply property triggers directly to elements such as Button. You can apply them only inside a Style object, so an in-depth examination of property triggers is saved for Chapter 14. In the meantime, to experiment with property triggers, you can apply the preceding Trigger to a Button by wrapping it in a few intermediate XML elements, as follows:
<Button
MinWidth=
"75
"Margin=
"10
">
<Button.Style>
<Style
TargetType=
"{x:Type Button}
">
<Style.Triggers>
<Trigger
Property=
"IsMouseOver
"Value=
"True
">
<Setter
Property=
"Foreground
"Value=
"Blue
"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
OK</Button>
Property triggers are just one of three types of triggers supported by WPF. A data trigger is a form of property trigger that works for all .NET properties (not just dependency properties), also covered in Chapter 14. An event trigger enables you to declaratively specify actions to take when a routed event (covered in Chapter 6) is raised. Event triggers always involve working with animations or sounds, so they aren’t covered until Chapter 17, “Animation.”
Property Value Inheritance
The term property value inheritance (or property inheritance for short) doesn’t refer to traditional object-oriented class-based inheritance but rather the flowing of property values down the element tree. A simple example of this can be seen in Listing 3.4, which updates the Window from Listing 3.1 by explicitly setting its FontSize and FontStyle dependency properties. Figure 3.6 shows the result of this change. (Notice that the Window automatically resizes to fit all the content thanks to its slick SizeToContent setting!)
FIGURE 3.6. The About dialog with FontSize and FontStyle set on the root Window.
LISTING 3.4. The About Dialog with Font Properties Set on the Root Window
<Window
xmlns=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation
"Title=
"About WPF 4.5 Unleashed
"SizeToContent=
"WidthAndHeight
"FontSize=
"30
"FontStyle=
"Italic
"Background=
"OrangeRed
">
<StackPanel>
<Label
FontWeight=
"Bold
"FontSize=
"20
"Foreground=
"White
">
WPF 4.5 Unleashed</Label>
<Label>
© 2013 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>
For the most part, these two settings flow all the way down the tree and are inherited by children. This affects even the Buttons and ListBoxItems, which are three levels down the logical tree. The first Label’s FontSize does not change because it is explicitly marked with a FontSize of 20, overriding the inherited value of 30. The inherited FontStyle setting of Italic affects all Labels, ListBoxItems, and Buttons, however, because none of them have this set explicitly.
Notice that the text in the StatusBar is unaffected by either of these values, despite the fact that it supports these two properties just like the other controls. The behavior of property value inheritance can be subtle in cases like this for two reasons:
- Not every dependency property participates in property value inheritance. (Internally, dependency properties can opt in to inheritance by passing FrameworkPropertyMetadataOptions.Inherits to DependencyProperty.Register.)
- There may be other higher-priority sources setting the property value, as explained in the next section.
In this case, the latter reason is to blame. A few controls, such as StatusBar, Menu, and ToolTip, internally set their font properties to match current system settings. This way, users get the familiar experience of controlling their font via Control Panel. The result can be confusing, however, because such controls end up “swallowing” any inheritance from proceeding further down the element tree. For example, if you add a Button as a logical child of the StatusBar in Listing 3.4, its FontSize and FontStyle would be the default values of 12 and Normal, respectively, unlike the other Buttons outside of the StatusBar.
Support for Multiple Providers
WPF contains many powerful mechanisms that independently attempt to set the value of dependency properties. Without a well-defined mechanism for handling these disparate property value providers, the system would be a bit chaotic, and property values could be unstable. Of course, as their name indicates, dependency properties were designed to depend on these providers in a consistent and orderly manner.
Figure 3.7 illustrates the five-step process that WPF runs each dependency property through in order to calculate its final value. This process happens automatically, thanks to the built-in change notification in dependency properties.
FIGURE 3.7. The pipeline for calculating the value of a dependency property.
Step 1: Determine the Base Value
Most of the property value providers factor into the base value calculation. The following list reveals the ten providers that can set the value of most dependency properties, in order from highest to lowest precedence:
- Local value
- Parent template trigger
- Parent template
- Style triggers
- Template triggers
- Style setters
- Theme style triggers
- Theme style setters
- Property value inheritance
- Default value
You’ve already seen some of the property value providers, such as property value inheritance (#9). Local value (#1) technically means any call to DependencyObject.SetValue, but this is typically seen with a simple property assignment in XAML or procedural code (because of the way dependency properties are implemented, as shown previously with Button.IsDefault). Default value (#10) refers to the initial value registered with the dependency property, which naturally has the lowest precedence. The other providers, which all involve styles and templates, are explained further in Chapter 14.
This order of precedence explains why StatusBar’s FontSize and FontStyle were not impacted by property value inheritance in Listing 3.4. The setting of StatusBar’s font properties to match system settings is done via theme style setters (#8). Although this has precedence over property value inheritance (#9), you can still override these font settings using any mechanism with a higher precedence, such as simply setting local values on StatusBar.
Step 2: Evaluate
If the value from step one is an expression (an object deriving from System.Windows.Expression), WPF performs a special evaluation step to convert the expression into a concrete result. Expressions mostly appear in data binding (the topic of Chapter 13, “Data Binding”).
Step 3: Apply Animations
If one or more animations are running, they have the power to alter the current property value (using the value after step 2 as input) or completely replace it. Therefore, animations (the topic of Chapter 17) can trump all other property value providers—even local values! This is often a stumbling block for people who are new to WPF.
Step 4: Coerce
After all the property value providers have had their say, WPF passes the almost-final property value to a CoerceValueCallback delegate, if one was registered with the dependency property. The callback is responsible for returning a new value, based on custom logic. For example, built-in WPF controls such as ProgressBar use this callback to constrain its Value dependency property to a value between its Minimum and Maximum values, returning Minimum if the input value is less than Minimum and Maximum if the input value is greater than Maximum. If you change your coercion logic at runtime, you can call CoerceValue to make WPF run the new coercion and validation logic again.
Step 5: Validate
Finally, the potentially coerced value is passed to a ValidateValueCallback delegate, if one was registered with the dependency property. This callback must return true if the input value is valid and false otherwise. Returning false causes an exception to be thrown, canceling the entire process.
Attached Properties
An attached property is a special form of dependency property that can effectively be attached to arbitrary objects. This may sound strange at first, but this mechanism has several applications in WPF.
For the About dialog example, imagine that rather than setting FontSize and FontStyle for the entire Window (as is done in Listing 3.4), you would rather set them on the inner StackPanel so they are inherited only by the two Buttons. Moving the property attributes to the inner StackPanel element doesn’t work, however, because StackPanel doesn’t have any font-related properties of its own! Instead, you must use the FontSize and FontStyle attached properties that happen to be defined on a class called TextElement. Listing 3.5 demonstrates this, introducing new XAML syntax designed especially for attached properties. This enables the desired property value inheritance, as shown in Figure 3.8.
FIGURE 3.8. The About dialog with FontSize and FontStyle set on both Buttons via inheritance from the inner StackPanel.
LISTING 3.5. The About Dialog with Font Properties Moved to the Inner StackPanel
<Window
xmlns=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title=
"About WPF 4.5 Unleashed
"SizeToContent=
"WidthAndHeight
"Background=
"OrangeRed
">
<StackPanel>
<Label
FontWeight=
"Bold
"FontSize=
"20
"Foreground=
"White
">
WPF 4.5 Unleashed</Label>
<Label>
© 2013 SAMS Publishing</Label>
<Label>
Installed Chapters:</Label>
<ListBox>
<ListBoxItem>
Chapter 1</ListBoxItem>
<ListBoxItem>
Chapter 2</ListBoxItem>
</ListBox>
<StackPanel
TextElement.FontSize=
"30
"TextElement.FontStyle=
"Italic
"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>
TextElement.FontSize and TextElement.FontStyle (rather than simply FontSize and FontStyle) must be used in the StackPanel element because StackPanel does not have these properties. When a XAML parser or compiler encounters this syntax, it requires that TextElement (sometimes called the attached property provider) have static methods called SetFontSize and SetFontStyle that can set the value accordingly. Therefore, the StackPanel declaration in Listing 3.5 is equivalent to the following C# code:
StackPanel panel
=new
StackPanel
();TextElement
.SetFontSize(panel, 30);TextElement
.SetFontStyle(panel,FontStyles
.Italic); panel.Orientation =Orientation
.Horizontal; panel.HorizontalAlignment =HorizontalAlignment
.Center;Button
helpButton =new
Button
(); helpButton.MinWidth = 75; helpButton.Margin =new
Thickness
(10); helpButton.Content ="Help"
;Button
okButton =new
Button
(); okButton.MinWidth = 75; okButton.Margin =new
Thickness
(10); okButton.Content ="OK"
; panel.Children.Add(helpButton); panel.Children.Add(okButton);
Notice that the enumeration values such as FontStyles.Italic, Orientation.Horizontal, and HorizontalAlignment.Center were previously specified in XAML simply as Italic, Horizontal, and Center, respectively. This is possible thanks to the EnumConverter type converter in the .NET Framework, which can convert any case-insensitive string.
Although the XAML in Listing 3.5 nicely represents the logical attachment of FontSize and FontStyle to StackPanel, the C# code reveals that there’s no real magic here, just a method call that associates an element with an otherwise-unrelated property. One of the interesting things about the attached property abstraction is that no .NET property is a part of it!
Internally, methods such as SetFontSize simply call the same DependencyObject.SetValue method that a normal dependency property accessor calls, but on the passed-in DependencyObject rather than the current instance:
public
static
void
SetFontSize(DependencyObject
element,double
value) { element.SetValue(TextElement
.FontSizeProperty, value); }
Similarly, attached properties also define a static GetXXX method (where XXX is the name of the property) that calls the familiar DependencyObject.GetValue method:
public
static
double
GetFontSize(DependencyObject
element) {return
(double
)element.GetValue(TextElement
.FontSizeProperty); }
As with property wrappers for normal dependency properties, these GetXXX and SetXXX methods must not do anything other than make a call to GetValue and SetValue.
Although the About dialog example uses attached properties for advanced property value inheritance, attached properties are most commonly used for layout of user interface elements. (In fact, attached properties were originally designed for WPF’s layout system.) Various Panel-derived classes define attached properties designed to be attached to their children for controlling how they are arranged. This way, each Panel can apply its own custom behavior to arbitrary children without requiring all possible child elements to be burdened with their own set of relevant properties. It also enables systems such as layout to be easily extensible, because anyone can write a new Panel with custom attached properties. Chapter 5, “Layout with Panels,” and Chapter 21, “Layout with Custom Panels,” have all the details.