WPF依赖属性优先级 - 模板父级

3
在依赖属性值的来源列表中,列出了相对优先级,本地值是确定基础值的最高优先级。
紧接着本地值的是来自模板父级的模板属性——例如,在模板上下文中创建的控件的父级ControlTemplate。
我的问题是这样的——既然本地值被列为优先于模板属性,那么这是否意味着在模板中设置显式(本地)值的属性值优先于使用属性触发器在该模板中设置的属性值?规则似乎是这样暗示的,但在控件模板中使用触发器设置的属性似乎会覆盖(即具有更高的优先级)在模板中设置的本地值。
或者说,优先级列表中的“本地值”仅指在未在模板中创建的控件上设置的值吗?因此,您无法真正比较本地值和通过模板父级触发器设置的属性之间的优先级?
1个回答

4
是的,在优先级列表中,“本地值”只涉及模板外设置的元素属性。相关的优先级列表部分是4b:
4. TemplatedParent模板属性。如果一个元素作为模板(ControlTemplate或DataTemplate)的一部分创建,则具有TemplatedParent。关于何时应用此内容,请参见本主题后面的TemplatedParent。在模板内,以下优先级适用:
a. TemplatedParent模板中的触发器。
b. TemplatedParent模板中的属性设置(通常通过XAML属性)。
模板内设置的属性值优先级低于模板中的触发器,这两者的优先级都低于本地值。
您可以通过调用DependencyPropertyHelper.GetValueSource并检查BaseValueSource属性来查看值是如何设置的。在模板外设置的值将具有“Local”来源,而在模板内设置的值将具有“ParentTemplate”来源。
将它们作为单独的源也意味着属性系统可以分别跟踪本地值和父模板值。如果您在具有来自模板的值的属性上设置了本地值,然后稍后调用ClearValue,它将恢复到由模板设置的值。
这是一个演示本地值覆盖模板值的示例。创建一个带有以下代码的UserControl并将其添加到窗口中。它有一个蓝色矩形,当鼠标在控件上时会变成绿色。如果单击“设置”,代码将在矩形上设置一个本地值,该值将覆盖两个值。如果单击“清除”,它将清除本地值并恢复来自模板的值。您可以单击“显示”以查看当前值源(您需要使用键盘按下按钮才能看到ParentTemplate,因为将鼠标悬停在按钮上会触发)。
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>

后台代码:

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);
    }
}

谢谢你的回答。“这两个都比本地值优先级低” - 我需要再尝试一些例子,但从目前为止我尝试过的所有内容来看,在模板中通过属性触发器设置的值似乎会覆盖模板外元素的本地值。也就是说,我似乎无法重现4a或4b(模板属性)优先级低于3(本地值)的情况。模板属性值总是覆盖本地值。 - Sean Sexton
@Sean:我在我的回答中添加了一个简单的示例,可以证明本地值的优先级更高。 - Quartermeister
非常感谢您提供的详细信息,您的示例使我们能够清楚地了解了这三种情况之间的优先顺序。最初的蓝色源于ParentTemplate,当您通过触发器进行更改时,它会变为ParentTemplateTrigger,然后当您通过代码进行设置时,它就会变成Local。 - Sean Sexton
如果你使用一个按钮并应用了一个模板,而不是一个用户控件,那么除非你使用TemplateBinding表达式将值显式地传递到模板中,否则你将失去在父按钮上设置的任何本地属性--但这时你实际上并没有谈论同一个属性,只是将外部/父级属性值复制到内部/子级。无论如何,再次感谢--这是一个很好的例子。 - Sean Sexton
@Quartermeister 我在这里创建了一个问题 - http://stackoverflow.com/questions/13181788/wpf-dependency-property-precedence-where-default-value-of-dependencyproperty-is仍然认为答案是这不可能。可能需要从不同的角度来解决这个问题。 - Dr. Andrew Burnett-Thompson
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接