Day 7 - 复盘与巩固
建议用时:180-210 分钟
你将学会什么
- 把 Week 7 的 MVVM 知识连成一条主线。
- 能解释
ObservableProperty、RelayCommand、CanExecute、ObservableCollection的作用。 - 能重写一个 MVVM 表单小窗口。
- 能根据报错判断是绑定问题、命令问题还是通知问题。
- 能说清 code-behind 事件和 MVVM 命令的区别。
复盘不是背概念。复盘要能做到:写得出、跑得通、说得清、能排错。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“小项目:MVVM CRUD”,今天继续学习“复盘与巩固”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。
今天的最低通过线
第一次学习不要求背完整页。完成下面 3 项,就可以继续:
- 能用自己的话说明“复盘与巩固”解决什么问题。
- 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
- 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。
第一部分:先学原理和最小知识
这一部分把 Week 7 的内容重新排成一张图。
1. 本周知识地图
| 日期 | 主题 | 解决的问题 | 必须掌握 |
|---|---|---|---|
| Day 1 | MVVM 入门 | View 和 ViewModel 分工 | Binding、DataContext |
| Day 2 | 绑定与转换 | 原始数据变成显示数据 | OneWay、TwoWay、计算属性、Converter |
| Day 3 | 命令机制 | 按钮动作放进 ViewModel | [RelayCommand]、CanExecute |
| Day 4 | 表单验证 | 保存前拦住错误输入 | 字段错误、TryParse、ValidateAll |
| Day 5 | 集合绑定 | 列表自动刷新 | ObservableCollection、ItemsSource、SelectedItem |
| Day 6 | MVVM CRUD | 把前面知识串成项目 | 新增、读取、更新、删除 |
2. MVVM 三层职责
| 层 | 负责什么 | 不该做什么 |
|---|---|---|
| View | 布局、控件、绑定 | 大量业务规则 |
| ViewModel | 页面状态、命令、验证、提示 | 直接操作具体控件 |
| Model | 业务数据结构 | 界面显示细节 |
如果你在 ViewModel 里写 NameBox.Text,说明职责混了。ViewModel 应该写 ProductName,View 通过绑定连接到它。
3. 本周最重要的 8 个关键词
| 关键词 | 直接解释 |
|---|---|
DataContext | View 绑定时使用的数据对象 |
x:DataType | 告诉 XAML 当前绑定的 ViewModel 类型 |
[ObservableProperty] | 自动生成可通知界面的属性 |
[NotifyPropertyChangedFor] | 一个属性变化时,通知另一个计算属性也变化 |
[RelayCommand] | 自动生成可绑定的命令 |
CanExecute | 判断命令当前能不能执行 |
ObservableCollection | 新增删除时能通知界面的集合 |
SelectedItem | 列表当前选中的那一项 |
4. 最常见错误和修法
| 现象 | 常见原因 | 修法 |
|---|---|---|
| 绑定不显示 | DataContext 没设置 | 检查 App.axaml.cs |
| 属性名绑定报错 | XAML 名字写错 | 对照 ViewModel 真实属性名 |
| 按钮命令找不到 | 方法没写 [RelayCommand] | 给方法加特性并检查命名 |
| 按钮状态不刷新 | 没写 NotifyCanExecuteChangedFor | 给影响条件的字段加通知 |
| 计算属性不刷新 | 没通知计算属性 | 使用 NotifyPropertyChangedFor 或手动 OnPropertyChanged |
| 列表新增不刷新 | 用了普通 List<T> | 改用 ObservableCollection<T> |
| 选中项为空时报错 | 没判断 SelectedItem | 先判断是否为 null |
5. 判断代码放哪里
| 需求 | 放哪里 |
|---|---|
| 按钮文字 | View |
| 输入框绑定哪个属性 | View |
| 商品名称当前值 | ViewModel |
| 保存按钮能不能点 | ViewModel |
| 保存命令执行什么 | ViewModel |
| 商品数据结构 | Model 或 ViewModel 附近的简单类 |
| 真实保存到数据库 | Service |
Week 7 MVVM 常用写法总表
| 主题 | 必须会的写法 |
|---|---|
| 属性通知 | [ObservableProperty] |
| 命令 | [RelayCommand] |
| 属性联动 | [NotifyPropertyChangedFor] |
| 文本绑定 | Text="{Binding ..., Mode=TwoWay}" |
| 命令绑定 | Command="{Binding SaveCommand}" |
| 列表绑定 | ItemsSource="{Binding Products}" |
| 选中项 | SelectedItem="{Binding SelectedProduct}" |
| 集合刷新 | ObservableCollection<T> |
| 表单验证 | TryParse + ErrorMessage |
| CRUD | Add、Select、Edit、Remove、Search |
第二部分:把知识组合成完整例子
Week 7 的主线是:
ViewModel 保存状态
-> View 用 Binding 显示和修改状态
-> Button 用 Command 触发动作
-> ViewModel 做验证和处理
-> View 自动显示处理结果下面这个窗口复习属性绑定、命令、验证和按钮可执行条件。
ViewModels/MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace ProductApp.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string productName = "Keyboard";
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string priceText = "199";
[ObservableProperty]
private string message = "修改商品信息后点击保存。";
public string StatusText =>
string.IsNullOrWhiteSpace(ProductName) ? "名称未填写" : $"当前商品:{ProductName.Trim()}";
partial void OnProductNameChanged(string value)
{
OnPropertyChanged(nameof(StatusText));
}
private bool CanSave()
{
return !string.IsNullOrWhiteSpace(ProductName) &&
decimal.TryParse(PriceText, out decimal price) &&
price > 0;
}
[RelayCommand(CanExecute = nameof(CanSave))]
private void Save()
{
decimal price = decimal.Parse(PriceText.Trim());
Message = $"保存成功:{ProductName.Trim()},价格 {price}";
}
}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="560"
Height="340"
Title="Week 7 复盘">
<StackPanel Margin="20"
Spacing="10">
<TextBlock Text="{Binding StatusText}"
FontSize="20"
FontWeight="Bold" />
<TextBlock Text="商品名称" />
<TextBox Text="{Binding ProductName, Mode=TwoWay}" />
<TextBlock Text="商品价格" />
<TextBox Text="{Binding PriceText, Mode=TwoWay}" />
<Button Content="保存"
Command="{Binding SaveCommand}" />
<TextBlock Text="{Binding Message}"
TextWrapping="Wrap" />
</StackPanel>
</Window>先用一句话理解
输入框改 ViewModel 属性,属性变化刷新状态文字和保存按钮状态,按钮执行命令,命令修改 Message,界面自动显示结果。
第三部分:跟着敲代码
从这里开始动手。今天只重做一个复盘窗口,目标是确认 MVVM 核心链路已经掌握。
步骤 1:确认 App.axaml.cs 设置了 DataContext
模板里应该有:
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};没有这段,绑定就不知道去哪里找 ViewModel。
步骤 2:写 ViewModel
使用第一部分的 ViewModels/MainWindowViewModel.cs。
你要特别看这几行:
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string productName = "Keyboard";它表示:ProductName 改变时,保存按钮要重新判断能不能点。
步骤 3:写 View
使用第一部分的 Views/MainWindow.axaml。
你要特别看这几行:
<TextBox Text="{Binding ProductName, Mode=TwoWay}" />
<Button Content="保存" Command="{Binding SaveCommand}" />第一行是双向绑定,第二行是命令绑定。
步骤 4:运行并做错误输入
必须做这几次验证:
- 清空商品名称。
- 把价格改成
abc。 - 把价格改成
0。 - 输入
Mouse和99后保存。
如果前三个输入时保存按钮不可点,说明 CanExecute 生效。
小白重复敲写训练
复盘日要独立写出 MVVM 三条最重要的闭环。
训练 1:属性绑定
[ObservableProperty]
private string _title = "商品管理";<TextBlock Text="{Binding Title}" />训练 2:命令绑定
[RelayCommand]
private void Clear() => Title = "";<Button Content="清空" Command="{Binding ClearCommand}" />训练 3:集合绑定
public ObservableCollection<string> Items { get; } = new();
[RelayCommand]
private void Add() => Items.Add($"Item {Items.Count + 1}");<ListBox ItemsSource="{Binding Items}" />把三段组合到同一个页面,再关掉答案重写一次。
每日小测
做完本页后,用这 5 题检查是否真的掌握。
1. 判断题
本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。
答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。
2. 填空题
本页主题是:复盘与巩固。今天至少要掌握的 3 个点是:
1. 把 Week 7 的 MVVM 知识连成一条主线。
2. 能解释 `ObservableProperty`、`RelayCommand`、`CanExecute`、`ObservableCollection` 的作用。
3. 能重写一个 MVVM 表单小窗口。答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。
3. 流程题
遇到本页相关功能时,先按什么顺序处理?
答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。
4. 找错误题
如果本页代码运行失败,第一步应该做什么?
答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。
5. 改需求题
在本页完整例子跑通后,至少改一个小需求。
可选改法:
- 改一个字段名称。
- 多加一个校验条件。
- 多输出一行结果。
- 把固定数据改成用户输入。
- 把一次处理改成多条数据处理。
答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:把 Week 7 的 MVVM 知识连成一条主线。。
上位机专项练习
本周过关标准是:界面只负责展示和绑定,状态与动作主要放在 ViewModel。
下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。
专项例子 1:复习属性通知
[ObservableProperty]
private double temperature;
private void UpdateTemperature(double value)
{
Temperature = value;
}运行结果或界面效果:
Temperature 改变后绑定界面自动刷新改动任务: 增加 Pressure 属性。
专项例子 2:复习命令
[RelayCommand]
private void StartCollecting()
{
StatusText = "正在采集";
}运行结果或界面效果:
按钮通过 StartCollectingCommand 启动采集改动任务: 增加 StopCollectingCommand。
专项例子 3:复习集合
Devices.Add(new DeviceItem("PLC-03", "在线"));
Alarms.Add(new AlarmItem("PLC-03", "高温"));运行结果或界面效果:
设备列表和报警列表都自动增加一项改动任务: 删除第一条已确认报警。
第四部分:作业完整答案
这一部分给出本周复盘作业的完整答案。
作业 1:写出 Week 7 主线
完整答案:
Week 7 主线:
ViewModel 保存页面状态。
View 用 Binding 显示和修改状态。
Button 用 Command 触发 ViewModel 方法。
ViewModel 用 CanExecute 控制按钮能不能执行。
ViewModel 做验证并更新 Message。
集合页面用 ObservableCollection 通知列表新增删除。
列表页面用 SelectedItem 表示当前选中项。作业 2:解释 6 个核心写法
完整答案:
| 写法 | 解释 |
|---|---|
[ObservableProperty] | 自动生成属性,并在属性变化时通知界面 |
[RelayCommand] | 自动生成按钮可以绑定的命令 |
Mode=TwoWay | 输入框变化会写回 ViewModel |
CanExecute | 决定命令现在能不能执行 |
ObservableCollection | 集合新增删除时通知界面刷新 |
SelectedItem | 当前选中的列表项 |
作业 3:排错题完整答案
| 问题 | 原因 | 修法 |
|---|---|---|
SaveCommand 不存在 | Save 方法没加 [RelayCommand] | 添加 [RelayCommand] |
| 输入框改了但保存按钮仍禁用 | 没通知命令重新判断 | 添加 [NotifyCanExecuteChangedFor(nameof(SaveCommand))] |
StatusText 不刷新 | 计算属性没有变化通知 | 调用 OnPropertyChanged(nameof(StatusText)) |
| 列表新增后不显示 | 没用 ObservableCollection | 改成 ObservableCollection<T> |
| 选中项删除时报错 | 没判断 SelectedProduct 是否为空 | 删除前判断 SelectedProduct is null |
作业 4:验收清单完整答案
Week 7 学完后,应能做到:
- 能写出
MainWindowViewModel并让窗口绑定它。 - 能用
[ObservableProperty]写可通知属性。 - 能用
TextBox双向绑定 ViewModel 属性。 - 能用
[RelayCommand]让按钮执行 ViewModel 方法。 - 能用
CanExecute禁用不该执行的按钮。 - 能写名称和价格验证。
- 能用
ObservableCollection绑定列表。 - 能用
SelectedItem显示当前详情。 - 能把新增、更新、删除放进命令。
- 能根据错误信息判断是绑定、命令、通知还是集合问题。
本周真正要带走的能力
看到一个 Avalonia 页面需求
-> 先定义 ViewModel 状态
-> 再让 View 绑定状态
-> 按钮动作写成命令
-> 输入错误写成验证
-> 列表数据用 ObservableCollection
-> 当前项用 SelectedItem
-> 最后把新增、保存、删除串成闭环这就是进入实际 Avalonia 项目之前必须掌握的 MVVM 基础线。