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

Day 7 - 复盘与巩固

建议用时:180-210 分钟

你将学会什么

  • 把 Week 6 的知识连成一条完整开发路线。
  • 能说清 App.axamlMainWindow.axamlMainWindow.axaml.cs 分别负责什么。
  • 能从 XAML 的按钮点击追踪到 C# 事件方法。
  • 能解释布局、控件、资源样式、表单校验、列表详情之间的关系。
  • 能重新敲出一个商品管理窗口,并完成基本排错。

复盘不是把前 6 天标题重新看一遍。复盘要能回答三件事:这个知识解决什么问题、代码放在哪个文件、出错时先查哪里。

本页固定顺序

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

学习衔接

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

今天的最低通过线

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

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

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

这一部分把 Week 6 的知识重新排成路线。先看清“每个知识点用来解决什么问题”,再去第三部分动手。

1. 本周知识地图

日期主题解决的问题最重要的判断
Day 1项目结构知道项目怎么启动入口在 Program.cs,窗口在 MainWindow.axaml
Day 2XAML 布局控件放在哪里简单排队用 StackPanel,行列结构用 Grid
Day 3常用控件用户看、输、点、选显示用 TextBlock,输入用 TextBox,动作靠 Button
Day 4资源与样式重复外观集中管理重复的值提资源,重复的外观提样式
Day 5表单页面输入、校验、反馈保存前先校验,不合格就停止
Day 6列表详情多条数据和当前一条数据ItemsSource 是数据来源,SelectedItem 是当前项

2. 文件分别负责什么

文件负责什么不应该负责什么
Program.cs启动 Avalonia 应用具体页面控件
App.axaml应用级资源和主题某个按钮点击逻辑
MainWindow.axaml当前窗口的结构、布局、控件复杂业务规则
MainWindow.axaml.cs当前窗口的事件处理大量跨页面业务逻辑
ViewModels/*.cs后面学习 MVVM 时放界面状态和命令当前周还不强行使用

Week 6 先用 MainWindow.axaml.cs 处理事件,是为了看清控件和 C# 代码如何连接。后面迁移到 ViewModel 时,处理流程不变,只是代码位置更清晰。

3. XAML 和 C# 如何连接

XAML:

<Button Content="保存" Click="OnSaveClick" />

C#:

private void OnSaveClick(object? sender, RoutedEventArgs e) { ResultText.Text = "已保存。"; }

这就是连接点:

用户点击按钮 -> Avalonia 找到 Click 指定的方法 -> 执行 OnSaveClick -> C# 修改界面控件

如果按钮点击没有反应,先查这三件事:

  1. XAML 里有没有写 Click="OnSaveClick"
  2. C# 方法名是不是也叫 OnSaveClick
  3. C# 方法参数是不是 object? sender, RoutedEventArgs e

4. 布局怎么选

场景推荐容器原因
几个控件从上到下排StackPanel简单直接
标签和输入框左右对齐Grid行列清楚
顶部、左侧、主内容区DockPanelGrid适合页面骨架
一块内容要边框和内边距Border区域感清楚

不要一开始就想“这个按钮写在哪里”。先想页面大区域,再想每个区域内部怎么排。

5. 表单保存为什么必须校验

表单里的输入都是用户输入的文本,程序不能直接相信。

例如价格输入框可能出现:

abc -1 0 199 199.00

所以保存顺序必须是:

读取文本 -> 去掉前后空格 -> 必填校验 -> 数字解析 -> 范围校验 -> 保存 -> 显示结果

只要中间某一步失败,就 return 停止,不要继续保存。

6. 列表详情为什么要判断 SelectedItem

ListBox.SelectedItem 可能没有值。比如列表为空,或者用户取消选择。

所以要先写:

Product? product = ProductList.SelectedItem as Product; if (product is null) { ResultText.Text = "请先选择一个商品。"; return; }

这不是多余代码。它是在防止“没有选中任何东西,却继续读取商品字段”的错误。

7. 本周最常见错误和直接修法

现象常见原因直接修法
XAML 编译报错标签没闭合或属性写错先看第一条错误的行号
点击按钮没反应Click 方法名不一致让 XAML 和 C# 方法名完全一致
C# 找不到控件名XAML 没有 x:Name给需要读取的控件加 x:Name
列表显示类型名对象没重写 ToString()Product 里重写 ToString()
价格输入字母后崩掉直接转换数字改用 decimal.TryParse
保存后列表不刷新普通 List<T> 不自动通知界面重新设置一次 ItemsSource

Week 6 Avalonia 常用写法总表

主题常用写法
创建项目dotnet new avalonia.mvvm
运行窗口dotnet run
布局StackPanelGridDockPanel
输入控件TextBox Text="{Binding ..., Mode=TwoWay}"
按钮命令Button Command="{Binding SaveCommand}"
列表ListBox ItemsSource="{Binding Products}"
选中项SelectedItem="{Binding SelectedProduct}"
列表刷新ObservableCollection<T>
错误提示ErrorMessage 绑定到 TextBlock
空状态IsVisible="{Binding IsEmpty}"
样式资源StaticResourceClasses

Week 6 重点不是把界面做复杂,而是把 XAML、绑定、命令、列表和详情这条线跑通。

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

Week 6 的目标不是只会写一个按钮,而是能写出一个小型桌面窗口:左侧列表、右侧详情、能选择、能编辑、能校验、能保存反馈。

本周最终闭环

Avalonia 项目启动 -> MainWindow.axaml 描述窗口结构 -> Grid / StackPanel / Border 摆布局 -> TextBox / Button / ListBox 做输入和操作 -> Click / SelectionChanged 连接到 C# 方法 -> C# 读取控件值 -> C# 校验数据 -> C# 修改当前商品 -> 界面显示结果

先看本周综合效果

综合窗口包含:

  • 顶部标题。
  • 左侧商品列表。
  • 右侧商品详情表单。
  • 保存按钮。
  • 错误或成功提示。

真正要记住的是:界面不是孤立控件,而是一条从用户操作到 C# 处理,再回到界面反馈的数据流。

第三部分:跟着敲代码

从这里开始动手。今天只做两个练习:先跑通按钮事件链,再跑通本周综合窗口。

例子 1:按钮事件链复盘

这个例子用最短路径复习 XAML 和 C# 的连接。

MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ProductApp.Views.MainWindow" Width="420" Height="240" Title="事件链复盘"> <StackPanel Margin="20" Spacing="12"> <TextBlock Text="点击按钮,C# 会修改下面的文字。" /> <Button Content="保存" Click="OnSaveClick" /> <TextBlock x:Name="ResultText" Text="还没有点击。" /> </StackPanel> </Window>

MainWindow.axaml.cs

using Avalonia.Controls; using Avalonia.Interactivity; namespace ProductApp.Views; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void OnSaveClick(object? sender, RoutedEventArgs e) { ResultText.Text = "按钮点击事件已经执行。"; } }

运行后你要能说清:

Button 的 Click 找到 OnSaveClick -> OnSaveClick 修改 ResultText.Text -> 界面文字变化

例子 2:本周综合窗口

这个练习把布局、控件、资源样式、表单校验、列表详情合在一起。

MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ProductApp.Views.MainWindow" Width="780" Height="480" Title="Week 6 综合练习"> <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> <Style Selector="Button.primary"> <Setter Property="Background" Value="{StaticResource PrimaryBrush}" /> <Setter Property="Foreground" Value="White" /> <Setter Property="Padding" Value="12,6" /> </Style> </Window.Styles> <Grid RowDefinitions="Auto,*" ColumnDefinitions="230,*" RowSpacing="12" ColumnSpacing="16" Margin="20"> <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Classes="title" Text="商品管理综合练习" /> <Border Grid.Row="1" Grid.Column="0" Classes="card"> <StackPanel Spacing="8"> <TextBlock Text="商品列表" FontWeight="Bold" /> <ListBox x:Name="ProductList" SelectionChanged="OnProductSelectionChanged" /> </StackPanel> </Border> <Border Grid.Row="1" Grid.Column="1" Classes="card"> <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*" ColumnDefinitions="Auto,*" RowSpacing="10" ColumnSpacing="12"> <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="商品详情" FontSize="18" FontWeight="Bold" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="名称" VerticalAlignment="Center" /> <TextBox x:Name="NameBox" Grid.Row="1" Grid.Column="1" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="价格" VerticalAlignment="Center" /> <TextBox x:Name="PriceBox" Grid.Row="2" Grid.Column="1" /> <TextBlock Grid.Row="3" Grid.Column="0" Text="分类" VerticalAlignment="Center" /> <TextBox x:Name="CategoryBox" Grid.Row="3" Grid.Column="1" /> <TextBlock Grid.Row="4" Grid.Column="0" Text="状态" VerticalAlignment="Center" /> <CheckBox x:Name="EnabledBox" Grid.Row="4" Grid.Column="1" Content="启用商品" /> <Button Grid.Row="5" Grid.Column="1" Classes="primary" Content="保存当前商品" HorizontalAlignment="Right" Click="OnSaveClick" /> <TextBlock x:Name="ResultText" Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" TextWrapping="Wrap" Text="请选择左侧商品。" /> </Grid> </Border> </Grid> </Window>

MainWindow.axaml.cs

using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Interactivity; namespace ProductApp.Views; public partial class MainWindow : Window { private readonly List<Product> _products = new() { new Product { Name = "Keyboard", Price = 199, Category = "普通商品", Enabled = true }, new Product { Name = "Mouse", Price = 99, Category = "普通商品", Enabled = true }, new Product { Name = "Online Course", Price = 299, Category = "虚拟商品", Enabled = false } }; public MainWindow() { InitializeComponent(); ProductList.ItemsSource = _products; ProductList.SelectedIndex = 0; } private void OnProductSelectionChanged(object? sender, SelectionChangedEventArgs e) { Product? product = ProductList.SelectedItem as Product; if (product is null) { ClearDetail(); ResultText.Text = "没有选中商品。"; return; } ShowProduct(product); } private void OnSaveClick(object? sender, RoutedEventArgs e) { Product? product = ProductList.SelectedItem as Product; if (product is null) { ResultText.Text = "请先选择一个商品。"; return; } string name = NameBox.Text?.Trim() ?? ""; if (name.Length == 0) { ResultText.Text = "商品名称不能为空。"; return; } string priceText = PriceBox.Text?.Trim() ?? ""; if (!decimal.TryParse(priceText, out decimal price)) { ResultText.Text = "价格必须是数字。"; return; } if (price <= 0) { ResultText.Text = "价格必须大于 0。"; return; } product.Name = name; product.Price = price; product.Category = CategoryBox.Text?.Trim() ?? ""; product.Enabled = EnabledBox.IsChecked == true; int selectedIndex = ProductList.SelectedIndex; ProductList.ItemsSource = null; ProductList.ItemsSource = _products; ProductList.SelectedIndex = selectedIndex; ResultText.Text = $"已保存:{product.Name}"; } private void ShowProduct(Product product) { NameBox.Text = product.Name; PriceBox.Text = product.Price.ToString(); CategoryBox.Text = product.Category; EnabledBox.IsChecked = product.Enabled; ResultText.Text = $"当前选中:{product.Name}"; } private void ClearDetail() { NameBox.Text = ""; PriceBox.Text = ""; CategoryBox.Text = ""; EnabledBox.IsChecked = false; } private sealed class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } public string Category { get; set; } = ""; public bool Enabled { get; set; } public override string ToString() { return $"{Name} - {Price}"; } } }

例子 3:故意制造错误并修复

复盘时要主动练排错。下面这些错误都很常见。

错误 1:按钮方法名不一致

错误写法:

<Button Content="保存" Click="SaveClick" />

C# 实际方法:

private void OnSaveClick(object? sender, RoutedEventArgs e) { }

修法:让 XAML 和 C# 名字完全一致。

<Button Content="保存" Click="OnSaveClick" />

错误 2:控件没有名字

错误写法:

<TextBox />

C# 却写:

string name = NameBox.Text ?? "";

修法:XAML 里补上 x:Name

<TextBox x:Name="NameBox" />

错误 3:价格直接转换

错误写法:

decimal price = decimal.Parse(PriceBox.Text);

用户输入 abc 时,程序会抛异常。

修法:用 TryParse

if (!decimal.TryParse(PriceBox.Text, out decimal price)) { ResultText.Text = "价格必须是数字。"; return; }

错误 4:没有判断选中项

错误写法:

Product product = (Product)ProductList.SelectedItem!; NameBox.Text = product.Name;

如果没有选中项,这段代码会出问题。

修法:先判断。

Product? product = ProductList.SelectedItem as Product; if (product is null) { ResultText.Text = "请先选择一个商品。"; return; }

小白重复敲写训练

复盘日从三个空窗口片段重新敲,不看完整答案。

训练 1:表单布局

<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="8"> <TextBlock Text="名称" /> <TextBox Grid.Column="1" /> <Button Grid.Row="1" Grid.Column="1" Content="保存" /> </Grid>

训练 2:统一按钮样式

<Style Selector="Button.action"> <Setter Property="Padding" Value="16,8" /> <Setter Property="Background" Value="#2563EB" /> </Style>

训练 3:按钮点击闭环

private void Save_Click(object? sender, RoutedEventArgs e) { MessageText.Text = string.IsNullOrWhiteSpace(NameInput.Text) ? "请输入名称" : $"已保存 {NameInput.Text.Trim()}"; }

把三个训练组合到一个窗口,运行成功后再关页面独立重写。

每日小测

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

1. 判断题

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

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

2. 填空题

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

1. 把 Week 6 的知识连成一条完整开发路线。 2. 能说清 `App.axaml`、`MainWindow.axaml`、`MainWindow.axaml.cs` 分别负责什么。 3. 能从 XAML 的按钮点击追踪到 C# 事件方法。

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

3. 流程题

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

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

4. 找错误题

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

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

5. 改需求题

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

可选改法:

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

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:把 Week 6 的知识连成一条完整开发路线。。

上位机专项练习

本周过关标准是能独立搭窗口、分区布局、使用常用控件并完成设备配置页。

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

专项例子 1:复习窗口骨架

<Grid RowDefinitions="Auto,*" Margin="20"> <TextBlock Text="上位机控制台" FontSize="28" /> <ContentControl Grid.Row="1" Margin="0,16,0,0" /> </Grid>

运行结果或界面效果:

上方标题,下方内容区

改动任务: 增加底部状态栏。

专项例子 2:复习监控卡片

<Border Padding="16" CornerRadius="8" Background="#F5F7FA"> <StackPanel> <TextBlock Text="PLC-01" FontWeight="Bold" /> <TextBlock Text="在线" Foreground="Green" /> <TextBlock Text="温度 26.8 C" /> </StackPanel> </Border>

运行结果或界面效果:

一张设备状态卡

改动任务: 再复制一张 PLC-02 卡片并显示报警。

专项例子 3:复习点击事件

private void Start_Click(object? sender, RoutedEventArgs e) { StatusText.Text = "正在采集"; StartButton.IsEnabled = false; StopButton.IsEnabled = true; }

运行结果或界面效果:

点击开始后状态和按钮可用性一起改变

改动任务: 补写 Stop_Click,恢复两个按钮。

第四部分:作业完整答案

这一部分给出本周复盘作业的完整答案。代码答案就是第三部分的“本周综合窗口”,下面补齐概念题、排错题和验收答案。

作业 1:写出 Week 6 主线

完整答案:

Week 6 主线: 1. 创建 Avalonia 项目,知道应用怎么启动。 2. 用 XAML 描述窗口结构。 3. 用 Grid、StackPanel、Border 管理布局。 4. 用 TextBlock、TextBox、Button、ListBox 做显示、输入、点击、选择。 5. 用 Click 和 SelectionChanged 把用户操作连接到 C# 方法。 6. 在 C# 方法里读取控件值,做校验。 7. 校验通过后修改数据。 8. 最后把处理结果显示回界面。

作业 2:解释 8 个关键词

完整答案:

关键词解释
Window一个桌面窗口,里面放布局和控件
Grid按行列切分空间的布局容器
StackPanel按水平或垂直方向排列子控件的布局容器
TextBox接收用户文本输入的控件
Button触发动作的控件
Click按钮点击时触发的事件
ItemsSourceListBox 要显示的数据来源
SelectedItemListBox 当前选中的那一项

作业 3:写出保存按钮的数据流

完整答案:

用户点击保存按钮 -> Avalonia 执行 OnSaveClick -> C# 从 NameBox、PriceBox、CategoryBox、EnabledBox 读取值 -> 名称做空值校验 -> 价格用 decimal.TryParse 解析 -> 价格做大于 0 校验 -> 读取当前 ProductList.SelectedItem -> 把输入值写回当前 Product -> 刷新 ProductList.ItemsSource -> ResultText 显示保存结果

作业 4:排错题完整答案

现象原因修法
OnSaveClick 找不到XAML 的 Click 名字和 C# 方法名不一致保持完全一致
NameBox 找不到TextBox 没写 x:Name="NameBox"补上 x:Name
RoutedEventArgs 找不到缺少命名空间添加 using Avalonia.Interactivity;
列表显示类型名Product 没有重写 ToString()添加 public override string ToString()
输入 abc 后价格处理失败没用 TryParse改成 decimal.TryParse
保存后列表文字没变化普通 List<Product> 不自动通知界面保存后重新设置 ItemsSource

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

你的 Week 6 综合窗口必须满足:

  1. 项目能启动,没有 XAML 编译错误。
  2. 左侧列表显示三个商品。
  3. 启动后默认选中第一个商品。
  4. 点击左侧其他商品,右侧详情跟着变化。
  5. 名称清空后点击保存,显示 商品名称不能为空。
  6. 价格输入 abc 后点击保存,显示 价格必须是数字。
  7. 价格输入 0 后点击保存,显示 价格必须大于 0。
  8. 修改名称和价格后保存,左侧列表显示新名称和新价格。
  9. 你能说清 MainWindow.axaml 负责界面,MainWindow.axaml.cs 负责事件处理。
  10. 你能从一个按钮点击追踪到 C# 方法执行。

本周真正要带走的能力

Week 6 学完后,应该能做到:

看到一个桌面窗口需求 -> 先拆页面区域 -> 选择布局容器 -> 放入控件 -> 给需要读取的控件命名 -> 给按钮或列表选择加事件 -> 在 C# 中读取输入 -> 做必要校验 -> 更新界面反馈

这条路线比单独记住某个控件属性更重要。