# 软件开发文档 - 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`
```csharp
// 文件: DMS.WPF/Messages/DeviceStatusChangedMessage.cs
namespace DMS.WPF.Messages;
///
/// 当设备状态在后台发生变化时,通过IMessenger广播此消息。
///
public class DeviceStatusChangedMessage
{
///
/// 状态发生变化的设备ID。
///
public int DeviceId { get; }
///
/// 设备的新状态文本 (例如: "在线", "离线", "错误")。
///
public string NewStatus { get; }
public DeviceStatusChangedMessage(int deviceId, string newStatus)
{
DeviceId = deviceId;
NewStatus = newStatus;
}
}
```
### 3.2. ItemViewModel (`ViewModels/Items/`)
#### `DeviceItemViewModel.cs`
```csharp
// 文件: DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using DMS.Application.DTOs;
namespace DMS.WPF.ViewModels.Items;
///
/// 代表设备列表中的单个设备项的ViewModel。
/// 实现了INotifyPropertyChanged,其任何属性变化都会自动通知UI。
///
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` (修订版)
```csharp
// 文件: 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;
///
/// 设备列表视图的ViewModel,现在接收消息以更新设备状态。
///
public class DeviceListViewModel : BaseViewModel, IRecipient
{
private readonly IDeviceAppService _deviceAppService;
///
/// 绑定到UI的设备集合,类型已变为DeviceItemViewModel。
///
public ObservableCollection Devices { get; } = new();
public DeviceListViewModel(IDeviceAppService deviceAppService, IMessenger messenger)
{
_deviceAppService = deviceAppService;
// 注册消息,以便可以接收到它
messenger.Register(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;
}
///
/// 实现IRecipient接口,当接收到DeviceStatusChangedMessage消息时此方法被调用。
///
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`) (修订版)
```csharp
// 文件: DMS.WPF/App.xaml.cs
using CommunityToolkit.Mvvm.Messaging;
// ...其他using
public partial class App : System.Windows.Application
{
// ...
private void ConfigureServices(IServiceCollection services)
{
// 消息总线 (关键新增)
// 使用弱引用信使,避免内存泄漏
services.AddSingleton(WeakReferenceMessenger.Default);
// 应用层 & 基础设施层
// ...
// WPF UI 服务
// ...
// ViewModels
services.AddSingleton();
services.AddTransient();
services.AddTransient(); // 每次导航都创建新的实例
// Views
services.AddSingleton();
}
}
```
### 3.5. 视图 (`Views/`)
`DeviceListView.xaml` 的绑定目标现在是 `DeviceItemViewModel` 的属性,无需更改绑定路径,但现在它具备了实时更新的能力。
```xml
```