Files
DMS/软件设计文档/原始文档/06-实现步骤与示例.md

217 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 软件开发文档 - 06. 实现步骤与示例
本文档提供关键功能的具体实现步骤和代码示例,以指导开发过程。
## 1. 添加新设备 (End-to-End)
这是一个从UI到数据库的完整流程示例。
**步骤 1: View (`DeviceListView.xaml`)**
用户点击“添加”按钮该按钮绑定到ViewModel的 `AddDeviceCommand`
```xml
<Button Content="添加设备" Command="{Binding AddDeviceCommand}" />
```
**步骤 2: ViewModel (`DeviceListViewModel.cs`)**
`AddDeviceCommand` 执行,调用对话框服务来显示一个用于输入新设备信息的窗口。
```csharp
public ICommand AddDeviceCommand { get; }
public DeviceListViewModel(IDeviceAppService deviceAppService, IDialogService dialogService)
{
_deviceAppService = deviceAppService;
_dialogService = dialogService;
AddDeviceCommand = new RelayCommand(async () => await ExecuteAddDevice());
}
private async Task ExecuteAddDevice()
{
var vm = new AddDeviceDialogViewModel(); // 一个专门用于对话框的ViewModel
var result = await _dialogService.ShowDialogAsync(vm);
if (result != null) // 假设对话框返回创建的DTO
{
await _deviceAppService.CreateDeviceAsync(result);
await LoadDevicesAsync(); // 刷新列表
}
}
```
**步骤 3: Application Service (`DeviceAppService.cs`)**
应用服务接收DTO将其映射为领域模型并调用仓储来保存。
```csharp
public async Task CreateDeviceAsync(CreateDeviceDto deviceDto)
{
// 参数校验
if (string.IsNullOrWhiteSpace(deviceDto.Name) || string.IsNullOrWhiteSpace(deviceDto.IpAddress))
{
throw new ArgumentException("设备名称和IP地址不能为空");
}
var device = _mapper.Map<Device>(deviceDto);
device.IsActive = true; // 设置默认值
await _deviceRepository.AddAsync(device);
}
```
**步骤 4: Repository (`DeviceRepository.cs`)**
仓储将领域模型映射为数据库实体,并使用 `SqlSugar` 将其插入数据库。
```csharp
public async Task AddAsync(Device entity)
{
var dbEntity = _mapper.Map<DbDevice>(entity);
await _db.Insertable(dbEntity).ExecuteCommandAsync();
}
```
## 2. S7数据采集后台服务
后台服务将轮询所有激活的S7设备读取变量值并启动数据处理链。
**步骤 1: 创建后台服务 (`S7BackgroundService.cs`)**
这个服务应该是一个长时间运行的服务,可以使用 `IHostedService` (如果项目是.NET Core) 或一个简单的 `Task.Run` 循环。
```csharp
// 文件: DMS.WPF/Services/S7BackgroundService.cs
public class S7BackgroundService : IDisposable
{
private readonly IDeviceRepository _deviceRepo;
private readonly IS7CommunicationService _s7Service;
private readonly DataAcquisitionService _acquisitionService; // 包含处理链逻辑的服务
private CancellationTokenSource _cts;
public S7BackgroundService(IDeviceRepository deviceRepo, IS7CommunicationService s7Service, DataAcquisitionService acquisitionService)
{
// ... 注入依赖
}
public void Start()
{
_cts = new CancellationTokenSource();
Task.Run(async () => await ExecuteAsync(_cts.Token));
}
protected async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var activeDevices = await _deviceRepo.GetActiveDevicesWithDetailsAsync();
foreach (var device in activeDevices.Where(d => d.Protocol == ProtocolType.S7))
{
await _s7Service.ConnectAsync(device); // 确保连接
foreach (var table in device.VariableTables.Where(t => t.IsActive))
{
foreach (var variable in table.Variables.Where(v => v.IsActive))
{
try
{
var value = await _s7Service.ReadVariableAsync(device, variable);
// 触发数据处理链
await _acquisitionService.OnDataReceived(variable, value);
}
catch (Exception ex)
{
// Log error
}
}
}
}
await Task.Delay(1000, stoppingToken); // 轮询间隔
}
}
public void Stop()
{
_cts?.Cancel();
}
}
```
**步骤 2: 在 `App.xaml.cs` 中启动服务**
在应用程序启动时从DI容器获取服务实例并启动它。
```csharp
// 在 App.xaml.cs OnStartup
var s7Service = _serviceProvider.GetRequiredService<S7BackgroundService>();
s7Service.Start();
```
## 3. 菜单导航实现
**步骤 1: `MainViewModel`**
```csharp
public class MainViewModel : BaseViewModel
{
private readonly INavigationService _navigationService;
public ICommand NavigateCommand { get; }
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateCommand = new RelayCommand<Type>(OnNavigate);
}
private void OnNavigate(Type viewModelType)
{
_navigationService.NavigateTo(viewModelType);
}
}
```
**步骤 2: `NavigationService`**
这个服务负责实际的ViewModel切换它会更新 `MainViewModel``CurrentViewModel` 属性。
```csharp
public class NavigationService : INavigationService
{
private readonly IServiceProvider _serviceProvider;
private readonly MainViewModel _mainViewModel; // 持有MainViewModel的引用
public NavigationService(IServiceProvider serviceProvider, MainViewModel mainViewModel)
{
_serviceProvider = serviceProvider;
_mainViewModel = mainViewModel;
}
public void NavigateTo(Type viewModelType)
{
var viewModel = (BaseViewModel)_serviceProvider.GetRequiredService(viewModelType);
_mainViewModel.CurrentViewModel = viewModel;
}
}
```
**步骤 3: View (`MainWindow.xaml`)**
`ContentControl` 绑定到 `CurrentViewModel`,并使用 `DataTemplate` 来根据ViewModel的类型选择对应的View。
```xml
<ContentControl Content="{Binding CurrentViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
<views:DashboardView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DeviceListViewModel}">
<views:DeviceListView />
</DataTemplate>
<!-- 其他模板 -->
</ContentControl.Resources>
</ContentControl>
```