目录
介绍
在 Avalonia UI 中有几种概念 Theme
Style
ControlTheme
,从WPF转过来的时候对于 ControlTheme 跟 Theme 的区别是什么呢? 为什么Style跟我们的WPF的Style的效果不太一样? Trigger 也没了?
首选需要说的是, Theme
、Style
、ControlTheme
都是继承自 IStyle
也就是说他们都是 样式(Style)
, 但是他们之间有一些差异.
这里介绍一下我个人如何理解这三种 IStyle
Theme
暂且理解为全局主题(Global Theme)
Style
暂且理解为局部主题(Local Theme)
ControlTheme
暂且理解为控件样式 (Control Style, 类似WPF中定义控件Style以及Template)
使用方式
全局主题 (Global Theme)
// App.xaml
<Application xmlns="https://github.com/avaloniaui"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme /> // 这里的Theme 其实也是Style
</Application.Styles>
</Application>
局部主题 (Local Theme)
如果将 Style
方式在 Window
或者UserControl
或者Control
下即为局部主题
<Window>
<Window.Styles>
<!-- Common button properties -->
<Style Selector="Button">
<Setter Property="Margin" Value="10" />
<Setter Property="MinWidth" Value="200" />
<Setter Property="Height" Value="50" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
<Style Selector="^:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="Green" />
</Style>
</Style>
</Window.Styles>
// ...
</Window>
控件主题 (ControlTheme)
注意: 这里的 ControlTheme
是放置在 Window.Resource
下
<Window.Resource>
<ControlTheme x:Key="{x:Type Button}" TargetType="Button">
<Setter Property="Background" Value="#C3C3C3" />
<Setter Property="FontFamily" Value="Arial" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Height" Value="100" />
<Setter Property="Template">
<ControlTemplate>
// ...
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Setter Property="Background" Value="red" />
</Style>
</ControlTheme>
</Window.Resource>
问题描述
从使用方式上看 Global Theme
与 Local Theme
是一样的, 都是放置在 Styles
属性下. 问题的关键是:
-
Styles
下的Theme
与Resource
下的ControlTheme
有什么区别? -
Styles
跟ControlTheme
同样可以重写Template
, 那我要选哪个来重写Template
?
问题分析
在下文我将 Styles
下的Theme
或Style
称为 Styles
, 将Resources
下的ControlTheme
成为ControlTheme
, 方便大家理解.
问题1 区别
按我个人的理解来看,这是属于两种UI设计模式
.
-
Styles
类似于CSS
样式表操作,针对在应用范围内的所有选择的元素的Style
都将被应用. -
ControlTheme
是类似与WPF
的 Style, 除了默认ControlTheme
, 其他ControlTheme
都需要指定Key,相对独立。
问题2 重写Template
用 Styles
还是 ControlTheme
?
两种模式都可以写, ControlTheme
是从 v11 版本引入的. 主要是为了解决 Styles 之间的隔离性. 如:
<Style Selector="Button"> // Default Style
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
</Style>
// 两个Button的Style有关联关系
<Style Selector="Button.NewStyle"> // 对 Default Style 的修改都有可能影响其他Style
<Setter Property="HorizontalContentAlignment" Value="Left" />
// <Setter Property="VerticalAlignment" Value="Bottom" /> // 来此Default Style
</Style>
试想这样一个场景, 如果我在代码中引入了第三方控件库,它重写了系统默认控件的样式, 这个时候我们又有自己的样式, 如
// 第三方库
<Style Selector="Button">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="100" />
</Style>
// 我们自己的
<Style Selector="Button">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
<Setter Property="Width" Value="50" />
</Style>
引用自官网: 如果你想要修改控件的特定实例的样式
Styles
,唯一的选项是应用一个新的Styles
为控件实例,并希望它能够重写原始的Styles
中的设置过的所有属性.
以上的场景 Button 的宽度是多少? 答 100
. 这个对于来自WPF的小朋友就感觉就难受了, 第三方库加了个MinWidth
,我又没继承,难道我还要在自己的样式中自己给MinWidth
或者重新整个样式吗? 也就是Avalonia UI
官方说的一旦一个Style被应用到一个控件上,没有办法移除它。
坑就来了啊,如果第三方库更新了Styles
加了个属性咋办? 我也要跟着加?
使用 ControlTheme
所以 V11 版本后引入了 ControlTheme
对于 特定 Control
的ControlTheme
之间是彼此独立的。
<ControlTheme x:Key="A" TargetType="Button">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="100" />
</ControlTheme>
// 两个Button的ControlTheme相互独立
<ControlTheme x:Key="B" TargetType="Button">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
<Setter Property="Width" Value="50" />
</ControlTheme>
问: Button
的宽度是多少? 答 50
. 这不就跟我们大WPF一样了吗? 如果我还要继承第三方库写的样式咋办? 加上 Baseon
属性即可.
最佳实践
- 开发UI框架时最好使用
ControlTheme
定义控件Template
, 这也是官方推荐的。 - 利用控件可以应用多个
Styles
的优点,标准化一些常用样式, 供控件的扩展UI使用 - 利用
Styles
应用优先级比ControlTheme
高的优点,- 标准业务里边的多颜色主题使用
Styles
- 全局控制应用的主题
- 标准业务里边的多颜色主题使用
- 不要写默认的全局Style
<Style Selector="Button">
, 否则所有Button都将受到影响, 官方全局样式里边的已经都替换成ControlTheme
了, 有兴趣参考下面链接。
总结
Style & ControlTheme 的特性
独立性
ControlTheme
彼此之间是独立的
Style
彼此是相互覆盖的
如:
<ControlTheme x:Key="A" TargetType="Button">
//...
</ControlTheme>
// 两个Button的ControlTheme相互独立
<ControlTheme x:Key="B" TargetType="Button">
//...
</ControlTheme>
<Style Selector="Button"> // Default Style
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="VerticalContentAlignment" Value="Bottom" />
</Style>
// 两个Button的Style有关联关系
<Style Selector="Button.NewStyle"> // 对 Default Style 的修改都有可能影响其他Style
<Setter Property="HorizontalContentAlignment" Value="Left" />
// <Setter Property="VerticalAlignment" Value="Bottom" /> // 来此Default Style
</Style>
继承性
ControlTheme
由于ControlTheme
之间相互独立,但是其支持 BaseOn
类似 WPF 的<Style BaseOn="{StaticResource BaseStyle}"
Style
参考上一点独立性, 新增的Style
都将继承Default Style
如:
<ControlTheme x:Key="A" TargetType="Button">
//...
</ControlTheme>
// 两个Button的ControlTheme相互独立
<ControlTheme x:Key="B" TargetType="Button" BaseOn="{StaticResource A}">
//...
</ControlTheme>
而 Styles
类似 CSS
, 它将所有作用域范围内的Styles
的Setters
都放在一起应用到控件上. 这也就是为什么原始的Style
如果新的Style
不需要也要设置相同属性进行覆盖
优先级
从应用样式的角度 Style > ControlTheme 即 Default Style
(或者设置了Classes的控件) 的 Setter
都将覆盖 ControlTheme
的 Setter
从定义控件的角度 ControlTheme > Style 即 定义一类新Template(类似WPF的 ControlTemplate) 优先使用ControlTheme, 并且利用 Style 控制上层颜色方案
样式来源
ControlTheme 将遍历 可视树 (Visual Tree)
, 这种方式也与WPF类似
Style 类似于将所有作用域范围内的所有 Setter
合并并收集起来, 应用到控件
实例上.
多Styles
单ControlTheme
控件实例可以引用多个Styles
, 引用方式为 Classes="H1 Blue"
控件实例只可以引用一个ControlTheme
, 引用方式为 Theme="{StaticResources XXXXTheme}"
参考文档
===
知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 0xJins
(包含此链接),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。