Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Order in Chaos: Dependency Property Value Resolution

0.00/5 (No votes)
27 Dec 2010 1  
Order in Chaos: Dependency Property Value Resolution

One of the strong points for WPF is the ability to set a property value using a wide range of methods.

Unfortunately, this variety makes it hard to understand how a WPF dependency property gets its final value. Hopefully, this post will help organize this issue.

This post is based on the comprehensive MSDN article “Dependency Property Value Precedence”.

Long Story Short

Diagram of dependency property value resolution

Can You Show An Example Please?

In order to see this in action, let’s create a WPF application that has all these scenarios used.

For this to be possible, we will create a custom control named MyCustomControl and add to it a dependency property named MyValue which we’ll use for setting values from different places.

To see the actual value at runtime, we will present this property in a TextBlock. In fact, our custom control will have just that in his control template.

Step 1 – Default Value

First, we add our dependency property and set its default value to “1”.

C#
public string MyValue
{
    get { return (string)GetValue(MyValueProperty); }
    set { SetValue(MyValueProperty, value); }
}

public static readonly DependencyProperty MyValueProperty =
    		DependencyProperty.Register("MyValue", typeof(string), 
		typeof(MyCustomControl), new UIPropertyMetadata("1"));

After setting the default control template (Themes\Generic.xaml) as follows:

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo">
    <Style TargetType="{x:Type local:MyCustomControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                    <TextBlock 
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        FontSize="32"
                        Text="{TemplateBinding MyValue}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

And the main window to:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid>
        <local:MyCustomControl />
    </Grid>
</Window>

We get:

Image with the number 1

Step 2 – Inheritance

To support inheritance, we will change our dependency property into an attached property that can also be set using CLR property syntax.

In addition, we will add FrameworkPropertyMetadataOptions.Inherits option to the property registration to allow the value to be inherited.

So the property declaration now goes:

C#
public static string GetMyValue(DependencyObject obj)
{
    return (string)obj.GetValue(MyValueProperty);
}

public static void SetMyValue(DependencyObject obj, string value)
{
    obj.SetValue(MyValueProperty, value);
}

public static readonly DependencyProperty MyValueProperty =
    DependencyProperty.RegisterAttached
	("MyValue", typeof(string), typeof(MyCustomControl), 
    new FrameworkPropertyMetadata("1", FrameworkPropertyMetadataOptions.Inherits));

public string MyValue
{
    get { return GetMyValue(this); }
    set { SetMyValue(this, value); }
}

And its use in the main window:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid local:MyCustomControl.MyValue="2">
        <local:MyCustomControl />
    </Grid>
</Window>

Which results in:

Image with the number 2

Step 3 - Default (theme) Style

Adding a setter to the default style overrides the previous definition:

XML
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo">
    <Style TargetType="{x:Type local:MyCustomControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                    <TextBlock 
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        FontSize="32"
                        Text="{TemplateBinding MyValue}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="MyValue" Value="3" />
    </Style>
</ResourceDictionary>

So now we get:

Image with the number 3

Step 4 - Style setters

We can override the previous value by setting a style setter in the main window:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid local:MyCustomControl.MyValue="2">
        <local:MyCustomControl>
            <local:MyCustomControl.Style>
                <Style TargetType="local:MyCustomControl">
                    <Setter Property="MyValue" Value="4" />
                </Style>
            </local:MyCustomControl.Style>
        </local:MyCustomControl>
    </Grid>
</Window>

So now we get:

Image with the number 4

Step 5 - Template Triggers

Now we will override our default template and add a template trigger, which will act when IsEnabled = True (I’ve just selected the property arbitrarily).

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid local:MyCustomControl.MyValue="2">
        <local:MyCustomControl>
            <local:MyCustomControl.Style>
                <Style TargetType="local:MyCustomControl">
                    <Setter Property="MyValue" Value="4" />
                </Style>
            </local:MyCustomControl.Style>
            <local:MyCustomControl.Template>
                <ControlTemplate TargetType="local:MyCustomControl">
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="True">
                            <Setter Property="MyValue" Value="5" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                    <TextBlock 
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        FontSize="32"
                        Text="{TemplateBinding MyValue}" />
                </ControlTemplate>
            </local:MyCustomControl.Template>
        </local:MyCustomControl>
    </Grid>
</Window>

The result:

Image with the number 5

Step 6 - Style Triggers

Now we add a trigger to the explicit style we’ve added before:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid local:MyCustomControl.MyValue="2">
        <local:MyCustomControl>
            <local:MyCustomControl.Style>
                <Style TargetType="local:MyCustomControl">
                    <Setter Property="MyValue" Value="4" />
                    <Style.Triggers>
                        <Trigger Property="IsEnabled" Value="True">
                            <Setter Property="MyValue" Value="6" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </local:MyCustomControl.Style>
            <local:MyCustomControl.Template>
                <ControlTemplate TargetType="local:MyCustomControl">
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="True">
                            <Setter Property="MyValue" Value="5" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                    <TextBlock 
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        FontSize="32"
                        Text="{TemplateBinding MyValue}" />
                </ControlTemplate>
            </local:MyCustomControl.Template>
        </local:MyCustomControl>
    </Grid>
</Window>

The result:

Image with the number 6

Step 7 - Implicit Style

This step is brought here for completeness although we can’t use it to override the value of MyValue dependency property.

It is relevant ONLY if the dependency property you are trying to set is the Style property.

Step 8 - TemplatedParent Template Properties

OK, this is a tough one.

Here we set the value “8” (I skipped “7” to remain consistent with the step number) by setting the MyValue property on a MyCustomControl instance, BUT, only when it is generated as a template (by setting either a ControlTemplate or DataTemplate).

In the following code, I’m creating a ContentControl, only to set its control template (via the Template property) to an instance of MyCustomControl. On this instance of MyCustomControl, I set MyValue property to 8.

This appears to be setting a local value, but it really isn’t!

The reason is that this template can be shared across several controls. So the value is saved behind the scenes as part of the template properties. We will see in the next step that setting a true local value will override this. In the mean time, here is the code:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <Grid local:MyCustomControl.MyValue="2">
        <ContentControl x:Name="contentControl">
            <ContentControl.Template>
                <ControlTemplate TargetType="ContentControl">
                    <local:MyCustomControl MyValue="8" x:Name="myCustomControl">
                        <local:MyCustomControl.Style>
                            <Style TargetType="local:MyCustomControl">
                                <Setter Property="MyValue" Value="4" />
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="True">
                                        <Setter Property="MyValue" Value="6" />
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </local:MyCustomControl.Style>
                        <local:MyCustomControl.Template>
                            <ControlTemplate TargetType="local:MyCustomControl">
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsEnabled" Value="True">
                                        <Setter Property="MyValue" Value="5" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                                <TextBlock 
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    FontSize="32"
                                    Text="{TemplateBinding MyValue}" />
                            </ControlTemplate>
                        </local:MyCustomControl.Template>
                    </local:MyCustomControl>
                </ControlTemplate>
            </ContentControl.Template>
        </ContentControl>
    </Grid>
</Window>

And the result:

Image with the number 8

Step 9 - Local Value

In this step, I’ll show you how I can override the previous template value by setting a local value from a button click handler. To prove that I’m not setting the same thing as in step 8, I’ll add another button that will clear the value, in which case the older value (“8”) will be restored.

So following is the code for main window, I’ve only added two buttons and replaced the main Grid with a StackPanel:

XML
<Window x:Class="DependencyPropertyValueResolutionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DependencyPropertyValueResolutionDemo"
        Title="MainWindow" Height="150" Width="150">
    <StackPanel local:MyCustomControl.MyValue="2">
        <ContentControl x:Name="contentControl">
            <ContentControl.Template>
                <ControlTemplate TargetType="ContentControl">
                    <local:MyCustomControl MyValue="8" x:Name="myCustomControl">
                        <local:MyCustomControl.Style>
                            <Style TargetType="local:MyCustomControl">
                                <Setter Property="MyValue" Value="4" />
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="True">
                                        <Setter Property="MyValue" Value="6" />
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </local:MyCustomControl.Style>
                        <local:MyCustomControl.Template>
                            <ControlTemplate TargetType="local:MyCustomControl">
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsEnabled" Value="True">
                                        <Setter Property="MyValue" Value="5" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                                <TextBlock 
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    FontSize="32"
                                    Text="{TemplateBinding MyValue}" />
                            </ControlTemplate>
                        </local:MyCustomControl.Template>
                    </local:MyCustomControl>
                </ControlTemplate>
            </ContentControl.Template>
        </ContentControl>

        <Button Content="set local value" Click="Set_Local_Click" />
        <Button Content="clear local value" Click="Clear_Local_Click" />
    </StackPanel>
</Window>

Following is the implementation of the buttons. In order to find the generated MyCustomControl, I use the method FrameworkTemplate.FindName.

Note that I don’t set the value “8” in the code, instead I use ClearValue and it gets restored since the value is saved inside the template definition.

C#
private void Set_Local_Click(object sender, RoutedEventArgs e)
{
    MyCustomControl myCustomControl = (MyCustomControl)contentControl.Template.FindName
				("myCustomControl", contentControl);
    myCustomControl.MyValue = "9";
}

private void Clear_Local_Click(object sender, RoutedEventArgs e)
{
    MyCustomControl myCustomControl = (MyCustomControl)contentControl.Template.FindName
				("myCustomControl", contentControl);
    myCustomControl.ClearValue(MyCustomControl.MyValueProperty);
}

The result now, after clicking on “set local value” looks like this:

Image with the number 9

Step 10- Animation

OK, this one is easy.

To set a value using animation, we’ll add a storyboard to the window resources:

XML
<Window.Resources>
    <Storyboard x:Key="animateValue">
        <StringAnimationUsingKeyFrames Storyboard.TargetProperty="MyValue">
            <DiscreteStringKeyFrame KeyTime="0:0:1" Value="10" />
        </StringAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>

And when the “animate value” button is clicked, we will start the animation:

C#
private void Animate_Click(object sender, RoutedEventArgs e)
{
    MyCustomControl myCustomControl = (MyCustomControl)contentControl.Template.FindName
				("myCustomControl", contentControl);

    Storyboard animateValue = (Storyboard)FindResource("animateValue");
    animateValue.Begin(myCustomControl);
}

The result:

Image with the number 10

Step 11 - Property Coercion

The last option for changing a dependency property value, which has the highest precedence is to use value coercion.

To use value coercion, we just add a value coercion callback when registering the dependency property:

C#
public static readonly DependencyProperty MyValueProperty =
    DependencyProperty.RegisterAttached("MyValue", 
		typeof(string), typeof(MyCustomControl),
    new FrameworkPropertyMetadata("1", FrameworkPropertyMetadataOptions.Inherits, 
		MyValueChanged, MyValueCoerceValue));

private static void MyValueChanged(DependencyObject d, 
		DependencyPropertyChangedEventArgs e)
{ 
}

private static object MyValueCoerceValue(DependencyObject d, object baseValue)
{
    return "11";
}

And the last result:

Image with the number 11

Now nothing can change our dependency property value!

Summary

We have seen 11 ways to affect a dependency property value, each time overriding the previous value. Hopefully, you have learned something new about dependency property value resolution.

You can download the full source code for this demo here.

That’s it for now,
Arik Poznanski.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here