3

In the list of sources for dependency property values, along with the relative precedence, a local value is highest precedence for determining the base value.
Immediately after the local value are template properties from the templated parent--e.g. the parent ControlTemplate for a control that was created in the context of a template.

My question is this--since a local value is listed as taking precedence over a template property, does this mean that a property value set explicitly (local value) on a control created in the template takes precedence over one set with a property trigger in that template? That seems to be what the rules imply, but properties set with triggers in a control template seem to override (i.e. higher precedence) local values set in the template.

Or does the "local value" in the precedence list refer to values set only on controls NOT created in templates--and therefore you can't really compare precedence between a local value and a property set via a trigger in the templated parent?

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
Sean Sexton
  • 1,069
  • 2
  • 11
  • 19

1 Answers1

4

Yes, the "local value" in the precedence list only refers to properties set on elements outside of templates. The relevant part of the precedence list is 4b:

4. TemplatedParent template properties. An element has a TemplatedParent if it was created as part of a template (a ControlTemplate or DataTemplate). For details on when this applies, see TemplatedParent later in this topic. Within the template, the following precedence applies:

a. Triggers from the TemplatedParent template.

b. Property sets (typically through XAML attributes) in the TemplatedParent template.

Property values set within the template are a lower precedence than triggers in the template, and both of these are lower precedence than local values.

You can see how a value was set by calling DependencyPropertyHelper.GetValueSource and checking the BaseValueSource property. Values set outside of a template will have a source of "Local" while values inside of a template will have a source of "ParentTemplate".

Having them as separate sources also means that the property system can keep track of the local value and the parent template value separately. If you set a local value on a property that has a value from a template and later call ClearValue, it will revert to the value set by the template.


Here is an example that demonstrates a local value overriding a value from the template. Create a UserControl with the code below and add it to a Window. It has a blue rectangle that changes to green when the mouse is anywhere over the control. If you click "Set", the code will set a local value on the rectangle that will override both values. If you click "Clear", it will clear the local value and restore the values from the template. You can click "Display" to see the current value source (you will need to press the button with the keyboard to see ParentTemplate since having the mouse over the button will set off the trigger).

XAML:

<UserControl
    x:Class="WpfApplication1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Template>
        <ControlTemplate>
            <StackPanel Background="Transparent">
                <Button Click="Display_Click" Content="Display"/>
                <Button Click="Set_Click" Content="Set"/>
                <Button Click="Clear_Click" Content="Clear"/>
                <Rectangle Width="100" Height="100"
                           Fill="Blue" Name="PART_Rectangle"/>
            </StackPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="PART_Rectangle"
                            Property="Fill" Value="Green"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

Code-behind:

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        rectangle = Template.FindName("PART_Rectangle", this) as Rectangle;
    }

    private Rectangle rectangle;

    private void Display_Click(object sender, RoutedEventArgs e)
    {
        var source = DependencyPropertyHelper.GetValueSource(
            rectangle, Rectangle.FillProperty);
        MessageBox.Show(string.Format("Value {0}; Source {1}",
            rectangle.Fill, source.BaseValueSource));
    }

    private void Set_Click(object sender, RoutedEventArgs e)
    {
        rectangle.Fill = Brushes.Red;
    }

    private void Clear_Click(object sender, RoutedEventArgs e)
    {
        rectangle.ClearValue(Rectangle.FillProperty);
    }
}
Quartermeister
  • 57,579
  • 7
  • 124
  • 111
  • Thanks for the answer. "both of these are lower precedence than local values"--I need to play around some more with some examples, but from everything I've tried so far, the value set via property trigger in the template seems to be overriding the local value on the element outside of the template. I.e. I can't seem to repro the scenario where 4a or 4b (template properties) are lower precedence than 3 (local value). The template property values are always overwriting the local value. – Sean Sexton Nov 21 '10 at 04:20
  • @Sean: I added a simple example to my answer that will demonstrate the higher precedence of local values. – Quartermeister Nov 21 '10 at 14:55
  • Thanks--great detail and your example makes it easy to see the precedence between the three--your initial Blue color has source of ParentTemplate, becomes ParentTemplateTrigger when you change via trigger, and then becomes Local when you set via code. – Sean Sexton Nov 22 '10 at 19:55
  • I hadn't thought of trying to set the prop via code to see an example of a local value. I was trying to set via XAML, but in XAML, any prop set in the template itself isn't considered a local value, but source is ParentTemplate. Then trying to set a prop on the parent element to which the template is applied doesn't really work because that parent element gets replaced entirely by the child elements in the template. – Sean Sexton Nov 22 '10 at 19:59
  • E.g. If instead of a UserControl, you started with a Button and applied a template to the Button, you'd lose any local props set on the parent Button unless you explicitly pulled value into the template with a TemplateBinding expression--but then you're not really talking about the same property at all, just copying the outer/parent prop value into the inner/child. Anyway, thanks again--good example. – Sean Sexton Nov 22 '10 at 20:00
  • Ahhh, just got stung by this. So, how can I set a default-value of a DependencyProperty using a CLR setter (say in the constructor of my type) but have a *lower* priority than template bindings? I imagine the answer is "You can't!" – Dr. Andrew Burnett-Thompson Nov 01 '12 at 11:12
  • @Dr. ABT: If you want to set a default value, try something like http://stackoverflow.com/questions/5653364/how-can-i-change-the-default-value-of-an-inherited-dependency-property ... if that's not what you need then you probably want to open a new question so that more people will see it. – Quartermeister Nov 01 '12 at 15:44
  • I've done some googling and I dont think what I want is possible. The Q above is related, but I'm talking about setting reference types, e.g. ObservableCollection as a default value. You can'd do that as dep-prop defaults as they get shared across different instances of the control. In WPF you can use a style setter which has a nice low priority (just one above DefaultValue), but it must be DynamicResource. In Silverlight DynamicResource doesn't exist! I know realise why ItemsControl has Items (collection, initialised to empty) and ItemsSource (null, for MVVM binding) – Dr. Andrew Burnett-Thompson Nov 01 '12 at 16:39
  • @Quartermeister I created a question here - http://stackoverflow.com/questions/13181788/wpf-dependency-property-precedence-where-default-value-of-dependencyproperty-is Still think the answer is this isn't possible. Might have to come at this at a different angle – Dr. Andrew Burnett-Thompson Nov 01 '12 at 16:51