样式与属性触发器
用资源来控制控件的外观:
- 资源数据多
- 设置繁琐, 需要一一绑定
Style
是一种特殊资源, 可以减少冗余的代码.
简单资源
为不同的控件提供统一的外观, 使用资源就可以完成. 作者给出一个示例加以说明. 同时为了说明代码中大量的绑定, 维护变得困难.
样式的处理方式与一般的资源不同: 资源是定义值与其标识符(名字), 样式则是定义 属性
与 值
, 即将什么属性设置为什么值, 然后统一打包到 Style
标签中 (使用 Setter
标签). 然后将其放到资源
中. 置于在那个控件的资源中, 就看需要它在哪里可以被访问到了, 遵循树节点继承的规则 (作用域, 子节点继承父节点特性).
最后在需要使用该样式的控件中使用 Style
属性来绑定样式.
Style
可以有x:Key
属性来定义名字.Style
可以使用TargetType
来指定什么类型的控件会使用该样式.Style
中使用Setter
标签来定义属性的值. 属性名使用Property
属性来指定, 属性值使用Value
来指定, 它可以是简单值, 也可以以标签属性的形式来定义复杂值.Style
中使用EventSetter
标签来定义事件.
例如, 定义样式可以是:
使用样式可以是:
指定 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
所在资源的作用域, 仅会作用域其作用域下的所有目标类型控件.
属性值的优先级
控件的属性可以有多种设置方式, 一旦出现冲突 (多种方式设置了一个属性), 那么就需要考虑优先级了.
- 从容器继承.
- 默认样式.
- 未命名样式.
- 具名样式.
- 本地设置值.
- 激活的动画. 在动画过程中设置的属性值有较高优先级.
补充, 如果设置了匿名样式, 作用域某特定类型控件, 但是又需要某个控件不设置该样式, 可以使用
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
, 来定义激活该触发器时, 什么属性具有什么值.
例如:
需要注意的是, 属性触发器精确匹配属性值, 不支持范围, 大小写变化等.
IsMouseOver 触发器
在鼠标悬浮都某个控件上是, IsMouseOver
属性的值会变化:
IsMouseOver
是 UIElement
的成员.
设置 Transform 和 BitmapEffect
约定, 不要尝试修改按钮与菜单的背景色.
设置透明度
需要注意的是, 背景色设置为透明, 与 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 触发器
适用属性触发器几乎可以监听任意属性.
激活与获得焦点.
其他略