Week 08 - 阶段检查
这一页用来确认 Week 8 的界面组织能力是否跑通。检查方式是重做三个小窗口:Style、DataTemplate、UserControl。
本周学习地图
| 阶段 | 先掌握什么 | 最终能写什么 |
|---|---|---|
| Style | 控件外观复用 | 能统一按钮、输入框、文字 |
| DataTemplate | 数据项显示结构 | 能让商品列表不显示类型名 |
| ControlTemplate | 控件内部结构 | 能理解何时重写控件结构 |
| UserControl | 可复用界面块 | 能抽出商品卡片 |
| Theme | 资源和样式拆分 | 能管理全局外观 |
| UI 状态 | 空、加载、错误、禁用 | 能让页面状态清楚 |
本周自测项目:商品卡片组件
从一个商品列表页面开始整理界面,至少包含:
- 一个商品
DataTemplate。 - 一个可复用
ProductCard。 - 页面显示空状态、加载状态、错误提示。
- 按钮样式只做最少统一,不影响默认可用性。
- 列表项显示名称、价格、库存状态。
过关标准:能解释什么时候用 Style、DataTemplate、UserControl,不能混用。
上位机阶段验收:可复用的工业监控界面
本周目标: 用 Style、DataTemplate 和 UserControl 统一设备、状态和报警展示。
必须亲手完成:
- 在线、离线、报警同时用文字和颜色表达
- DeviceCard 可显示任意设备
- 空状态、加载状态和失败状态都有反馈
过关标准: 新增设备不需要复制一整段界面代码,所有页面视觉和操作含义一致。
不要只看答案。新建一个空项目重做一次,运行成功后再故意改坏一处并自己排错。
本周流程图
页面出现重复外观
-> Style 统一控件属性
-> 列表项显示重复
-> DataTemplate 统一数据展示
-> 页面块重复
-> UserControl 封装可复用组件
-> 空、加载、错误、禁用状态由 ViewModel 控制第一部分:本周原理地图
Week 8 的主线是:减少界面重复,让外观、数据项显示、控件结构、组件复用各有固定位置。
本周能力拆解
| 能力 | 你要能做到什么 |
|---|---|
| Style | 统一控件外观 |
| DataTemplate | 统一数据项显示结构 |
| ControlTemplate | 改控件内部结构并保留内容显示 |
| UserControl | 封装可复用界面块 |
| Theme | 拆分全局资源和样式文件 |
| UI 状态 | 显示空、加载、错误、禁用状态 |
本周最重要的判断表
| 需求 | 应该用 |
|---|---|
| 多个按钮颜色一致 | Style |
| 商品列表每项显示三行 | DataTemplate |
| 按钮内部结构要重写 | ControlTemplate |
| 商品卡片多个页面复用 | UserControl |
| 全局颜色统一管理 | Themes/Colors.axaml |
| 没数据时显示提示 | IsEmpty + IsVisible |
Week 8 必须会用的写法
| 主题 | 必须会的写法 |
|---|---|
| 资源 | x:Key、StaticResource |
| 样式 | Style Selector、Classes |
| 数据模板 | DataTemplate、x:DataType |
| 列表模板 | ListBox.ItemTemplate |
| 控件模板 | ControlTemplate、TemplateBinding |
| 用户控件 | UserControl、xmlns:views |
| 主题拆分 | 全局资源、页面资源 |
| 状态体验 | IsVisible、IsEnabled、Message、ErrorMessage |
判断优先级:
默认样式能解决 -> 不自定义
数据项重复显示 -> DataTemplate
界面块重复出现 -> UserControl
外观值重复很多 -> Resource 或 Style
控件内部结构必须改变 -> ControlTemplate第二部分:阶段检查 1 - Style
目标:确认你能用 Style 和 Classes 统一按钮外观。
Views/MainWindow.axaml 完整答案
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ProductApp.Views.MainWindow"
Width="480"
Height="260"
Title="Style 检查">
<Window.Styles>
<Style Selector="Button.primary">
<Setter Property="Background" Value="#2563EB" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Padding" Value="12,6" />
</Style>
</Window.Styles>
<StackPanel Margin="20"
Spacing="10">
<Button Classes="primary"
Content="保存" />
<Button Content="取消" />
</StackPanel>
</Window>必须能解释
| 代码 | 解释 |
|---|---|
Style Selector="Button.primary" | 只影响带 primary 类的按钮 |
Classes="primary" | 让这个按钮套用 primary 样式 |
| 普通取消按钮 | 没有 class,所以保持默认外观 |
第三部分:阶段检查 2 - DataTemplate
目标:确认你能让列表里的对象按模板显示。
ViewModels/MainWindowViewModel.cs 完整答案
using System.Collections.ObjectModel;
namespace ProductApp.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ProductItem> Products { get; } = new()
{
new ProductItem { Name = "Keyboard", Price = 199 },
new ProductItem { Name = "Mouse", Price = 99 }
};
}
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="520"
Height="320"
Title="DataTemplate 检查">
<ListBox Margin="20"
ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ProductItem">
<StackPanel Spacing="4">
<TextBlock Text="{Binding Name}"
FontWeight="Bold" />
<TextBlock Text="{Binding Price}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>必须能解释
| 代码 | 解释 |
|---|---|
ItemsSource="{Binding Products}" | ListBox 显示商品集合 |
DataTemplate x:DataType="vm:ProductItem" | 模板内部绑定当前商品 |
Text="{Binding Name}" | 绑定当前商品的名称 |
第四部分:阶段检查 3 - UserControl
目标:确认你能封装一个可复用商品卡片,并在页面使用它。
Views/ProductCard.axaml 完整答案
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ProductApp.Views.ProductCard"
x:Name="Root">
<Border BorderBrush="#DDDDDD"
BorderThickness="1"
Padding="12"
Margin="0,0,0,8">
<StackPanel Spacing="4">
<TextBlock Text="{Binding Title, ElementName=Root}"
FontWeight="Bold" />
<TextBlock Text="{Binding Price, ElementName=Root}" />
</StackPanel>
</Border>
</UserControl>Views/ProductCard.axaml.cs 完整答案
using Avalonia;
using Avalonia.Controls;
namespace ProductApp.Views;
public partial class ProductCard : UserControl
{
public static readonly StyledProperty<string> TitleProperty =
AvaloniaProperty.Register<ProductCard, string>(nameof(Title), "");
public static readonly StyledProperty<decimal> PriceProperty =
AvaloniaProperty.Register<ProductCard, decimal>(nameof(Price));
public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public decimal Price
{
get => GetValue(PriceProperty);
set => SetValue(PriceProperty, value);
}
public ProductCard()
{
InitializeComponent();
}
}Views/MainWindow.axaml 完整答案
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:ProductApp.Views"
x:Class="ProductApp.Views.MainWindow"
Width="520"
Height="320"
Title="UserControl 检查">
<StackPanel Margin="20"
Spacing="8">
<views:ProductCard Title="Keyboard"
Price="199" />
<views:ProductCard Title="Mouse"
Price="99" />
</StackPanel>
</Window>必须能解释
| 代码 | 解释 |
|---|---|
StyledProperty | 让外部 XAML 可以给组件传值 |
ElementName=Root | UserControl 内部绑定自己的属性 |
xmlns:views | 让 MainWindow 能使用 ProductCard |
第五部分:常见错误完整修法
| 现象 | 原因 | 修法 |
|---|---|---|
| 样式不生效 | selector 或 class 不匹配 | 检查 Button.primary 和 Classes="primary" |
| DataTemplate 绑定空白 | 模板内部类型不明确 | 写 x:DataType="vm:ProductItem" |
| 按钮模板文字消失 | ControlTemplate 没有 ContentPresenter | 加 ContentPresenter Content="{TemplateBinding Content}" |
| UserControl 属性不显示 | 内部绑定到外部 DataContext | 改成 ElementName=Root |
| 主题文件找不到 | Include 路径错 | 检查 avares://ProductApp/Themes/... |
| 空状态不刷新 | 集合变化后没通知计算属性 | 调用 OnPropertyChanged(nameof(IsEmpty)) |
第六部分:通过标准
达到下面标准,再进入 Week 9。
- 能用
Style和Classes复用控件外观。 - 能用
DataTemplate显示对象列表。 - 能解释模板内部绑定的是当前项。
- 能写一个最小
ControlTemplate并保留ContentPresenter。 - 能写一个带
StyledProperty的 UserControl。 - 能用
ResourceInclude和StyleInclude拆分主题文件。 - 能用 ViewModel 状态显示空状态和加载状态。
- 能判断一个界面问题该用 Style、DataTemplate、ControlTemplate 还是 UserControl。
Week 8 的通过标准是:界面不再靠复制粘贴堆出来,而是能按职责拆成样式、模板、组件和状态。