Day 5 - 主题结构
建议用时:240-270 分钟
你将学会什么
- 知道为什么要拆分主题文件。
- 会创建
Themes/Colors.axaml保存颜色资源。 - 会创建
Themes/Spacing.axaml保存间距资源。 - 会创建
Themes/Buttons.axaml保存按钮样式。 - 会在
App.axaml里用ResourceInclude和StyleInclude引入主题文件。 - 会在页面里使用主题资源和样式类。
主题结构不是为了多建文件,而是为了让颜色、间距、按钮样式有固定位置。以后改主题时,不需要到每个页面里找散落的颜色和边距。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“UserControl 复用”,今天继续学习“主题结构”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。
今天的最低通过线
第一次学习不要求背完整页。完成下面 3 项,就可以继续:
- 能用自己的话说明“主题结构”解决什么问题。
- 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
- 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。
第一部分:先学原理和最小知识
这一部分从最小知识开始。先读解释,再把紧跟着的短例子敲一遍。主题结构解决的是“资源和样式越来越多以后放在哪里”的问题。
1. 为什么要拆分主题文件
如果所有资源和样式都放在 App.axaml,一开始能用,但后面会变得很长:
颜色
间距
字号
按钮
输入框
卡片
列表拆分后,每个文件职责清楚:
| 文件 | 负责什么 |
|---|---|
Colors.axaml | 颜色画刷 |
Spacing.axaml | 页面边距、卡片内边距 |
Buttons.axaml | 按钮样式 |
App.axaml | 引入主题文件 |
2. ResourceDictionary 是什么
ResourceDictionary 是资源字典,用来放可复用值。
<ResourceDictionary>
<SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB" />
</ResourceDictionary>页面使用:
Foreground="{StaticResource PrimaryBrush}"3. ResourceInclude 是什么
ResourceInclude 用来把外部资源字典引入应用。
<ResourceInclude Source="avares://ProductApp/Themes/Colors.axaml" />avares://ProductApp/... 的意思是从当前 Avalonia 应用资源里找这个文件。
4. StyleInclude 是什么
StyleInclude 用来引入外部样式文件。
<StyleInclude Source="avares://ProductApp/Themes/Buttons.axaml" />样式文件的根节点是:
<Styles>资源文件的根节点是:
<ResourceDictionary>这两个不要写反。
5. 为什么 FluentTheme 不能删
模板自带:
<FluentTheme />它提供 Avalonia 默认控件主题。如果删掉,很多控件默认外观会出问题。
正确做法是在它下面追加自己的样式:
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://ProductApp/Themes/Buttons.axaml" />
</Application.Styles>6. 什么应该放主题文件
适合放:
- 全局颜色。
- 全局间距。
- 全局按钮样式。
- 多个页面共用的卡片样式。
不适合放:
- 某个页面只用一次的临时颜色。
- 某个控件的业务数据。
- 保存、删除、校验逻辑。
7. 修改主题时的顺序
如果要换主色,不要去每个页面改按钮。
正确顺序:
- 打开
Themes/Colors.axaml。 - 修改
PrimaryBrush的颜色。 - 运行应用。
- 检查所有使用
PrimaryBrush或Button.primary的地方是否一起变化。
主题结构常用写法速查
| 需求 | 写法 | 说明 |
|---|---|---|
| 全局主题 | App.axaml | 应用级资源 |
| 颜色资源 | SolidColorBrush | 统一颜色 |
| 间距资源 | x:Double | 统一间距 |
| 控件样式 | Style Selector="Button.primary" | 统一外观 |
| 页面局部资源 | Window.Resources | 只给当前页面 |
| 引用资源 | {StaticResource Key} | 使用资源 |
主题结构原则:
默认样式够用就不改
重复值再提取
全局通用放 App.axaml
页面专用放页面资源第二部分:把知识组合成完整例子
今天要把主题拆成这些文件:
Themes/
Colors.axaml
Spacing.axaml
Buttons.axaml
App.axaml
Views/MainWindow.axamlThemes/Colors.axaml
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB" />
<SolidColorBrush x:Key="DangerBrush" Color="#DC2626" />
<SolidColorBrush x:Key="CardBorderBrush" Color="#DDDDDD" />
</ResourceDictionary>Themes/Spacing.axaml
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Thickness x:Key="PageMargin">20</Thickness>
<Thickness x:Key="CardPadding">16</Thickness>
</ResourceDictionary>Themes/Buttons.axaml
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="Button.primary">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Padding" Value="12,6" />
</Style>
<Style Selector="Button.danger">
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Padding" Value="12,6" />
</Style>
</Styles>App.axaml
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ProductApp.App"
RequestedThemeVariant="Default">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ProductApp/Themes/Colors.axaml" />
<ResourceInclude Source="avares://ProductApp/Themes/Spacing.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://ProductApp/Themes/Buttons.axaml" />
</Application.Styles>
</Application>Views/MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ProductApp.Views.MainWindow"
Width="620"
Height="360"
Title="主题结构">
<StackPanel Margin="{StaticResource PageMargin}"
Spacing="12">
<TextBlock Text="商品管理"
FontSize="24"
FontWeight="Bold"
Foreground="{StaticResource PrimaryBrush}" />
<Border BorderBrush="{StaticResource CardBorderBrush}"
BorderThickness="1"
Padding="{StaticResource CardPadding}">
<StackPanel Spacing="8">
<TextBlock Text="名称:Keyboard" />
<TextBlock Text="价格:199" />
<StackPanel Orientation="Horizontal"
Spacing="8">
<Button Classes="primary"
Content="保存" />
<Button Content="取消" />
<Button Classes="danger"
Content="删除" />
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Window>先用一句话理解
颜色和间距放进资源字典,按钮样式放进样式文件,App.axaml 统一引入,页面只使用资源和样式类。
第三部分:跟着敲代码
从这里开始动手。今天会创建 Themes 文件夹。
步骤 1:创建 Themes 文件夹
在项目根目录创建:
Themes/然后创建:
Themes/Colors.axaml
Themes/Spacing.axaml
Themes/Buttons.axaml步骤 2:写 Colors.axaml
使用第一部分的 Themes/Colors.axaml。
步骤 3:写 Spacing.axaml
使用第一部分的 Themes/Spacing.axaml。
步骤 4:写 Buttons.axaml
使用第一部分的 Themes/Buttons.axaml。
步骤 5:修改 App.axaml
使用第一部分的 App.axaml,确认:
<FluentTheme />仍然存在。
步骤 6:修改 MainWindow.axaml
使用第一部分的 Views/MainWindow.axaml,确认页面没有直接写散落主色,而是使用:
Foreground="{StaticResource PrimaryBrush}"和:
Classes="primary"常见错误和修法
| 错误 | 为什么错 | 修法 |
|---|---|---|
| 主题里写具体业务文字 | 主题应该管外观,不管业务 | 文字放页面或 ViewModel |
| 颜色到处写死 | 后期换主题困难 | 集中放资源变量 |
| 资源 key 命名混乱 | 查找和复用困难 | 用清楚名称,例如 PrimaryBrush |
| 一次改太多主题项 | 很难定位错误 | 先改颜色,再改字号,再改间距 |
| 忽略默认主题 | 控件状态可能变差 | 先基于默认样式小步调整 |
小白重复敲写训练
主题训练只改资源值,不改每个按钮本身。
训练 1:定义颜色资源
<Application.Resources>
<SolidColorBrush x:Key="PageBackgroundBrush" Color="#F8FAFC" />
<SolidColorBrush x:Key="TextBrush" Color="#111827" />
<SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB" />
</Application.Resources>训练 2:使用动态资源
<Border Background="{DynamicResource PageBackgroundBrush}">
<TextBlock Text="商品管理" Foreground="{DynamicResource TextBrush}" />
</Border>改动资源颜色,观察使用它的控件一起变化。
训练 3:切换一个资源
Application.Current!.Resources["PageBackgroundBrush"] =
new SolidColorBrush(Color.Parse("#111827"));第三遍同时切换文字颜色,避免深色背景上看不清。
每日小测
做完本页后,用这 5 题检查是否真的掌握。
1. 判断题
本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。
答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。
2. 填空题
本页主题是:主题结构。今天至少要掌握的 3 个点是:
1. 知道为什么要拆分主题文件。
2. 会创建 `Themes/Colors.axaml` 保存颜色资源。
3. 会创建 `Themes/Spacing.axaml` 保存间距资源。答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。
3. 流程题
遇到本页相关功能时,先按什么顺序处理?
答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。
4. 找错误题
如果本页代码运行失败,第一步应该做什么?
答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。
5. 改需求题
在本页完整例子跑通后,至少改一个小需求。
可选改法:
- 改一个字段名称。
- 多加一个校验条件。
- 多输出一行结果。
- 把固定数据改成用户输入。
- 把一次处理改成多条数据处理。
答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:知道为什么要拆分主题文件。。
上位机专项练习
主题资源集中管理颜色、间距和字号,让白天模式、夜间模式或客户配色可以整体切换。
下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。
专项例子 1:定义语义颜色
<Color x:Key="OnlineColor">#2E7D32</Color>
<Color x:Key="AlarmColor">#C62828</Color>
<Color x:Key="SurfaceColor">#F5F7FA</Color>运行结果或界面效果:
颜色名称表达用途而不是具体色值改动任务: 增加 OfflineColor。
专项例子 2:使用动态资源
<Border Background="{DynamicResource SurfaceBrush}">
<TextBlock Foreground="{DynamicResource PrimaryTextBrush}"
Text="设备监控" />
</Border>运行结果或界面效果:
切换主题时资源可以更新改动任务: 为报警文字使用 AlarmBrush。
专项例子 3:设置主题变体
Application.Current!.RequestedThemeVariant = ThemeVariant.Dark;运行结果或界面效果:
应用切换为深色主题改动任务: 增加一个按钮在 Light 和 Dark 间切换。
第四部分:作业完整答案
这一部分给出当天作业的完整答案。完整代码就是第一部分所有文件。下面补充检查答案。
作业要求
建立一个主题结构:
Colors.axaml保存PrimaryBrush、DangerBrush、CardBorderBrush。Spacing.axaml保存PageMargin、CardPadding。Buttons.axaml保存Button.primary和Button.danger。App.axaml使用ResourceInclude引入资源。App.axaml使用StyleInclude引入按钮样式。MainWindow.axaml使用这些资源和样式。
文件结构完整答案
ProductApp/
App.axaml
Themes/
Colors.axaml
Spacing.axaml
Buttons.axaml
Views/
MainWindow.axaml必须验证的结果
| 检查项 | 应该看到 |
|---|---|
PrimaryBrush | 标题使用主色 |
Button.primary | 保存按钮使用主按钮样式 |
Button.danger | 删除按钮使用危险按钮样式 |
PageMargin | 页面外边距来自资源 |
CardPadding | 卡片内边距来自资源 |
如果报错,按这个顺序检查
- 检查
Themes文件夹和文件名是否完全一致。 - 检查
ResourceInclude路径是否是avares://ProductApp/Themes/Colors.axaml。 - 检查资源文件根节点是否是
ResourceDictionary。 - 检查样式文件根节点是否是
Styles。 - 检查
App.axaml是否保留了<FluentTheme />。 - 检查资源 key 是否一致,比如
PageMargin不能写成PagePadding。
今天真正要掌握的闭环
Themes 文件夹拆分资源和样式
-> ResourceInclude 引入资源字典
-> StyleInclude 引入样式文件
-> 页面通过 StaticResource 和 Classes 使用主题
-> 修改主题文件,全局一起变化主题结构的价值是让视觉规则有固定位置,而不是散落在每个页面里。