Skip to Content
Week 07📋 Week 07 阶段检查

Week 07 - 阶段检查

这一页用来确认 Week 7 的 MVVM 能力是否真正跑通。检查方式是重做三个窗口:属性绑定、命令验证、集合绑定。

本周学习地图

阶段先掌握什么最终能写什么
分层View、ViewModel、Model能说清每层职责
属性[ObservableProperty]、通知刷新能让界面自动显示状态
绑定OneWay、TwoWay、计算属性能让输入框和属性互通
命令[RelayCommand]CanExecute能用按钮触发 ViewModel 方法
验证字段错误、保存按钮禁用能阻止错误输入保存
集合ObservableCollection、选中项能做增删改查列表

本周自测项目:MVVM 商品管理页面

从 Avalonia MVVM 模板开始写一个页面,至少包含:

  • MainWindow.axaml 只写控件和绑定。
  • MainWindowViewModel.cs 保存输入、列表、选中项、错误消息。
  • 新增商品、删除商品、清空表单。
  • 名称为空、价格非法时保存按钮不可执行或显示错误。
  • 列表变化后空状态自动更新。

过关标准:能完整解释 TextBox -> ProductName -> SaveCommand -> Products -> ListBox 的数据流。

上位机阶段验收:MVVM 设备管理页面

本周目标: 把设备状态、集合、验证和操作命令移入 ViewModel。

必须亲手完成:

  1. 属性绑定后数据变化自动刷新
  2. 新增、删除、连接使用 Command
  3. 无效设备名和端口阻止保存

过关标准: View 后台代码不再承担主要业务,ViewModel 可以脱离窗口单独测试。

不要只看答案。新建一个空项目重做一次,运行成功后再故意改坏一处并自己排错。

本周流程图

TextBox 输入 -> TwoWay Binding 写入 ViewModel 属性 -> SaveCommand 执行 ViewModel 方法 -> Service 校验业务规则 -> ObservableCollection 更新列表 -> ListBox 自动刷新 -> ErrorMessage / Message 显示状态

第一部分:本周原理地图

Week 7 的主线是:把页面状态和页面动作放进 ViewModel,让 View 通过绑定和命令使用它们。

本周能力拆解

能力你要能做到什么对应页面
MVVM 分层能说清 View、ViewModel、Model 职责Day 1
绑定转换能处理 OneWayTwoWay、计算属性Day 2
命令机制能写 RelayCommandCanExecuteDay 3
表单验证能显示字段错误并阻止错误保存Day 4
集合绑定能绑定列表、选中项、新增删除Day 5
CRUD 项目能把增删改查串成一个页面Day 6

必须掌握的关键词

关键词完整解释
DataContextView 绑定时使用的数据对象
x:DataType告诉 XAML 当前绑定的 ViewModel 类型
[ObservableProperty]自动生成属性和变化通知
[NotifyPropertyChangedFor]一个属性变化时通知计算属性刷新
[RelayCommand]自动生成可绑定命令
CanExecute控制命令当前能不能执行
ObservableCollection新增删除时能通知界面刷新
SelectedItem当前选中的列表项

Week 7 必须会用的 MVVM 写法

主题必须会的写法
ViewModel 属性[ObservableProperty] private string name = "";
计算属性联动[NotifyPropertyChangedFor(nameof(CanSave))]
普通命令[RelayCommand] private void Save()
异步命令[RelayCommand] private async Task LoadAsync()
文本双向绑定Text="{Binding Name, Mode=TwoWay}"
命令绑定Command="{Binding SaveCommand}"
按钮可用状态IsEnabled="{Binding CanSave}"
列表绑定ItemsSource="{Binding Products}"
选中项绑定SelectedItem="{Binding SelectedProduct}"
可刷新集合ObservableCollection<T>
错误提示ErrorMessage

本周最重要的数据流

ViewModel 暴露属性和命令 -> View 用 Binding 读取或修改属性 -> Button 用 Command 调用 ViewModel 方法 -> ViewModel 做验证、更新集合、更新消息 -> View 自动显示新状态

第二部分:阶段检查 1 - 属性绑定

目标:确认 View 能显示 ViewModel 属性,输入框能把用户输入写回 ViewModel。

ViewModels/MainWindowViewModel.cs 完整答案

using CommunityToolkit.Mvvm.ComponentModel; namespace ProductApp.ViewModels; public partial class MainWindowViewModel : ViewModelBase { [ObservableProperty] [NotifyPropertyChangedFor(nameof(DisplayName))] private string productName = "Keyboard"; public string DisplayName => string.IsNullOrWhiteSpace(ProductName) ? "未填写商品名称" : ProductName.Trim(); }

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="520" Height="280" Title="属性绑定检查"> <StackPanel Margin="20" Spacing="10"> <TextBlock Text="商品名称" /> <TextBox Text="{Binding ProductName, Mode=TwoWay}" /> <TextBlock Text="{Binding DisplayName}" /> </StackPanel> </Window>

必须能解释

代码解释
Mode=TwoWay输入框变化会写回 ViewModel
DisplayName根据 ProductName 算出来的显示属性
NotifyPropertyChangedForProductName 变化时通知 DisplayName 刷新

第三部分:阶段检查 2 - 命令和验证

目标:确认按钮能绑定命令,输入错误时按钮不可执行。

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 = ""; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private string priceText = "199"; [ObservableProperty] private string message = "请输入商品信息。"; 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="540" Height="340" Title="命令验证检查"> <StackPanel Margin="20" Spacing="10"> <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>

必须验证

输入应该看到
名称空保存按钮不可点击
价格 abc保存按钮不可点击
价格 0保存按钮不可点击
名称 Keyboard,价格 199保存按钮可点击,保存后显示成功

第四部分:阶段检查 3 - 集合绑定

目标:确认 ObservableCollection 能绑定列表,新增后界面自动刷新。

ViewModels/MainWindowViewModel.cs 完整答案

using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ProductApp.ViewModels; public partial class MainWindowViewModel : ViewModelBase { private int nextNumber = 4; public ObservableCollection<ProductItem> Products { get; } = new() { new ProductItem { Name = "Keyboard", Price = 199 }, new ProductItem { Name = "Mouse", Price = 99 }, new ProductItem { Name = "Monitor", Price = 899 } }; [ObservableProperty] [NotifyPropertyChangedFor(nameof(SelectedName))] private ProductItem? selectedProduct; public string SelectedName => SelectedProduct?.Name ?? "未选择商品"; public MainWindowViewModel() { SelectedProduct = Products[0]; } [RelayCommand] private void AddProduct() { var product = new ProductItem { Name = $"Product {nextNumber}", Price = nextNumber * 100 }; nextNumber++; Products.Add(product); SelectedProduct = product; } } public sealed class ProductItem { public string Name { get; set; } = ""; public decimal Price { get; set; } }

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="700" Height="400" Title="集合绑定检查"> <Grid ColumnDefinitions="260,*" ColumnSpacing="16" Margin="20"> <ListBox Grid.Column="0" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"> <ListBox.ItemTemplate> <DataTemplate x:DataType="vm:ProductItem"> <StackPanel> <TextBlock Text="{Binding Name}" FontWeight="Bold" /> <TextBlock Text="{Binding Price}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <StackPanel Grid.Column="1" Spacing="10"> <TextBlock Text="当前商品" FontSize="20" FontWeight="Bold" /> <TextBlock Text="{Binding SelectedName}" /> <Button Content="新增商品" Command="{Binding AddProductCommand}" /> </StackPanel> </Grid> </Window>

必须能解释

代码解释
ObservableCollection<ProductItem>新增删除时通知界面
ItemsSource="{Binding Products}"列表显示商品集合
DataTemplate x:DataType="vm:ProductItem"定义每个商品怎么显示
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"界面选中项和 ViewModel 当前项同步

第五部分:常见错误完整修法

现象原因修法
绑定属性找不到XAML 绑定名和 ViewModel 属性名不同对照属性名大小写
输入框改了但 TextBlock 不变缺少 NotifyPropertyChangedFor给源字段加通知
命令找不到方法没有 [RelayCommand]给方法加 [RelayCommand]
按钮状态不刷新缺少 NotifyCanExecuteChangedFor给影响 CanSave 的字段加通知
列表不刷新使用普通 List<T>改用 ObservableCollection<T>
列表项绑定报错DataTemplate 没写类型x:DataType="vm:ProductItem"
当前项为空时报错没处理 SelectedProductnull使用 SelectedProduct?.Name ?? "未选择商品"

第六部分:通过标准

达到下面标准,再进入 Week 8。

  1. 能解释 View 和 ViewModel 的区别。
  2. 能设置并检查 DataContext
  3. 能用 [ObservableProperty] 写可绑定属性。
  4. 能用 Mode=TwoWay 绑定输入框。
  5. 能用 [RelayCommand] 写按钮命令。
  6. 能用 CanExecute 禁用按钮。
  7. 能用 decimal.TryParse 做价格验证。
  8. 能用 ObservableCollection 绑定列表。
  9. 能用 SelectedItem 表示当前选中项。
  10. 能重做一个内存版 MVVM CRUD 页面。

通过标准不是把每个特性背下来,而是能用它们完成一个清晰的数据流:输入、验证、命令、列表、反馈。