Creating a dependency property
Dependency properties are the workhorse of WPF. This infrastructure provides for many of WPF's features, such as data binding, animations, and visual inheritance. In fact, most of the various element properties are Dependency Properties. Sometimes we need to create such properties for our own controls or windows.
Getting ready
Make sure you have Visual Studio up and running.
How to do it...
We'll create a simple user control with one new dependency property to illustrate the entire procedure:
- Within Visual Studio 2010, create a new WPF Application named CH01.DependencyProperties.
- We'll add a simple User Control, to which we'll add a dependency property. Don't worry if you don't understand exactly what a user control is; we'll discuss those in a later chapter. For now, just concentrate on the dependency properties we'll create and use. To create the User Control, right-click on the Project node in the Solution Explorer and select Add and then User Control….
- In the resulting dialog, type
SimpleControl
in the Name box, and then click on Add: - We'll add a dependency property to the
SimpleControl
class. A dependency property needs to be "registered" with the property system. Open theSimpleControl.xaml.cs
file and typepropdp
just after the closing brace of the constructor. This is how it would look in the Visual Studio editor: - This is a code snippet that helps with the (somewhat unpleasant) details of properly registering the property. Press Tab; the code snippet is expanded to something like the following:
The first part looks like a normal getter/setter of a property (although the implementation is anything but "normal"). The second part actually registers the property with some information (more on that in the How it works… section). Let's create a property named
YearPublished
of typeint
. - Press Tab to skip the
int
part (as that's what we want here). The focus should jump toMyProperty
. TypeYearPublished
as the property name. - Press Tab again. Note that this changes the property name in the lower
Register
call toYearPublished
. The focus should jump to theownerclass
part. TypeSimpleControl
. - Press Tab again. The focus should jump to the
0
. This should be the default value of the property, if not altered. Change the0
into2000
. After removing the (unhelpful) comment from the snippet provided, the code should look as follows:public int YearPublished { get { return (int)GetValue(YearPublishedProperty); } set { SetValue(YearPublishedProperty, value); } } public static readonly DependencyProperty YearPublishedProperty = DependencyProperty.Register( "YearPublished", typeof(int), typeof(SimpleControl), new UIPropertyMetadata(2000));
- Now let's test it. If that's indeed a dependency property, then there are a few things it can do, such as data bind. We'll add an instance of the
SimpleControl
class to our main window and bind the property we defined to some other control. - Open the
MainWindow.xaml
file. Replace the existingGrid
with aStackPanel
, and add an instance of ourSimpleControl
. The entire markup should look like as follows:<Window x:Class="CH01.DependencyProperties.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CH01.DependencyProperties" Title="MainWindow" Height="350" Width="525"> <StackPanel> <local:SimpleControl x:Name="_simple" /> </StackPanel> </Window>
- Note the
local
prefix pointing to our .NET namespace (as explained in an earlier recipe of this chapter). Now let's add aTextBlock
and aButton
inside theStackPanel
. TheText
property ofTextBlock
should bind to theYearPublished
property of theSimpleControl
instance:<TextBlock Text="{Binding YearPublished, ElementName=_simple}" FontSize="30" /> <Button Content="Change Value" FontSize="20"/>
- When the button is clicked, we'll increment the
YearPublished
property and see if that changes the displayed text in theTextBlock
. First, add aClick
event handler; within theButton
element typeClick=
. Visual Studio writes a pair of quotes and suggests adding a handler. You can press Tab to accept the default handler name, or type yourself an appropriate name, such asOnChangeValue
:<Button Content="Change Value" FontSize="20" Click="OnChangeValue"/>
- Right-click on the handler name (
OnChangeValue
in this case), and select Navigate to Event Handler: - Visual Studio switches to the
MainWindow.xaml.cs
file inside the event handler. Add a simple increment of theYearPublished
property ofSimpleControl
named_simple
. The entire method should look as follows:private void OnChangeValue(object sender, RoutedEventArgs e) { _simple.YearPublished++; }
- Run the application. You should see the
TextBlock
showing 2000. That's the default value we set in theDependencyProperty.Register
call. - Now press the button Change Value a few times— the text should be incremented. This happened because of the change notifications raised by the dependency property system, to which the data binding system registered.
How it works...
A dependency property is managed by a public static
(readonly
) field named with the property name suffixed with Property
; in our case it's MyValueProperty
. This field manages the property value for any instance of the type it's declared in. The call to DependencyProperty.Register
sets the property's name, its type, the owner type, and set of metadata for that property. The previous code uses an instance of UIPropertyMetadata
(one of several possible types), that accepts (at least) the default value for the property (10 in our example).
The classic getter/setter method pair includes calls to SetValue
and GetValue
. These are defined in the DependencyObject
base class, which means any type that wants to leverage the dependency property system must inherit from this class (directly or indirectly). For WPF elements, this is not a problem, as everything inherits from DependencyObject
eventually.
When a new value is set for the property (as we did for our code), the SetValue
method does "the right thing", meaning (for example), sending notifications to whoever is listening (such as the data binding system).
There's more...
When we register a dependency property, we can provide a property changed callback delegate, to be called when that property value changes for whatever reason:
public static readonly DependencyProperty YearPublishedProperty = DependencyProperty.Register( "YearPublished", typeof(int), typeof(SimpleControl), new UIPropertyMetadata(2000, OnValueChanged)); private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { // do something when property changes }
We'll examine this technique in a future recipe when we discuss attached properties.
This may seem unnecessary— after all, can't we just add code to the setter, and then just know when the property value is changed? No. The reason is that the property setter is just syntactic sugar—it's not always called directly. It is used by developers, as this is the familiar property syntax, but the XAML parser, for example, just calls SetValue
directly—bypassing the property setter altogether. And it's not the only entity doing so. The setter should have nothing in it except the SetValue
call.
User interface needs to be consistent, usually having the same fonts, sizes, and so on. Setting a font size, for example, on each and every element so they have the same value is tedious and unmaintainable. One of the ways WPF deals with that (not the only way; we'll see another powerful way in Chapter 8) is the idea of propagating a property value down the visual tree. This mechanism, property value inheritance, is supported by the dependency property infrastructure.
A canonical example is the font-related properties. If we set the FontSize
property (for instance) on some container element, child elements (in any level) would use that property instead of the default:
<Window x:Class="CH01.InheritDemo.MainWindow" Title="MainWindow" FontSize="20" Width="400" Height="300"> <StackPanel> <TextBlock Text="Text 1" /> <TextBlock Text="Text 2" /> <TextBlock Text="Text 3" /> </StackPanel> </Window>
Note the FontSize
is set to 20
on the Window
object. All the TextBlock
s will now use the value of 20
for their font size instead of the default. This is a feature a dependency property may choose to use, specified at registration time. Here's an example of a dummy property:
public static readonly DependencyProperty DummyProperty = DependencyProperty.Register("Dummy", typeof(int), typeof(MainWindow), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
That last flag makes this property inheritable by default (same as the font related properties).
What is the "dependency" part of dependency properties? In the previous section, we looked at visual inheritance. Suppose that one of those example TextBlocks
sets a different FontSize
values like so:
<Window x:Class="CH01.InheritDemo.MainWindow" Title="MainWindow" FontSize="20" Width="400" Height="300"> <StackPanel> <TextBlock Text="Text 1" /> <TextBlock Text="Text 2" FontSize="30"/> <TextBlock Text="Text 3" /> </StackPanel> </Window>
What will be the final result? It turns out 30 is the winner for the second TextBlock
. What we see is a set of priorities for providers of values. The first (and lowest) priority is the default value registered with DependencyProperty.Register
. A higher priority is the inheritance feature (if registered as such for that property). A higher still priority is a local value (30 in our example) that takes precedence over inheritance. So, a dependency property depends on one of several levels or priorities of value providers. In fact, there are about 11 different levels in all (we have seen three in this example). All provider values are not lost—they may become effective if the highest provider is cleared. Here's an example:
_text2.ClearValue(TextBlock.FontSizeProperty);
This clears the local value of the (for example) second TextBlock
, reverting its FontSize
value to 20 (the inherited value).
By the way, the highest priority provider (except for a coercion callback, explained in the next section) is an active animation. If this wasn't so, an animation would simply have no effect. Once the animation is removed, the property value reverts to its previous state (depending on the highest provider at that time).
We need to take this behavior into consideration. If a property does not seem to get the expected value, there's a good chance we missed some provider that's "stronger" than the one we expected to win. The Visual Studio debugger has a visualizer that can be used to view the current property values of elements, and (very important) the provider that's effectively providing this value. Here's an example for our famous second TextBlock
:
Note the Local reading of the Source column.
The CH11.InheritDemo
project, available with the downloadable source for this chapter, can be used to test it out. To get to this dialog, set a breakpoint where you have easy access to the required variable (in this case it could be done in the MainWindow
constructor after InitializeComponent
), and then click on the small magnifying glass near the variable's value column:
If we remove the FontSize="30"
local value setting, and use the visualizer again, we get the following:
The Source column clearly indicates that the value was set because of visual inheritance.
This information is also available by using other tools that don't require Visual Studio or a debugger of any kind. One such free tool is Snoop (http://snoopwpf.codeplex.com). This tool can look at any WPF window and drill down into the visual tree, showing property values (with their source); since it does not require anything special, it can be used in production environments, where tools such as Visual Studio are not typically found.
As mentioned, there are 11 levels, or priorities, of dependency property providers. Here's the complete list (highest to lowest precedence):
- Property coercion: The coercion mechanism allows a delegate to execute before the final value is set for the property. That coercion delegate is provided as part of the property metadata at registration time. For example, if a property signifies an hour in the day, it should have a value between 0 and 23. The coercion callback can look at the suggested value, and if (say) it's greater than 23, return 23 as the final value.
- Active animation: If an animation is active, it provides the property's current value.
- Local value: Set through the property setter in code, or through XAML.
- Template parent properties: If the control was created as part of a
ControlTemplate
orDataTemplate
, these properties apply (we'll discuss data templates in Chapter 6 and control templates in Chapter 8). - Implicit style: (We'll discuss implicit styles in Chapter 8).
- Style triggers from Windows or the application (we'll discuss triggers in Chapter 8).
- Template triggers: Triggers that are part of a template (again, Chapter 8).
- Style setters: Values from styles defined in the Window or the application (styles are discussed in Chapter 8).
- Default style: Set by the control creator and can be based on the current Windows theme.
- Inheritance: As discussed in a previous section.
- Default value: As set in the property metadata.
For a detailed look at all dependency property levels, you can refer to this link in the official MSDN documentation: http://msdn.microsoft.com/en-us/library/1FBADA8E-4867-4ED1-8D97-62C07DAD7EBC(v=vs.100,d=loband).aspx