Avalonia UI 设置器优先级

Avatar
不若风吹尘
2024-10-14T15:56:34
730
0

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 外直接设置时,会分配此值。下面两个 BackgroundSetter 都将具有 LocalValue 优先级。

<Button Background="Orange" />
<Button Background="{DynamicResource ButtonBrush}" />

提示

资源标记扩展对优先级没有影响。

StyleTrigger

当一个 Selector 具有条件激活时,SetterBindingPriority 会从 Style 提升到 StyleTrigger。任何具有条件激活的两个选择器都将具有相同的优先级,无论激活器的数量和在选择器语法中的位置如何。Avalonia 没有 CSS 中的特异性(Specificity)概念。

<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
    <Setter Property="Background" Value="Orange" />
</Style>

提示

样式类、伪类、子元素位置和属性匹配选择器是条件性的。控件名称选择器不是条件性的。

模板

当一个属性直接在 ControlTemplate 中设置时,下面的 BorderThicknessBackgroundPadding 具有 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);

视觉树局部性

具有相同 BindingPrioritySetter 会根据它们在视觉树中相对于 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 来诊断原因。

setter-precedence-animation-wrong-bf6087321628b9c23e605885907089c5.gif

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>

顶部的 SetterButtonBackground 应用于 Button,具有 Style 优先级。Background 的渲染由具有 Template 优先级的 ContentPresenter 处理,它会获取已应用于 ButtonButtonBackground

然而,当 Button 被悬停时,:pointerover 选择器被激活,其具有 StyleTrigger 优先级,覆盖了 TemplateBinding 并获取 ButtonBackgroundPointerOver 而不是原来的 ButtonBackground。这绕过了我们原始 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 来针对 ContentPresenterBindingPriority.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>

setter-precedence-animation-correct-a9869f936ae19fa4ce2aee53be489078.gif

Last Modification : 4/30/2025 5:24:57 PM


In This Document