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
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
”.
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:
<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:
<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:
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:
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:
<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:
Step 3 - Default (theme) Style
Adding a setter to the default style overrides the previous definition:
<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:
Step 4 - Style setters
We can override the previous value by setting a style setter in the main window:
<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:
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).
<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:
Step 6 - Style Triggers
Now we add a trigger to the explicit style we’ve added before:
<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:
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:
<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:
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
:
<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.
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:
Step 10- Animation
OK, this one is easy.
To set a value using animation, we’ll add a storyboard
to the window resources:
<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:
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:
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:
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:
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.