Dependency Property Metadata
The Windows Presentation Foundation (WPF) property system includes a metadata reporting system that goes beyond what can be reported about a property through reflection or general common language runtime (CLR) characteristics. Metadata for a dependency property can also be assigned uniquely by the class that defines a dependency property, can be changed when the dependency property is added to a different class, and can be specifically overridden by all derived classes that inherit the dependency property from the defining base class.
This topic contains the following sections.
- How Dependency Property Metadata is Used
- Metadata APIs
- When to Override Metadata, When to Subclass
- Scenarios for Changing Existing Metadata
- Related Topics
This topic assumes that you understand dependency properties from the perspective of a consumer of existing dependency properties on Windows Presentation Foundation (WPF) classes, and have read the Dependency Properties Overview. In order to follow the examples in this topic, you should also understand XAML and know how to write WPF applications.
How Dependency Property Metadata is Used
Dependency property metadata exists as an object that can be queried to examine the property's characteristics. This metadata is also accessed frequently by the property system as it processes any given dependency property. The metadata object for a dependency property can contain the following types of information:
Default value for the property, if no other value can be determined for the property by local value, style, inheritance, etc. For a thorough discussion of how default values fall in the precedence used by the property system when assigning values for dependency properties, see Dependency Property Value Precedence.
References to callback implementations that affect coercion or change-notification behaviors on a per-owner-type basis. Note that these callbacks are often defined with a nonpublic access level, therefore obtaining the actual references from metadata is generally not possible unless these references are within your allowed access scope. For more information on dependency property callbacks, see Dependency Property Callbacks and Validation.
If the dependency property in question is considered to be a WPF framework-level property, the metadata might contain a bitwise flag enumeration value of WPF framework-level dependency property characteristics, which report information and state for services such as the WPF framework-level layout engine and property inheritance logic. For more information on this aspect of dependency property metadata, see Framework Property Metadata.
The type that reports most of the metadata information used by the property system is the PropertyMetadata class. Metadata instances are optionally specified when dependency properties are registered with the property system, and can be specified again for additional types that either add themselves as owners or override metadata they inherit from the base class property definition. The registered metadata is returned as PropertyMetadata when you call the various GetMetadata overloads that get metadata from a dependency property on a DependencyObject instance.
The PropertyMetadata class is derived from to provide more specific metadata for architectural divisions such as the WPF framework-level. UIPropertyMetadata adds animation reporting, and FrameworkPropertyMetadata provides the WPF framework-level flags mentioned in the previous section. When dependency properties are registered they can be registered with these PropertyMetadata derived classes, and when the metadata is examined, the base PropertyMetadata type can potentially be cast to the derived classes so that you can examine the more specific information properties.
The property characteristics that can be specified in FrameworkPropertyMetadata particularly are sometimes referred to in this documentation as "flags". When you create new metadata instances for use in property registrations or metadata overrides, you actually specify these values using the flagwise enumeration FrameworkPropertyMetadataOptions and then you supply possibly concatenated values of the enumeration to the FrameworkPropertyMetadata constructor. However, once constructed, these option characteristics are exposed within a FrameworkPropertyMetadata as a series of Boolean properties. The Boolean properties enable you to check each conditional, rather than requiring you to apply a mask to a flagwise enumeration value to get the information you are interested in. The constructor uses the concatenated FrameworkPropertyMetadataOptions in order to keep the length of the constructor signature reasonable, whereas the actual constructed metadata exposes the discrete properties to make querying more intuitive.
When to Override Metadata, When to Subclass
The WPF property system has established capabilities for changing some characteristics of dependency properties without requiring them to be entirely re-implemented. This is accomplished by constructing a different instance of property metadata for the property as it exists on a particular type. Note that most existing dependency properties are not virtual properties, therefore strictly speaking "re-implementing" them on inherited classes could only be accomplished by shadowing the existing member.
If the scenario you are trying to enable for a property on a type cannot be accomplished by modifying characteristics of existing dependency properties, it may then be necessary to create a derived class, and then declare a custom dependency property on your derived class. A custom dependency property behaves identically to dependency properties defined by the WPF APIs. For more details about custom dependency properties, see Custom Dependency Properties.
One notable characteristic of a dependency property that you cannot override is its type. If you are inheriting a dependency property that has the approximate behavior you require, but you require a different type for it, you will have to implement a custom dependency property and perhaps link the properties through type conversion or other implementation on your custom class. You also cannot replace an existing ValidateValueCallback.
Scenarios for Changing Existing Metadata
If you are working with metadata of an existing dependency property, one common scenario for changing dependency property metadata is to change the default value. Changing or adding property system callbacks is a more advanced scenario. You might want to do this if your implementation of a derived class has different interrelationships between properties. One of the conditionals of having a programming model that supports both code and declarative usage is that properties must enable being set in any order. Thus any dependent properties need to be set just-in-time without context and cannot rely on knowing a setting order such as might be found in a constructor. For more information on this aspect of the property system, see Dependency Property Callbacks and Validation. Note that validation callbacks are not part of the metadata, they are part of the dependency property identifier. Therefore, validation callbacks cannot be changed by overriding the metadata.
In some cases you might also want to alter the framework property metadata options on existing dependency properties. These options communicate certain known conditionals about framework-level properties to other WPF framework-level processes such as the layout system. Setting the options is generally done only when registering a new dependency property, but it is also possible to change the framework property metadata as part of a OverrideMetadata or AddOwner call. For the specific values to use and more information, see Framework Property Metadata. For more information that is pertinent to how these options should be set for a newly registered dependency property, see Custom Dependency Properties.
The purpose of overriding metadata is primarily so that you have the opportunity to change the various metadata-derived behaviors that are applied to the property as it exists on your type. The reasons for this are explained in more detail in the Metadata section. For more information including some code examples, see How to: Override Metadata for a Dependency Property.
Property metadata can be supplied for a property during the registration call (Register). However, in many cases, you might want to provide type-specific metadata for your class when it inherits that property. You can do this by calling the OverrideMetadata method. For an example from the WPF APIs, the FrameworkElement class is the type that first registers the Focusable property. But the Control class overrides that property's metadata to provide its own initial default value, changing it from false to true, and otherwise re-uses the original Focusable implementation.
When you override metadata, the different metadata characteristics are either merged or replaced.
PropertyChangedCallback is merged. If you add a new PropertyChangedCallback, that callback is stored in the metadata. If you do not specify a PropertyChangedCallback in the override, the value of PropertyChangedCallback is promoted as a reference from the nearest ancestor that specified it in metadata.
The actual property system behavior for PropertyChangedCallback is that implementations for all metadata owners in the hierarchy are retained and added to a table, with order of execution by the property system being that the most derived class's callbacks are invoked first.
DefaultValue is replaced. If you do not specify a PropertyChangedCallback in the override, the value of DefaultValue comes from the nearest ancestor that specified it in metadata.
CoerceValueCallback implementations are replaced. If you add a new CoerceValueCallback, that callback is stored in the metadata. If you do not specify a CoerceValueCallback in the override, the value of CoerceValueCallback is promoted as a reference from the nearest ancestor that specified it in metadata.
The property system behavior is that only the CoerceValueCallback in the immediate metadata is invoked. No references to other CoerceValueCallback implementations in the hierarchy are retained.
This behavior is implemented by Merge, and can be overridden on derived metadata classes.
Adding a Class as an Owner of an Existing Dependency Property
A class can add itself as an owner of a property that has already been registered, by using the AddOwner method. This enables the class to use a property that was originally registered for a different type. The adding class is typically not a derived class of the type that first registered that property as owner. Effectively, this allows your class and its derived classes to "inherit" a dependency property implementation without the original owner class and the adding class being in the same true class hierarchy. In addition, the adding class (and all derived classes as well) can then provide type-specific metadata for the original dependency property.
As well as adding itself as owner through the property system utility methods, the adding class should declare additional public members on itself in order to make the dependency property a full participant in the property system with exposure to both code and markup. A class that adds an existing dependency property has the same responsibilities as far as exposing the object model for that property as does a class that defines a new custom dependency property. The first such member to expose is a dependency property identifier field. This field should be a public static readonly field of type DependencyProperty, which is assigned to the return value of the AddOwner call. The second member to define is the common language runtime (CLR) "wrapper" property. The wrapper makes it much more convenient to manipulate your property in code (you avoid calls to SetValue each time, and can make that call only once in the wrapper itself). The wrapper is implemented identically to how it would be implemented if you were registering a custom dependency property. For more information about implementing a dependency property, see Custom Dependency Properties and How to: Add an Owner Type for a Dependency Property.