
Checklist for Defining a Dependency Property
Defining a dependency property consists of four distinct concepts. These concepts are not necessarily strict procedural steps, because some of these end up being combined as single lines of code in the implementation:
(Optional) Create property metadata for the dependency property.
Register the property name with the property system, specifying an owner type and the type of the property value. Also specify the property metadata, if used.
Define a DependencyProperty identifier as a public static readonly field on the owner type.
Define a CLR "wrapper" property whose name matches the name of the dependency property. Implement the CLR "wrapper" property's get and set accessors to connect with the dependency property that backs it.
Registering the Property with the Property System
In order for your property to be a dependency property, you must register that property into a table maintained by the property system, and give it a unique identifier that is used as the qualifier for later property system operations. These operations might be internal operations, or your own code calling property system APIs. To register the property, you call the Register method within the body of your class (inside the class, but outside of any member definitions). The identifier field is also provided by the Register method call, as the return value. The reason that the Register call is done outside of other member definitions is because you use this return value to assign and create a public static readonly field of type DependencyProperty as part of your class. This field becomes the identifier for your dependency property.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
Dependency Property Name Conventions
There are established naming conventions regarding dependency properties that you must follow in all but exceptional circumstances.
The dependency property itself will have a basic name, "AquariumGraphic" as in this example, which is given as the first parameter of Register. That name must be unique within each registering type. Dependency properties inherited through base types are considered to be already part of the registering type; names of inherited properties cannot be registered again. However, there is a technique for adding a class as owner of a dependency property even when that dependency property is not inherited; for details, see Dependency Property Metadata.
When you create the identifier field, name this field by the name of the property as you registered it, plus the suffix Property. This field is your identifier for the dependency property, and it will be used later as an input for the SetValue and GetValue calls you will make in the wrappers, by any other code access to the property by your own code, by any external code access you allow, by the property system, and potentially by XAML processors.
Note: |
|---|
Defining the dependency property in the class body is the typical implementation, but it is also possible to define a dependency property in the class static constructor. This approach might make sense if you need more than one line of code to initialize the dependency property.
|
Implementing the "Wrapper"
Your wrapper implementation should call GetValue in the get implementation, and SetValue in the set implementation (the original registration call and field are shown here too for clarity).
In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue actions, respectively. The reason for this is discussed in the topic XAML Loading and Dependency Properties.
All existing public dependency properties that are provided on the WPF classes use this simple wrapper implementation model; most of the complexity of how dependency properties work is either inherently a behavior of the property system, or is implemented through other concepts such as coercion or property change callbacks through property metadata.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
public Uri AquariumGraphic
{
get { return (Uri)GetValue(AquariumGraphicProperty); }
set { SetValue(AquariumGraphicProperty, value); }
}
Again, by convention, the name of the wrapper property must be the same as the name chosen and given as first parameter of the Register call that registered the property. If your property does not follow the convention, this does not necessarily disable all possible uses, but you will encounter several notable issues:
Certain aspects of styles and templates will not work.
Most tools and designers must rely on the naming conventions to properly serialize XAML, or to provide designer environment assistance at a per-property level.
The current implementation of the WPF XAML loader bypasses the wrappers entirely, and relies on the naming convention when processing attribute values. For more information, see XAML Loading and Dependency Properties.
Property Metadata for a New Dependency Property
When you register a dependency property, the registration through the property system creates a metadata object that stores property characteristics. Many of these characteristics have defaults that are set if the property is registered with the simple signatures of Register. Other signatures of Register allow you to specify the metadata that you want as you register the property. The most common metadata given for dependency properties is to give them a default value that is applied on new instances that use the property.
If you are creating a dependency property that exists on a derived class of FrameworkElement, you can use the more specialized metadata class FrameworkPropertyMetadata rather than the base PropertyMetadata class. The constructor for the FrameworkPropertyMetadata class has several signatures where you can specify various metadata characteristics in combination. If you want to specify the default value only, use the signature that takes a single parameter of type Object. Pass that object parameter as a type-specific default value for your property (the default value provided must be the type you provided as the propertyType parameter in the Register call).
For FrameworkPropertyMetadata, you can also specify metadata option flags for your property. These flags are converted into discrete properties on the property metadata after registration and are used to communicate certain conditionals to other processes such as the layout engine.
Setting Appropriate Metadata Flags
If your property (or changes in its value) affects the user interface (UI), and in particular affects how the layout system should size or render your element in a page, set one or more of the following flags: AffectsMeasure, AffectsArrange, AffectsRender.
AffectsMeasure indicates that a change to this property requires a change to UI rendering where the containing object might require more or less space within the parent. For example, a "Width" property should have this flag set.
AffectsArrange indicates that a change to this property requires a change to UI rendering that typically does not require a change in the dedicated space, but does indicate that the positioning within the space has changed. For example, an "Alignment" property should have this flag set.
AffectsRender indicates that some other change has occurred that will not affect layout and measure, but does require another render. An example would be a property that changes a color of an existing element, such as "Background".
These flags are often used as a protocol in metadata for your own override implementations of property system or layout callbacks. For instance, you might have an OnPropertyChanged callback that will call InvalidateArrange if any property of the instance reports a value change and has AffectsArrange as true in its metadata.
Some properties may affect the rendering characteristics of the containing parent element, in ways above and beyond the changes in required size mentioned above. An example is the MinOrphanLines property used in the flow document model, where changes to that property can change the overall rendering of the flow document that contains the paragraph. Use AffectsParentArrange or AffectsParentMeasure to identify similar cases in your own properties.
By default, dependency properties support data binding. You can deliberately disable data binding, for cases where there is no realistic scenario for data binding, or where performance in data binding for a large object is recognized as a problem.
By default, data binding Mode for dependency properties defaults to OneWay. You can always change the binding to be TwoWay per binding instance; for details, see How to: Specify the Direction of the Binding. But as the dependency property author, you can choose to make the property use TwoWay binding mode by default. An example of an existing dependency property is MenuItem..::.IsSubmenuOpen; the scenario for this property is that the IsSubmenuOpen setting logic and the compositing of MenuItem interact with the default theme style. The IsSubmenuOpen property logic uses data binding natively to maintain the state of the property in accordance to other state properties and method calls. Another example property that binds TwoWay by default is TextBox..::.Text.
You can also enable property inheritance in a custom dependency property by setting the Inherits flag. Property inheritance is useful for a scenario where parent elements and child elements have a property in common, and it makes sense for the child elements to have that particular property value set to the same value as the parent set it. An example inheritable property is DataContext, which is used for binding operations to enable the important master-detail scenario for data presentation. By making DataContext inheritable, any child elements inherit that data context also. Because of property value inheritance, you can specify a data context at the page or application root, and do not need to respecify it for bindings in all possible child elements. DataContext is also a good example to illustrate that inheritance overrides the default value, but it can always be set locally on any particular child element; for details, see How to: Use the Master-Detail Pattern with Hierarchical Data. Property value inheritance does have a possible performance cost, and thus should be used sparingly; for details, see Property Value Inheritance.
Set the Journal flag to indicate if your dependency property should be detected or used by navigation journaling services. An example is the SelectedIndex property; any item selected in a selection control should be persisted when the journaling history is navigated.