Windows Presentation Foundation (WPF) 属性系统的工作会影响依赖属性的值。 本文介绍 WPF 属性系统中不同基于属性的输入的优先级如何确定依赖属性的有效值。
先决条件
本文假设对依赖属性有一个基本的了解,并且你已阅读 依赖项属性概述。 若要遵循本文中的示例,如果熟悉可扩展应用程序标记语言(XAML),并且知道如何编写 WPF 应用程序,则很有帮助。
WPF 属性系统
WPF 属性系统使用各种因素来确定依赖属性的值,例如实时属性验证、后期绑定和相关属性的属性更改通知。 尽管用于确定依赖属性值的顺序和逻辑很复杂,但学习有助于避免不必要的属性设置,并找出为何尝试设置依赖属性不会导致预期值。
在多个位置设置的依赖项属性
以下 XAML 示例演示了如何通过三个不同的“设置”操作对按钮的Background属性进行修改,从而影响其值。
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</StackPanel.Resources>
<Button Template="{StaticResource ButtonTemplate}" Background="Red">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
Which color do you expect?
</Button>
</StackPanel>
在此示例中,属性 Background
在本地设置为 Red
. 但是,按钮作用域中声明的隐式样式会尝试将 Background
属性 Blue
设置为 。 并且,当鼠标悬停在按钮上时,隐式样式中的触发器将尝试将 Background
属性设置为 Yellow
。 除强制和动画外,本地设置的属性值具有最高优先级,因此按钮将为红色,即使在鼠标悬停时也是如此。 但是,如果从按钮中删除本地设置值,它将从样式中获取其 Background
值。 在样式中,触发器优先,因此按钮将在鼠标悬停时为黄色,否则为蓝色。 该示例替换按钮的默认值 ControlTemplate ,因为默认模板具有硬编码的鼠标悬停 Background
值。
依赖属性优先级列表
以下列表是属性系统将运行时值分配给依赖属性时使用的明确优先级顺序。 首先列出了最高优先级。
属性系统强制。 有关强制的详细信息,请参阅 强制和动画。
活动动画或具有保持行为的动画。 若要产生实际效果,动画值必须优先于基本值(未修改)值,即使在本地设置基值也是如此。 有关详细信息,请参阅 强制和动画。
本地值。 可以通过“包装器”属性来设置本地值,这等同于在 XAML 中设置属性或属性元素,或者通过使用特定实例的属性调用 SetValue API。 通过绑定或资源设置的本地值将具有与直接设置的值相同的优先级。
TemplatedParent 模板属性值。 如果元素是由模板(
或< c2 />)创建的,它将具有 。 有关详细信息,请参阅 TemplatedParent。 在模板 TemplatedParent
中指定的优先级顺序为:触发器。
属性集,通常通过 XAML 属性。
隐式样式。 仅适用于 Style 属性。 该值
Style
是具有 TargetType 与元素类型匹配的值的任何样式资源。 样式资源必须存在于页面或应用程序中。 查找隐式样式资源不会扩展到主题中的样式资源。样式触发器。 样式触发器是显式或隐式样式中的触发器。 样式必须存在于页面或应用程序中。 默认样式中的触发器的优先级较低。
模板触发器。 模板触发器可以是直接应用的模板中的触发器,也可以是样式中的模板触发器。 样式必须存在于页面或应用程序中。
样式设置器值。 样式设置器值是通过 Setter 应用在样式中的值。 样式必须存在于页面或应用程序中。
默认样式,也称为 主题样式。 有关详细信息,请参阅 默认(主题)样式。 在默认样式中,优先级顺序为:
活动触发器。
设定者
继承。 子元素的某些依赖属性从父元素继承其值。 因此,可能不需要在整个应用程序中为每个元素设置属性值。 有关详细信息,请参阅 属性值继承。
依赖属性元数据中的默认值 依赖属性可以在此属性的属性值系统注册过程中设置默认值。 继承依赖属性的派生类可以针对每个类型覆盖依赖属性元数据(包括默认值)。 有关详细信息,请参阅 Dependency 属性元数据。 对于继承的属性,父元素的默认值优先于子元素的默认值。 因此,如果未设置可继承属性,则使用根或父元素的默认值,而不是子元素的默认值。
TemplatedParent
TemplatedParent 优先级不适用于在标准应用程序标记中直接声明的元素的属性。 概 TemplatedParent
念仅适用于在可视化树中通过模板应用而生成的子项。 当属性系统搜索由 TemplatedParent
元素的属性值指定的模板时,它会搜索创建该元素的模板。 模板中的 TemplatedParent
属性值通常与元素上的本地设置值一样,但优先级低于实际本地值,因为模板可能会共享。 有关详细信息,请参阅 TemplatedParent。
Style 属性
相同的优先级顺序适用于所有依赖属性,除了 Style 属性。 该 Style
属性是唯一的,因为它本身无法设置样式。 不建议对Style
属性进行强制处理或动画化(并且对Style
属性进行动画化需要自定义动画类)。 因此,并非所有优先项都适用。 设置Style
属性的方法只有三种:
显式样式。 元素的
Style
属性被直接设置。 属性值Style
的作用类似于它是本地值,并且优先级列表中的项 3 具有相同的 优先级。 在大多数情况下,显式样式未内联定义,而是显式引用为资源,例如Style="{StaticResource myResourceKey}"
。隐式样式。 元素的
Style
属性未被直接设置。 相反,样式在页面或应用程序中存在某个级别时应用,并且具有与样式所适用的元素类型匹配的资源键,例如<Style TargetType="x:Type Button">
。 该类型必须完全匹配,例如<Style TargetType="x:Type Button">
,即使MyButton
派生自Button
类型,也不会应用于MyButton
类型。 属性值Style
与 优先列表中的项 5 具有相同的优先级。 可以通过调用 DependencyPropertyHelper.GetValueSource 方法、传入Style
属性以及检查ImplicitStyleReference
结果来检测隐式样式值。默认样式,也称为 主题样式。 元素的
Style
属性未被直接设置。 相反,它来自 WPF 演示文稿引擎的运行时主题评估。 在运行时之前,Style
属性值为null
. 属性值Style
与 优先列表中的项 9 具有相同的优先级。
默认(主题)样式
随 WPF 附带的每个控件都有一个默认样式,该样式可能因主题而异,这就是为什么默认样式有时称为 主题样式的原因。
这是 ControlTemplate 控件的默认样式中的一个重要项。
ControlTemplate
是样式的 Template 属性的 setter 值。 如果默认样式不包含模板,则没有自定义模板的控件作为自定义样式的一部分将没有视觉外观。 模板不仅定义控件的视觉外观,还定义模板可视化树中属性与相应控件类之间的连接。 每个控件都会公开一组属性,这些属性可以影响控件的视觉外观,而无需替换模板。 例如,考虑控件的默认视觉外观 Thumb ,该控件是一个 ScrollBar 组件。
控件 Thumb 具有某些可自定义属性。 控件的默认模板Thumb
创建了一个基本结构或可视化树,其中包含多个嵌套的Border组件,以形成斜面外观。 在模板中,可以由Thumb
类自定义的属性通过TemplateBinding呈现。
默认样式在其定义中指定 TargetType 。 运行时主题评估将 TargetType
默认样式与 DefaultStyleKey 控件的属性匹配。 相比之下,隐式样式的查找行为依赖于控件的具体类型。 其值 DefaultStyleKey
由派生类继承,因此可能没有关联样式的派生元素获得默认视觉外观。 例如,如果派生MyButton
自Button,MyButton
将继承Button
的默认模板。 派生类可以替代依赖属性元数据中的默认值 DefaultStyleKey
。 因此,如果您希望为MyButton
提供不同的视觉表示形式,可以在MyButton
上覆盖依赖属性元数据DefaultStyleKey
,然后定义相关的默认样式,包括一个模板,并将其与MyButton
控件一起打包。 有关详细信息,请参阅 控件创作概述
动态资源
动态资源引用在技术上不是属性系统的一部分,并且具有与 优先列表交互的自己的查找顺序。 从本质上讲,动态资源引用的优先级是:元素到页面根节点,然后是应用程序、主题,最后是系统。 有关详细信息,请参阅 XAML 资源。
尽管动态资源引用和绑定在设置时具有优先级,但其应用会被延迟。 其一个后果是,如果将动态资源或绑定设置为本地值,则对本地值所做的任何更改将完全替换动态资源或绑定。 即使调用 ClearValue 此方法来清除本地集值,也不会还原动态资源或绑定。 事实上,如果对一个具有动态资源或绑定、且没有文字形式的本地值的属性调用 ClearValue
,则该动态资源或绑定将被清除。
设置当前值
该方法 SetCurrentValue 是设置属性的另一种方法,但它不在 优先列表中。
SetCurrentValue
允许你更改属性的值,而无需覆盖之前值的来源。 例如,如果某个属性由触发器设置,然后使用 SetCurrentValue
分配另一个值,则下一个触发器动作会将该属性设置回触发器值。 每当想要设置属性值时,都可以使用 SetCurrentValue
,而无需为该值提供本地值的优先级别。 类似地,你可以使用 SetCurrentValue
更改属性的值,而无需覆盖绑定。
强制和动画
强制和动画都作用于 基值。 基值是优先级最高的依赖属性值,通过优先列表向上计算,直到达到第 2 项为止。
如果动画没有为某些行为指定 From 和 To 属性值,或者动画在完成后故意恢复到基值,那么基值可能会影响动画值。 若要在实践中了解这一点,请运行 目标值 示例应用程序。 在示例中,对于矩形高度,请尝试设置与任何 From
值不同的初始本地值。 示例动画使用 From
值而不是基值立即开始。 通过指定 Stop 为动画 FillBehavior,动画将在完成时将属性值重置为其基值。 正常优先级用于动画结束后的基值确定。
可以将多个动画应用于单个属性,每个动画具有不同的优先级。 与其应用优先级最高的动画,WPF 的呈现引擎可能会根据动画定义的方式以及要动画的值类型来组合动画值。 有关详细信息,请参阅 动画概述
强制位于 优先列表的顶部。 即使是正在运行的动画也会受到值强制约束。 WPF 中的一些现有依赖项属性具有内置强制。 对于自定义依赖属性,可以通过编写一个作为元数据的一部分传递的函数来定义强制行为,此函数会在创建属性时使用。 还可以通过重写派生类中该属性的元数据来替代现有属性的强制行为。 在强制与基值交互时,强制的约束会在当时的状态下被应用,但基值仍然会保留。 因此,如果以后解除强制约束,强制将返回可能与基值最近的值,并且一旦解除所有约束,对属性的强制影响可能会立即停止。 有关强制行为的详细信息,请参阅 Dependency 属性回调和验证。
触发器行为
控件通常将触发器行为定义为其 默认样式的一部分。 在控件上设置本地属性可能会与这些触发器冲突,从而阻止触发器以视觉方式或行为方式响应用户驱动事件。 属性触发器的常见用途是控制状态属性,例如 IsSelected 或 IsEnabled。 例如,默认情况下,当禁用 Button 时,主题样式触发器 (IsEnabled
是 false
) 设置 Foreground 值以使 Button
显为灰色。如果您已设置本地 Foreground
值,则优先级较高的本地属性值即使在 Button
被禁用时也将覆盖主题样式的 Foreground
值。 设置替代控件主题级触发器行为的属性值时,请注意不要过度干扰该控件的预期用户体验。
ClearValue
该方法 ClearValue 清除元素依赖属性的任何本地设置的值。 但是,调用 ClearValue
不保证在属性注册期间在元数据中建立的默认值是新的有效值。
优先列表中的所有其他参与者仍然处于活动状态,并且仅删除本地设置值。 例如,如果调用 ClearValue
具有主题样式的属性,主题样式值将作为新值应用,而不是基于元数据的默认值。 如果要将属性值设置为已注册的元数据默认值,则通过查询依赖属性元数据来获取默认元数据值,并使用调用 SetValue本地设置属性值。