7.3 KiB
7.3 KiB
软件开发文档 - 04. DMS.WPF 详细设计 (修订版)
DMS.WPF 是系统的用户界面层。本文档详细描述其设计,此修订版特别强调了构建响应式UI的核心模式。
1. 核心设计模式
1.1. MVVM (Model-View-ViewModel)
严格遵循MVVM模式,实现UI与逻辑的彻底分离。
1.2. 依赖注入 (Dependency Injection)
使用 Microsoft.Extensions.DependencyInjection 统一管理所有服务的生命周期,实现松耦合。
1.3. ItemViewModel 模式
问题:从应用层获取的DTO(如DeviceDto)是简单数据对象,不具备变更通知能力。当后台数据变化时,直接绑定DTO的UI无法自动更新。
解决方案:为集合中的每一项数据(如每个设备)创建一个专属的、能发出变更通知的ViewModel。这个ItemViewModel会“包裹”原始的DTO,并暴露绑定到UI的属性。当ItemViewModel的属性更新时,UI会通过数据绑定自动刷新。
1.4. 消息总线 (Messenger)
问题:后台服务(如S7通信服务)与UI线程中的ViewModel需要通信(例如,通知设备状态已更新),但它们之间不应有直接引用,否则会造成紧耦合。
解决方案:使用消息总线(或事件聚合器)作为解耦的通信中介。CommunityToolkit.Mvvm 提供了轻量级的 IMessenger 实现。后台服务向总线“广播”消息,所有“订阅”了该消息的ViewModel都会收到通知并做出响应。
2. 目录结构 (修订版)
DMS.WPF/
├── App.xaml.cs
├── ...
├── Messages/ <-- (新增) 存放消息总线的消息类
│ └── DeviceStatusChangedMessage.cs
├── Services/
│ ├── ... (导航、对话框服务)
├── ViewModels/
│ ├── Base/ <-- 存放ViewModel基类和命令
│ │ ├── BaseViewModel.cs
│ │ └── RelayCommand.cs
│ ├── Items/ <-- (新增) 存放所有ItemViewModel
│ │ └── DeviceItemViewModel.cs
│ ├── DashboardViewModel.cs
│ ├── DeviceListViewModel.cs <-- (已修改)
│ └── MainViewModel.cs
├── Views/
│ └── ...
└── DMS.WPF.csproj
3. 文件详解
3.1. 消息 (Messages/)
DeviceStatusChangedMessage.cs
// 文件: DMS.WPF/Messages/DeviceStatusChangedMessage.cs
namespace DMS.WPF.Messages;
/// <summary>
/// 当设备状态在后台发生变化时,通过IMessenger广播此消息。
/// </summary>
public class DeviceStatusChangedMessage
{
/// <summary>
/// 状态发生变化的设备ID。
/// </summary>
public int DeviceId { get; }
/// <summary>
/// 设备的新状态文本 (例如: "在线", "离线", "错误")。
/// </summary>
public string NewStatus { get; }
public DeviceStatusChangedMessage(int deviceId, string newStatus)
{
DeviceId = deviceId;
NewStatus = newStatus;
}
}
3.2. ItemViewModel (ViewModels/Items/)
DeviceItemViewModel.cs
// 文件: DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using DMS.Application.DTOs;
namespace DMS.WPF.ViewModels.Items;
/// <summary>
/// 代表设备列表中的单个设备项的ViewModel。
/// 实现了INotifyPropertyChanged,其任何属性变化都会自动通知UI。
/// </summary>
public partial class DeviceItemViewModel : ObservableObject
{
public int Id { get; }
[ObservableProperty]
private string _name;
[ObservableProperty]
private string _protocol;
[ObservableProperty]
private string _ipAddress;
[ObservableProperty]
private bool _isActive;
[ObservableProperty]
private string _status; // 这个属性的改变会立刻反映在UI上
public DeviceItemViewModel(DeviceDto dto)
{
Id = dto.Id;
_name = dto.Name;
_protocol = dto.Protocol;
_ipAddress = dto.IpAddress;
_isActive = dto.IsActive;
_status = dto.Status; // 初始状态
}
}
3.3. 核心视图模型 (ViewModels/)
DeviceListViewModel.cs (修订版)
// 文件: DMS.WPF/ViewModels/DeviceListViewModel.cs
using CommunityToolkit.Mvvm.Messaging;
using DMS.Application.Interfaces;
using DMS.WPF.Messages;
using DMS.WPF.ViewModels.Items;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace DMS.WPF.ViewModels;
/// <summary>
/// 设备列表视图的ViewModel,现在接收消息以更新设备状态。
/// </summary>
public class DeviceListViewModel : BaseViewModel, IRecipient<DeviceStatusChangedMessage>
{
private readonly IDeviceAppService _deviceAppService;
/// <summary>
/// 绑定到UI的设备集合,类型已变为DeviceItemViewModel。
/// </summary>
public ObservableCollection<DeviceItemViewModel> Devices { get; } = new();
public DeviceListViewModel(IDeviceAppService deviceAppService, IMessenger messenger)
{
_deviceAppService = deviceAppService;
// 注册消息,以便可以接收到它
messenger.Register<DeviceStatusChangedMessage>(this);
}
public override async Task LoadAsync()
{
IsBusy = true;
Devices.Clear();
var deviceDtos = await _deviceAppService.GetAllDevicesAsync();
foreach (var dto in deviceDtos)
{
Devices.Add(new DeviceItemViewModel(dto));
}
IsBusy = false;
}
/// <summary>
/// 实现IRecipient接口,当接收到DeviceStatusChangedMessage消息时此方法被调用。
/// </summary>
public void Receive(DeviceStatusChangedMessage message)
{
var deviceToUpdate = Devices.FirstOrDefault(d => d.Id == message.DeviceId);
if (deviceToUpdate != null)
{
// 直接更新ItemViewModel的属性,UI会自动响应!
deviceToUpdate.Status = message.NewStatus;
}
}
}
3.4. 依赖注入 (App.xaml.cs) (修订版)
// 文件: DMS.WPF/App.xaml.cs
using CommunityToolkit.Mvvm.Messaging;
// ...其他using
public partial class App : System.Windows.Application
{
// ...
private void ConfigureServices(IServiceCollection services)
{
// 消息总线 (关键新增)
// 使用弱引用信使,避免内存泄漏
services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);
// 应用层 & 基础设施层
// ...
// WPF UI 服务
// ...
// ViewModels
services.AddSingleton<MainViewModel>();
services.AddTransient<DashboardViewModel>();
services.AddTransient<DeviceListViewModel>(); // 每次导航都创建新的实例
// Views
services.AddSingleton<MainWindow>();
}
}
3.5. 视图 (Views/)
DeviceListView.xaml 的绑定目标现在是 DeviceItemViewModel 的属性,无需更改绑定路径,但现在它具备了实时更新的能力。
<!-- 文件: DMS.WPF/Views/DeviceListView.xaml -->
<DataGrid ItemsSource="{Binding Devices}" ...>
<DataGrid.Columns>
<DataGridTextColumn Header="名称" Binding="{Binding Name}" />
<!-- 这个绑定现在是响应式的 -->
<DataGridTextColumn Header="状态" Binding="{Binding Status}" />
<!-- ... -->
</DataGrid.Columns>
</DataGrid>