WPF Concepts—Dependency Property Implementation
| Visual C# Tutorials |
| .NET Framework Tutorials |
| © 2007 Sams Publishing |
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. 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, a 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 always 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.
| WARNING: .NET property wrappers are bypassed at run-time when setting dependency properties in XAML! |
Although the XAML compiler depends on the property wrapper at compile-time, at run-time WPF calls the underlying GetValue and SetValue methods directly! Therefore, to maintain parity between setting a property in XAML and procedural code, it's crucial that property wrappers do not contain any logic in addition to the GetValue/SetValue calls. If you want to add custom logic, that's what the registered callbacks are for. All of WPF's built-in property wrappers abide by this rule, so this warning is for anyone writing a custom class with its own dependency properties.
|
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 96 fields for each Button, 89 fields for each Label, and so forth would add up quickly! Instead, 78 out of Button's 96 properties are dependency properties, and 71 out of Label's 89 properties are dependency properties.
The benefits of the dependency property implementation extend to more than just memory usage, however. It 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.
|

