Skip to Content
Week 08Day 7 - 复盘与巩固

Day 7 - 复盘与巩固

建议用时:180-210 分钟

你将学会什么

  • 把 Week 8 的样式、模板、组件、主题和状态打磨串起来。
  • 能分清 StyleDataTemplateControlTemplateUserControl 的用途。
  • 能重写一个带样式和数据模板的商品列表。
  • 能说清主题文件拆分的目的。
  • 能根据界面问题判断应该改样式、模板、组件还是 ViewModel 状态。

本周复盘重点是“界面结构怎么组织”。不要把 Style、DataTemplate、ControlTemplate、UserControl 混成同一个东西。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“界面打磨”,今天继续学习“复盘与巩固”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“复盘与巩固”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分把 Week 8 的概念重新排成判断表。

1. 本周知识地图

日期主题解决的问题
Day 1样式体系重复外观集中管理
Day 2DataTemplate每条数据怎么显示
Day 3ControlTemplate控件内部结构怎么改
Day 4UserControl重复界面块怎么封装
Day 5主题结构资源和样式文件怎么组织
Day 6界面打磨空、加载、错误、禁用状态怎么表达

2. 四个容易混的概念

概念负责什么例子
Style改控件外观属性按钮背景色、标题字号
DataTemplate定义数据项显示结构商品显示名称、价格、状态
ControlTemplate重写控件内部结构按钮内部用自定义 Border
UserControl封装一块可复用界面ProductCard 商品卡片

3. 什么时候用哪个

需求选择
多个按钮要同样颜色Style
列表里的商品要显示三行DataTemplate
按钮内部结构要完全改ControlTemplate
多个页面都要同一块商品卡片UserControl
多个页面共用颜色和间距主题资源文件
没有数据时要提示ViewModel 状态 + IsVisible

4. 本周最常见错误和修法

现象原因修法
样式不生效Classes 没写或 selector 写错检查 Button.primaryClasses="primary"
模板项不显示属性DataTemplate 没写正确类型x:DataType="vm:ProductItem"
按钮文字消失ControlTemplate 没有 ContentPresenterContentPresenter
UserControl 参数不显示内部没绑定自己的属性使用 ElementName=Root
主题文件找不到Include 路径错检查 avares://ProductApp/Themes/...
空状态不刷新没通知 IsEmpty集合变化后调用 OnPropertyChanged(nameof(IsEmpty))

Week 8 界面复用与样式常用写法总表

主题必须会的写法
资源StaticResourcex:Key
样式Style SelectorClasses
列表模板DataTemplatex:DataType
用户控件UserControlxmlns:views
主题结构全局资源和局部资源分开
界面状态空状态、保存中、错误提示、禁用按钮
控件提示PlaceholderText
列表显示ItemsSource + ItemTemplate

Week 8 的目标不是把界面做花,而是知道什么时候该复用、什么时候该提取、什么时候保持默认。

第二部分:把知识组合成完整例子

Week 8 的主线是:

资源管理重复值 -> Style 管理控件外观 -> DataTemplate 管理数据项显示 -> ControlTemplate 管理控件内部结构 -> UserControl 管理可复用界面块 -> ViewModel 状态控制空、加载、错误、禁用

下面这个窗口复习资源、样式、DataTemplate 和空状态。

ViewModels/MainWindowViewModel.cs

using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ProductApp.ViewModels; public partial class MainWindowViewModel : ViewModelBase { public ObservableCollection<ProductItem> Products { get; } = new() { new ProductItem { Name = "Keyboard", Price = 199, Enabled = true }, new ProductItem { Name = "Old Monitor", Price = 399, Enabled = false } }; [ObservableProperty] private string message = "商品列表已加载。"; public bool IsEmpty => Products.Count == 0; [RelayCommand] private void ClearProducts() { Products.Clear(); Message = "列表已清空。"; OnPropertyChanged(nameof(IsEmpty)); } } public sealed class ProductItem { public string Name { get; set; } = ""; public decimal Price { get; set; } public bool Enabled { get; set; } public string EnabledText => Enabled ? "启用" : "停用"; }

Views/MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:ProductApp.ViewModels" x:Class="ProductApp.Views.MainWindow" x:DataType="vm:MainWindowViewModel" Width="680" Height="460" Title="Week 8 复盘"> <Window.Resources> <SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB" /> <SolidColorBrush x:Key="CardBorderBrush" Color="#DDDDDD" /> </Window.Resources> <Window.Styles> <Style Selector="TextBlock.title"> <Setter Property="FontSize" Value="24" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="Foreground" Value="{StaticResource PrimaryBrush}" /> </Style> <Style Selector="Border.card"> <Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="Padding" Value="12" /> </Style> </Window.Styles> <StackPanel Margin="20" Spacing="12"> <TextBlock Classes="title" Text="商品列表复盘" /> <TextBlock Text="暂无商品,请先新增数据。" IsVisible="{Binding IsEmpty}" /> <ListBox ItemsSource="{Binding Products}"> <ListBox.ItemTemplate> <DataTemplate x:DataType="vm:ProductItem"> <Border Classes="card" Margin="0,0,0,8"> <StackPanel Spacing="4"> <TextBlock Text="{Binding Name}" FontWeight="Bold" /> <TextBlock Text="{Binding Price}" /> <TextBlock Text="{Binding EnabledText}" /> </StackPanel> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="清空列表" Command="{Binding ClearProductsCommand}" /> <TextBlock Text="{Binding Message}" TextWrapping="Wrap" /> </StackPanel> </Window>

先用一句话理解

资源提供颜色,样式统一标题和卡片,DataTemplate 定义每个商品怎么显示,IsEmpty 控制没有数据时的提示。

第三部分:跟着敲代码

从这里开始动手。今天只重写第一部分的复盘窗口。

步骤 1:写 ViewModel

使用第一部分的 ViewModels/MainWindowViewModel.cs

重点看:

public bool IsEmpty => Products.Count == 0;

它控制空状态是否显示。

步骤 2:写资源和样式

Window.Resources 放颜色,在 Window.Styles 放标题和卡片样式。

重点看:

<Style Selector="Border.card">

这个样式会作用到带 Classes="card"Border

步骤 3:写 DataTemplate

重点看:

<DataTemplate x:DataType="vm:ProductItem">

模板内部绑定的是当前商品,而不是整个 ViewModel。

步骤 4:验证空状态

点击 清空列表 后:

  • Products.Clear() 清空集合。
  • OnPropertyChanged(nameof(IsEmpty)) 通知空状态刷新。
  • 暂无商品,请先新增数据。 显示出来。

小白重复敲写训练

复盘日从空白 XAML 重写三个视觉基础。

训练 1:样式类

<Style Selector="Button.primary"> <Setter Property="Background" Value="#2563EB" /> <Setter Property="Foreground" Value="White" /> </Style>

训练 2:数据模板

<DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate>

训练 3:空状态

<TextBlock Text="暂无数据" IsVisible="{Binding IsEmpty}" HorizontalAlignment="Center" />

把三段放进同一个列表页面,再改成自己的颜色和文案。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:复盘与巩固。今天至少要掌握的 3 个点是:

1. 把 Week 8 的样式、模板、组件、主题和状态打磨串起来。 2. 能分清 `Style`、`DataTemplate`、`ControlTemplate`、`UserControl` 的用途。 3. 能重写一个带样式和数据模板的商品列表。

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:把 Week 8 的样式、模板、组件、主题和状态打磨串起来。。

上位机专项练习

本周过关标准是能做出统一、可复用、状态明确的设备监控界面,而不是只会堆控件。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:复习样式复用

<Button Classes="primary" Content="保存" /> <Button Classes="primary" Content="连接" /> <Button Classes="danger" Content="停止" />

运行结果或界面效果:

同类操作统一,危险操作可区分

改动任务: 检查三个按钮是否需要相同宽度。

专项例子 2:复习模板复用

<ItemsControl ItemsSource="{Binding Devices}"> <ItemsControl.ItemTemplate> <DataTemplate><views:DeviceCard /></DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>

运行结果或界面效果:

设备数量变化时自动生成卡片

改动任务: 增加 WrapPanel 让卡片换行。

专项例子 3:复习可读状态

<TextBlock Text="离线 | 最后通信 10:32:15 | 点击重连" Foreground="{DynamicResource OfflineBrush}" />

运行结果或界面效果:

状态、时间和下一步同时可见

改动任务: 为报警状态写一条同结构文字。

第四部分:作业完整答案

这一部分给出本周复盘作业的完整答案。

作业 1:写出 Week 8 主线

完整答案:

Week 8 主线: 把界面重复内容整理清楚。 资源负责重复值。 Style 负责控件外观。 DataTemplate 负责数据项显示。 ControlTemplate 负责控件内部结构。 UserControl 负责可复用界面块。 主题文件负责组织全局资源和样式。 ViewModel 状态负责空、加载、错误、禁用这些界面反馈。

作业 2:概念选择题完整答案

需求正确选择
所有主按钮统一蓝色Style
商品列表每一项显示名称和价格DataTemplate
按钮内部要自定义圆角 BorderControlTemplate
多个页面复用商品卡片UserControl
全局主色统一管理Themes/Colors.axaml
保存中显示进度条IsSaving + ProgressBar

作业 3:排错题完整答案

问题原因修法
Button.primary 不生效按钮没写 Classes="primary"给按钮加 class
Name 绑定为空模板内部数据类型不对或属性名错检查 ProductItem.Namex:DataType
UserControl 显示不出 Title内部绑定到外部 DataContext改成 ElementName=Root
主题资源找不到ResourceInclude 路径错检查 avares://ProductApp/...
清空列表后空提示不显示没通知 IsEmpty调用 OnPropertyChanged(nameof(IsEmpty))

作业 4:验收清单完整答案

Week 8 学完后,应能做到:

  1. 能写全局资源和样式。
  2. 能用 Classes 套用样式。
  3. 能用 DataTemplate 自定义列表项。
  4. 能解释模板内部绑定的是当前项。
  5. 能写一个简单 ControlTemplate 并保留 ContentPresenter
  6. 能写一个带 StyledProperty 的 UserControl。
  7. 能用 ResourceIncludeStyleInclude 拆分主题。
  8. 能用 IsEmptyIsSavingErrorMessage 表示界面状态。
  9. 能判断界面问题应该改样式、模板、组件还是状态。

本周真正要带走的能力

重复外观 -> Style 重复值 -> Resource 重复数据项显示 -> DataTemplate 重复控件内部结构 -> ControlTemplate 重复界面块 -> UserControl 重复全局视觉规则 -> Theme files 界面状态不清楚 -> ViewModel 状态属性

能按这张表判断问题,界面代码就不会越写越乱。