There is something very important to know about dependency properties. Once you understand this fundamental point, much of WPF becomes more intuitive.
The value of a dependency property is resolved.
Normal CLR properties (i.e. not dependency properties) typically provide a getter and/or setter with which the value of a private field can be retrieved and/or modified. When you set a normal CLR property, you might usually think of it as assigning a value to a field. Usually whatever value you set a property to is the value which is returned when you access its getter. Of course, properties can contain other logic which executes before and after the field is modified or returned. That’s a major reason why they exist.
Dependency properties are a different animal altogether. They never represent a private field in your class. You can assign a DP a specific value, but that is not necessarily the value which will be returned when you access its getter. Even if your DP has no validation or value coercion logic, you still might not get back the same value you put in.
There is a well-defined set of rules which is used internally by WPF to figure out what the real value of a DP is. Here is a brief summary of the rules of precedence used when resolving the value of a DP (read more about it here):
- Property system coercion
- Active animations, or animations with a Hold behavior
- Local value
- TemplatedParent template
- Style triggers
- Template triggers
- Style setters
- Theme style
- Inheritance
- Default value from dependency property metadata
As that list suggests, there is a lot more going on when the value of a DP is determined than simply using the value of a field. When you explicitly set a DP to a value (ex. this.Height = 123;), you are assigning it a “local value.” As you can see above, a DP’s local value is the third place the system will look when resolving the property’s value.
If the DP was registered with a callback to coerce the value to which it is set, the value returned by the callback is returned the next time you access the property. If the DP is being animated, or was animated by an Animation which “holds” its value after it completes, the Animation’s value is returned. If neither of those conditions is met, the DP will resolve to the value which you assigned to the property (the local value). Failing that, the DP resolution keeps walking down that list looking for an applicable value to use. If it reaches the end of the list and discovers that the DP was not registered with a default value, then the property’s getter returns null or zero (which are the default values for a reference type or value type, respectively).
On a side note, this explains how a trigger is able to “magically” revert the value of a DP back to the original value, once the trigger is no longer active. Since the value applied by a trigger is part of the value resolution algorithm, as soon as a trigger stops applying a value to a DP, the DP’s value is taken from somewhere further down the list. The DP’s value is never actually reverted, because it does not have a value in the first place!
Remember, a DP never really “has” a value…its value depends on various external factors. That’s why they are called dependency properties.
In my next post we take a look at an example of the DP value resolution process being used, to gain a clearer understanding of how it works.