Avalonia 的 Setters
按照 BindingPriority
的顺序应用,然后是视觉树的局部性,最后是 Styles
集合中的顺序。优先级针对每个 StyledProperty
单独应用,以便样式可以从组合中受益。DirectProperty
和 CLR 属性不能被设置样式,因此不参与此优先级。
BindingPriority 值
Animation = -1, // Highest priority
LocalValue = 0,
StyleTrigger,
Template,
Style,
Inherited,
Unset = int.MaxValue, // Lowest priority
XAML 中的 BindingPriority 如何分配?
BindingPriority
不能在 XAML 中显式设置。以下示例演示了 BindingPriority
在不同场景中是如何隐式分配的。这对于设计和排查按预期工作的样式至关重要。
动画
Animation
拥有最高的 BindingPriority
,并应用于 Keyframe
内的 Setter
和整个过渡系统。
<Button Background="Green" Content="Bounces from Red to Blue">
<Button.Styles>
<Style Selector="Button">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background" Value="Blue" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Button.Styles>
</Button>
LocalValue
当一个 XAML 属性在 ControlTemplate
外直接设置时,会分配此值。下面两个 Background
的 Setter
都将具有 LocalValue
优先级。
<Button Background="Orange" />
<Button Background="{DynamicResource ButtonBrush}" />
提示
资源标记扩展对优先级没有影响。
StyleTrigger
当一个 Selector
具有条件激活时,Setter
的 BindingPriority
会从 Style
提升到 StyleTrigger
。任何具有条件激活的两个选择器都将具有相同的优先级,无论激活器的数量和在选择器语法中的位置如何。Avalonia 没有 CSS 中的特异性(Specificity)概念。
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Orange" />
</Style>
提示
样式类、伪类、子元素位置和属性匹配选择器是条件性的。控件名称选择器不是条件性的。
模板
当一个属性直接在 ControlTemplate
中设置时,下面的 BorderThickness
、Background
和 Padding
具有 Template
优先级。
<ControlTemplate>
<Border BorderThickness="2">
<Button Background="{DynamicResource ButtonBrush}" Padding="{TemplateBinding Padding}" />
</Border>
</ControlTemplate>
样式
当在 Style
中定义了一个 Setter
而无需条件激活时。
<Style Selector="Button /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Orange" />
</Style>
提示
特别需要注意的是,其优先级低于
Template
。因此,这些选择器不能用于覆盖上面Template
示例中提到的属性。
继承
当一个属性没有被设置时,它可能会从其父级继承属性值。这必须在注册属性时或通过 OverrideMetadata
来指定。
public static readonly StyledProperty<bool> UseLayoutRoundingProperty =
AvaloniaProperty.Register<Layoutable, bool>(
nameof(UseLayoutRounding),
defaultValue: true,
inherits: true);
视觉树局部性
具有相同 BindingPriority
的 Setter
会根据它们在视觉树中相对于 Control
的位置来选择。所需跨越的节点数最少的 Setter
将具有优先权。内联样式的 Setter
在此步骤中具有最高优先级。
<Window>
<Window.Styles>
<Style Selector="Button">
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="Red" />
</Style>
</Window.Styles>
<StackPanel>
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="FontSize" Value="24" />
</Style>
</StackPanel.Styles>
<Button Content="This Has FontSize=24 with Foreground=Red" />
</StackPanel>
</Window>
样式集合顺序
当 BindingPriority
和视觉树局部性都相等时,最终的决定因素是 Styles
集合中的顺序。最后一个适用的 Setter
将具有优先权。
<StackPanel>
<StackPanel.Styles>
<Style Selector="Button.small">
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="Button.big">
<Setter Property="FontSize" Value="24" />
</Style>
</StackPanel.Styles>
<Button Classes="small big" Content="This Has FontSize=24" />
<Button Classes="big small" Content="This Also Has FontSize=24" />
</StackPanel>
提示
这些按钮以不同的顺序指定了它们的样式类,但这对设置优先级没有影响。
BindingPriority 不会传递
回顾上面的 Animation
示例。当你悬停时,动画背景会被静态背景替换,尽管 BindingPriority.Animation
有最高优先级。这是因为 Selector
定位到了错误的 Control
。需要检查 ControlTheme
来诊断原因。
Button 的简化 ControlTheme:
<ControlTheme x:Key="{x:Type Button}" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}"/>
</Style>
</ControlTheme>
顶部的 Setter
将 ButtonBackground
应用于 Button
,具有 Style
优先级。Background
的渲染由具有 Template
优先级的 ContentPresenter
处理,它会获取已应用于 Button
的 ButtonBackground
。
然而,当 Button
被悬停时,:pointerover
选择器被激活,其具有 StyleTrigger
优先级,覆盖了 TemplateBinding
并获取 ButtonBackgroundPointerOver
而不是原来的 Button
的 Background
。这绕过了我们原始 Animation
选择器所针对的 Background
。以下表格总结了这一点:
悬停时的 Background 设置和样式 | 优先级 | 位置 |
---|---|---|
~~Background="Green"~~ | LocalValue | Button |
Background="Red" | Animation (覆盖 LocalValue) | Keyframe |
~~<ContentPresenter Background="{TemplateBinding Background}"/> ~~ |
Template | ControlTemplate |
^:pointerover /template/ ContentPresenter#PART_ContentPresenter |
StyleTrigger (覆盖 Template) | ControlTheme |
相反,我们应该使用至少具有 StyleTrigger
优先级的 Setter
来针对 ContentPresenter
。BindingPriority.Animation
符合这一要求。这是一个不检查原始 ControlTemplate
就无法得出的观察结果,并强调仅依赖优先级不足以有效地对应用程序进行样式设置。
修正以覆盖 :pointerover 优先级
<Button Background="Green" Content="Bounces from Red to Blue">
<Button.Styles>
<Style Selector="Button /template/ ContentPresenter#PART_ContentPresenter">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background" Value="Blue" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Button.Styles>
</Button>
Comments