jk's notes
  • 样式与属性触发器

样式与属性触发器

用资源来控制控件的外观:

  • 资源数据多
  • 设置繁琐, 需要一一绑定

Style 是一种特殊资源, 可以减少冗余的代码.

简单资源

为不同的控件提供统一的外观, 使用资源就可以完成. 作者给出一个示例加以说明. 同时为了说明代码中大量的绑定, 维护变得困难.

样式的处理方式与一般的资源不同: 资源是定义值与其标识符(名字), 样式则是定义 属性 与 值, 即将什么属性设置为什么值, 然后统一打包到 Style 标签中 (使用 Setter 标签). 然后将其放到资源中. 置于在那个控件的资源中, 就看需要它在哪里可以被访问到了, 遵循树节点继承的规则 (作用域, 子节点继承父节点特性).

最后在需要使用该样式的控件中使用 Style 属性来绑定样式.

  • Style 可以有 x:Key 属性来定义名字.
  • Style 可以使用 TargetType 来指定什么类型的控件会使用该样式.
  • Style 中使用 Setter 标签来定义属性的值. 属性名使用 Property 属性来指定, 属性值使用 Value 来指定, 它可以是简单值, 也可以以标签属性的形式来定义复杂值.
  • Style 中使用 EventSetter 标签来定义事件.

例如, 定义样式可以是:

image-20250721114504035

使用样式可以是:

image-20250721114516570

指定 TargetType 时无法简单的从字符串来时, 可以使用 x:Type 标记扩展:

<Style x:Key="..." TargetType="{x:Type 类型}">
  ...
</Style>

然后作者给出了一个更为复杂的示例, 在 Window 中定义了用于 Grid, Rectangle, 以及 TextBlock 的样式.

补充. 传统 WinForm 只需要关注哪些被修改的属性, 所有默认属性都会被不分类封装, 由设计器生成. 但是在 XAML 中不能, 它需要用标签的方式把属性列出来, 这对于阅读并不友好 (不同于 WinForm 有针对性), 但是使用 Style 就可以实现这个功能了, 可以将不变的属性打包到 Style 中, 只关注改变的属性.

Key 与 Target 类型

KEYS AND TARGET TYPES

Style 的 x:Key 和 TargetType 用于描述什么时候在什么地方会用到该样式. 这里存在三种情况:

  • 非特定目标类型
  • 多目标类型
  • 未命名样式

非特定目标类型

Style 的 TargetType 决定了该样式什么类型的控件可以使用, 为了可用于多个控件, 可以使用对应控件的父类型.

例如 TargetType="Button" 只有 Button 可以使用, TargetType="Label" 则只有 Label 可以使用, 但是设置为 TargetType="Control" 则所有 Control 及其子类均可使用.

这里存在一个特殊机制, 就是不同子类存在不同的独有属性, 那么使用父类来写 Style, 势必存在特定子类的特定属性无法设置的问题. 因此在 WPF 中有一个机制, 可以将需要的属性都写到 Style 中, 子类无法识别的属性会被自动忽略掉.

多目标类型

如果想要将样式作用到更多控件上, 可以再往上设置父类, 但是也存在问题. 部分父类存在无法支持的情况.

  • 那么可以考虑不去设置 TargetType
  • 而是在 Setter 中设置 Property 的使用使用属性的全名来控制. 例如 Property="Label.BorderBrush".

未命名样式

如果定义 Style 的时候指定了 TargetType, 而未指定 x:Key, 那么样式将作用与 TargetType 描述的所有控件上.

但是需要注意的是 Style 所在资源的作用域, 仅会作用域其作用域下的所有目标类型控件.

属性值的优先级

控件的属性可以有多种设置方式, 一旦出现冲突 (多种方式设置了一个属性), 那么就需要考虑优先级了.

  1. 从容器继承.
  2. 默认样式.
  3. 未命名样式.
  4. 具名样式.
  5. 本地设置值.
  6. 激活的动画. 在动画过程中设置的属性值有较高优先级.

补充, 如果设置了匿名样式, 作用域某特定类型控件, 但是又需要某个控件不设置该样式, 可以使用 Style="{x:Null}" 来清除样式.

样式继承

资源允许将属性值作用与多个控件中. 样式允许你将资源打包到一个 Style 中, 然后作用于控件.

  • 样式继承是指, 基于某个样式来定义新的样式, 新样式自动具备旧样式的特征 (传统继承观点).
  • 同时, 样式继承可以对父样式的某些特性来重写, 实现类似于 OOP 中重写的特性.
  • 语法实现:
    • 父样式提供 x:Key="父样式名"
    • 子样式开始标签使用 BasedOn="{StaticResource 父样式名}"
    • 子样式要重写父样式的某些属性, 直接提供同名的即可.

需要注意的是, 样式可以基于继承来创建, 但也必须满足 TargetType 的约束, 即子类样式的 TargetType 需要是父类样式 TargetType 的子类.

看起来是 样式与 TargetType 具有相同的包含等次关系.

在没有提供 TargetType 时, 可以更加灵活. 允许子样式指定 TargetType.

适用样式继承, 可以实现跨窗口, 甚至是跨应用, 样式一致的应用.

TRIGGER (触发器)

Style 中 Setter 定义的是静态的样式, WPF 会默认加载, 如果需要响应一些交互, 例如鼠标悬浮时改变样式, 那么可以定义 Trigger 来实现. WPF 也提供了三种不同的 Trigger

  • 属性 Trigger, 本节介绍.
  • 事件 Trigger, 与动画相关, ch14 中介绍.
  • 数据 Trigger, 与 数据绑定相关, ch18 中再介绍.

属性触发器在某个属性具有特定值的时候会执行一组 Setter. 当对应属性不在具有该值时, 触发器则失效.

怎么看怎么角色像事件, 也与 CSS 中的 :active 等类似.

Text 触发器

其实现是直击通过 Style.Triggers 来定义触发器:

  • 在 Style 中定义 Style.Triggers, 并在其中定义 Trigger.
  • 在 Trigger 中利用 Property 来指定监视什么属性, Value 来定义监视什么值.
  • 在 Trigger 中定义一组 Setter, 来定义激活该触发器时, 什么属性具有什么值.

例如:

image-20250721161514290

需要注意的是, 属性触发器精确匹配属性值, 不支持范围, 大小写变化等.

IsMouseOver 触发器

在鼠标悬浮都某个控件上是, IsMouseOver 属性的值会变化:

image-20250721161933483

IsMouseOver 是 UIElement 的成员.

设置 Transform 和 BitmapEffect

image-20250721162431360

image-20250721162444029

约定, 不要尝试修改按钮与菜单的背景色.

设置透明度

image-20250721162617507

image-20250721162643311

image-20250721162651992

需要注意的是, 背景色设置为透明, 与 Background="{x:Null}" 看起来效果一样, 但是鼠标悬浮时 IsMouseOver 的属性值是不同的.

  • Background="{x:Null}" 时, 鼠标进入后, 属性 IsMouseOver 不会响应, 即不会改变为 true.
  • Background="Transparent" 时, 鼠标进入与离开, IsMouseOver 的值会发生变化.

2025年7月21日 演示后, 似乎不是如此

<StackPanel>
    <Border BorderBrush="Black" Margin="5">
        <Button x:Name="btn1" Click="Button_Click" Width="200" Height="80" 
                Background="Transparent">
            <StackPanel>
                <TextBlock>修改资源数据1</TextBlock>
                <TextBlock Text="{Binding ElementName=btn1, Path=IsMouseOver}">
                </TextBlock>
            </StackPanel>
        </Button>
    </Border>
    <Border BorderBrush="Blue" Margin="5">
        <Button x:Name="btn2" Click="Button_Click" Width="200" Height="80" 
                Background="{x:Null}">
            <StackPanel>
                <TextBlock>修改资源数据2</TextBlock>
                <TextBlock Text="{Binding ElementName=btn2, Path=IsMouseOver}">
                </TextBlock>
            </StackPanel>
        </Button>
    </Border>
</StackPanel>

IsActive 和 IsFocused 触发器

适用属性触发器几乎可以监听任意属性.

激活与获得焦点.

其他略

小结

Last Updated:
Contributors: jk