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

6.4 KiB
Raw Blame History

软件开发文档 - 06. 实现步骤与示例

本文档提供关键功能的具体实现步骤和代码示例,以指导开发过程。

1. 添加新设备 (End-to-End)

这是一个从UI到数据库的完整流程示例。

步骤 1: View (DeviceListView.xaml)

用户点击“添加”按钮该按钮绑定到ViewModel的 AddDeviceCommand

<Button Content="添加设备" Command="{Binding AddDeviceCommand}" />

步骤 2: ViewModel (DeviceListViewModel.cs)

AddDeviceCommand 执行,调用对话框服务来显示一个用于输入新设备信息的窗口。

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将其映射为领域模型并调用仓储来保存。

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 将其插入数据库。

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 循环。

// 文件: 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容器获取服务实例并启动它。

// 在 App.xaml.cs OnStartup
var s7Service = _serviceProvider.GetRequiredService<S7BackgroundService>();
s7Service.Start();

3. 菜单导航实现

步骤 1: MainViewModel

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切换它会更新 MainViewModelCurrentViewModel 属性。

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。

<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>