source-guide
怎么加一个页面
从零加一个设备调试页,走完 MVVM 的完整套路。
这一篇用一个真实任务把 MVVM 的约定走一遍:加一个「吸嘴真空调试」页。
1. ViewModel
// 07.PreciSim.App/ViewModels/VacuumDebugViewModel.cs
public partial class VacuumDebugViewModel : PageViewModelBase
{
private readonly IDigitalIo _io;
public VacuumDebugViewModel(IDigitalIo io, ILogger<VacuumDebugViewModel> logger)
: base(logger)
{
_io = io;
}
[ObservableProperty] private bool _vacuumOn;
[ObservableProperty] private bool _partDetected;
[RelayCommand(CanExecute = nameof(CanToggle))]
private async Task ToggleVacuumAsync(CancellationToken ct)
{
await _io.WriteAsync(IoMap.VacuumValve, !VacuumOn);
VacuumOn = !VacuumOn;
}
private bool CanToggle() => !IsBusy && ConnectionState == DeviceState.Connected;
protected override void OnActivated()
{
// 页面可见时才订阅 IO 事件,离开时自动退订(基类处理)
Subscribe(_io.Changes, OnIoChanged);
}
private void OnIoChanged(IoChange c)
{
if (c.Channel == IoMap.PartSensor) PartDetected = c.Value;
}
}要点:
- 用
CommunityToolkit.Mvvm的[ObservableProperty]/[RelayCommand],不手写INotifyPropertyChanged; - 命令带
CanExecute,按钮的可用状态由它驱动,不在 View 里写IsEnabled的逻辑; - 订阅走基类的
Subscribe,页面切走自动退订——忘了退订是 WPF 内存泄漏的头号原因。
2. View
<!-- 07.PreciSim.App/Views/VacuumDebugView.xaml -->
<UserControl x:Class="PreciSim.App.Views.VacuumDebugView"
d:DataContext="{d:DesignInstance vm:VacuumDebugViewModel}">
<StackPanel Margin="16" Orientation="Vertical">
<ToggleButton Content="真空"
IsChecked="{Binding VacuumOn, Mode=OneWay}"
Command="{Binding ToggleVacuumCommand}" />
<TextBlock Text="{Binding PartDetected, Converter={StaticResource BoolToPresence}}" />
</StackPanel>
</UserControl>要点:用标准 {Binding},不引入任何绑定 DSL。
代码后置文件里只允许有 InitializeComponent(),别的一行都不写。
3. 注册
// 07.PreciSim.App/Startup/ViewRegistration.cs
services.AddTransient<VacuumDebugViewModel>();
services.AddTransient<VacuumDebugView>();
// 07.PreciSim.App/Navigation/PageCatalog.cs
new PageEntry(
id: "debug.vacuum",
titleKey: "Page.VacuumDebug", // 中英文文案在 Resources/Strings.*.resx
icon: Icons.Vacuum,
requiredRole: UserRole.Engineer, // 权限分级在这里声明,不在页面里判断
viewModel: typeof(VacuumDebugViewModel))4. 文案
<!-- Resources/Strings.zh-CN.resx -->
<data name="Page.VacuumDebug"><value>真空调试</value></data>
<!-- Resources/Strings.en.resx -->
<data name="Page.VacuumDebug"><value>Vacuum debug</value></data>两个 resx 都要加。 CI 里有一条测试会比对两份资源的键集合,缺一个就失败—— 和这个网站的中英文字典是同一套思路。
5. 测试
[Fact]
public async Task ToggleVacuum_WritesToIo()
{
var io = new FakeDigitalIo();
var vm = new VacuumDebugViewModel(io, NullLogger<VacuumDebugViewModel>.Instance);
await vm.ToggleVacuumCommand.ExecuteAsync(null);
Assert.True(io.LastWrite(IoMap.VacuumValve));
Assert.True(vm.VacuumOn);
}ViewModel 不依赖 WPF,所以这类测试跑得飞快,不需要 UI 线程。 这就是坚持 MVVM 的实际回报,不是为了架构好看。
检查清单
- ViewModel 里没有
Dispatcher、没有MessageBox(弹窗走IDialogService) - View 的代码后置只有
InitializeComponent() - 两份 resx 都加了文案
- 页面声明了
requiredRole - 有一个不依赖 UI 的单元测试
最后更新: 2026/9/21
这页有帮助吗?