From c173ab08d3c191511a22ece6b1db447a3ec63466 Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Fri, 12 Sep 2025 17:22:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=AE=BE=E5=A4=87=E7=9A=84?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E5=92=8C=E5=81=9C=E7=94=A8=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Events/DeviceConnectChangedEventArgs.cs | 45 ++++++++++ DMS.Application/Interfaces/IEventService.cs | 12 +++ DMS.Application/Services/EventService.cs | 17 ++++ .../Services/OpcUaServiceManager.cs | 13 ++- DMS.WPF/Converters/BooleanToBrushConverter.cs | 85 +++++++++++++++++++ DMS.WPF/Services/DeviceDataService.cs | 37 +++++++- .../ViewModels/Items/DeviceItemViewModel.cs | 5 ++ DMS.WPF/Views/DevicesView.xaml | 18 ++-- 8 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 DMS.Application/Events/DeviceConnectChangedEventArgs.cs create mode 100644 DMS.WPF/Converters/BooleanToBrushConverter.cs diff --git a/DMS.Application/Events/DeviceConnectChangedEventArgs.cs b/DMS.Application/Events/DeviceConnectChangedEventArgs.cs new file mode 100644 index 0000000..8b2e33f --- /dev/null +++ b/DMS.Application/Events/DeviceConnectChangedEventArgs.cs @@ -0,0 +1,45 @@ +namespace DMS.Application.Events; + +public class DeviceConnectChangedEventArgs +{ + /// + /// 设备ID + /// + public int DeviceId { get; } + + /// + /// 设备名称 + /// + public string DeviceName { get; } + + /// + /// 旧状态 + /// + public bool OldStatus { get; } + + /// + /// 新状态 + /// + public bool NewStatus { get; } + + /// + /// 状态改变时间 + /// + public DateTime ChangeTime { get; } + + /// + /// 初始化DeviceStatusChangedEventArgs类的新实例 + /// + /// 设备ID + /// 设备名称 + /// 旧状态 + /// 新状态 + public DeviceConnectChangedEventArgs(int deviceId, string deviceName, bool oldStatus, bool newStatus) + { + DeviceId = deviceId; + DeviceName = deviceName; + OldStatus = oldStatus; + NewStatus = newStatus; + ChangeTime = DateTime.Now; + } +} \ No newline at end of file diff --git a/DMS.Application/Interfaces/IEventService.cs b/DMS.Application/Interfaces/IEventService.cs index ca3ab8f..a4b95f8 100644 --- a/DMS.Application/Interfaces/IEventService.cs +++ b/DMS.Application/Interfaces/IEventService.cs @@ -55,4 +55,16 @@ public interface IEventService void RaiseMqttConnectionChanged(object sender, MqttConnectionChangedEventArgs e); #endregion + + /// + /// 设备运行改变事件 + /// + event EventHandler OnDeviceConnectChanged; + + /// + /// 触发设备状态改变事件 + /// + /// 事件发送者 + /// 设备状态改变事件参数 + void RaiseDeviceConnectChanged(object sender, DeviceConnectChangedEventArgs e); } \ No newline at end of file diff --git a/DMS.Application/Services/EventService.cs b/DMS.Application/Services/EventService.cs index 8d54951..caf85cf 100644 --- a/DMS.Application/Services/EventService.cs +++ b/DMS.Application/Services/EventService.cs @@ -22,6 +22,12 @@ public class EventService : IEventService /// 设备状态改变事件 /// public event EventHandler OnDeviceActiveChanged; + + /// + /// 设备运行改变事件 + /// + public event EventHandler OnDeviceConnectChanged; + /// /// 触发设备状态改变事件 @@ -40,6 +46,17 @@ public class EventService : IEventService } } + /// + /// 触发设备状态改变事件 + /// + /// 事件发送者 + /// 设备状态改变事件参数 + public void RaiseDeviceConnectChanged(object sender, DeviceConnectChangedEventArgs e) + { + OnDeviceConnectChanged?.Invoke(sender, e); + + } + #endregion #region 变量事件 diff --git a/DMS.Infrastructure/Services/OpcUaServiceManager.cs b/DMS.Infrastructure/Services/OpcUaServiceManager.cs index f95b211..4d77a8c 100644 --- a/DMS.Infrastructure/Services/OpcUaServiceManager.cs +++ b/DMS.Infrastructure/Services/OpcUaServiceManager.cs @@ -55,7 +55,7 @@ namespace DMS.Infrastructure.Services } else { - await DisconnectDeviceAsync(e.DeviceId, CancellationToken.None); + await DisconnectDeviceAsync(e.DeviceId, CancellationToken.None); } } @@ -187,11 +187,16 @@ namespace DMS.Infrastructure.Services if (context.OpcUaService.IsConnected) { context.IsConnected = true; + context.Device.IsRunning = true; + _eventService.RaiseDeviceConnectChanged( + this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, true)); await SetupSubscriptionsAsync(context, cancellationToken); _logger.LogInformation("设备 {DeviceName} 连接成功", context.Device.Name); } else { + _eventService.RaiseDeviceConnectChanged( + this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, false)); _logger.LogWarning("设备 {DeviceName} 连接失败", context.Device.Name); } } @@ -200,6 +205,9 @@ namespace DMS.Infrastructure.Services _logger.LogError(ex, "连接设备 {DeviceName} 时发生错误: {ErrorMessage}", context.Device.Name, ex.Message); context.IsConnected = false; + context.Device.IsRunning = false; + _eventService.RaiseDeviceConnectChanged( + this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, false)); } finally { @@ -220,6 +228,9 @@ namespace DMS.Infrastructure.Services _logger.LogInformation("正在断开设备 {DeviceName} 的连接", context.Device.Name); await context.OpcUaService.DisconnectAsync(); context.IsConnected = false; + context.Device.IsRunning = false; + _eventService.RaiseDeviceConnectChanged( + this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, false)); _logger.LogInformation("设备 {DeviceName} 连接已断开", context.Device.Name); } catch (Exception ex) diff --git a/DMS.WPF/Converters/BooleanToBrushConverter.cs b/DMS.WPF/Converters/BooleanToBrushConverter.cs new file mode 100644 index 0000000..df8ea33 --- /dev/null +++ b/DMS.WPF/Converters/BooleanToBrushConverter.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace DMS.WPF.Converters +{ + public class BooleanToBrushConverter : IValueConverter + { + // Predefined "True" color + private static readonly Color DefaultTrueColor = Color.FromArgb(0xFF, 0xA3, 0xE4, 0xD7); // Mint Green + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool boolValue) + { + // If parameter is provided, try to use it as the "True" color + string param = parameter as string; + if (!string.IsNullOrEmpty(param)) + { + // Split the parameter by '|' to see if it contains two colors + string[] colors = param.Split('|'); + + try + { + if (colors.Length == 2) + { + // Two colors: TrueColor|FalseColor + Color trueColor = (Color)ColorConverter.ConvertFromString(colors[0]); + if (colors[1].Equals("Default", StringComparison.OrdinalIgnoreCase)) + { + // For false, return UnsetValue to let the control use its default background + return boolValue ? new SolidColorBrush(trueColor) : + System.Windows.DependencyProperty.UnsetValue; + } + else + { + Color falseColor = (Color)ColorConverter.ConvertFromString(colors[1]); + return boolValue ? new SolidColorBrush(trueColor) : + new SolidColorBrush(falseColor); + } + } + else if (colors.Length == 1) + { + // One color: TrueColor + Color trueColor = (Color)ColorConverter.ConvertFromString(colors[0]); + if (boolValue) + { + return new SolidColorBrush(trueColor); + } + else + { + // For false, return UnsetValue to let the control use its default background + return System.Windows.DependencyProperty.UnsetValue; + } + } + } + catch (FormatException) + { + // If color format is invalid, fall back to default colors + } + } + + // Default behavior + if (boolValue) + { + return new SolidColorBrush(DefaultTrueColor); + } + else + { + // For false, return UnsetValue to let the control use its default background + return System.Windows.DependencyProperty.UnsetValue; + } + } + + // If value is not a boolean, return UnsetValue + return System.Windows.DependencyProperty.UnsetValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/DMS.WPF/Services/DeviceDataService.cs b/DMS.WPF/Services/DeviceDataService.cs index 07d3f53..6bbc825 100644 --- a/DMS.WPF/Services/DeviceDataService.cs +++ b/DMS.WPF/Services/DeviceDataService.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; +using System.Windows.Threading; using AutoMapper; using CommunityToolkit.Mvvm.ComponentModel; using DMS.Application.DTOs; +using DMS.Application.Events; using DMS.Application.Interfaces; using DMS.WPF.Interfaces; using DMS.WPF.ViewModels.Items; @@ -17,22 +19,54 @@ public class DeviceDataService : IDeviceDataService private readonly IAppDataCenterService _appDataCenterService; private readonly IAppDataStorageService _appDataStorageService; private readonly IDataStorageService _dataStorageService; + private readonly IEventService _eventService; + private readonly INotificationService _notificationService; private readonly IMenuDataService _menuDataService; private readonly IVariableDataService _variableDataService; + private readonly Dispatcher _uiDispatcher; /// /// DeviceDataService类的构造函数。 /// /// AutoMapper 实例。 /// 数据服务中心实例。 - public DeviceDataService(IMapper mapper, IAppDataCenterService appDataCenterService,IAppDataStorageService appDataStorageService, IDataStorageService dataStorageService,IMenuDataService menuDataService,IVariableDataService variableDataService) + public DeviceDataService(IMapper mapper, IAppDataCenterService appDataCenterService, + IAppDataStorageService appDataStorageService, IDataStorageService dataStorageService, + IEventService eventService,INotificationService notificationService, + IMenuDataService menuDataService, IVariableDataService variableDataService) { _mapper = mapper; _appDataCenterService = appDataCenterService; _appDataStorageService = appDataStorageService; _dataStorageService = dataStorageService; + _eventService = eventService; + _notificationService = notificationService; _menuDataService = menuDataService; _variableDataService = variableDataService; + _uiDispatcher = Dispatcher.CurrentDispatcher; + + _eventService.OnDeviceConnectChanged += OnDeviceConnectChanged; + } + + private void OnDeviceConnectChanged(object? sender, DeviceConnectChangedEventArgs e) + { + _uiDispatcher.Invoke(() => + { + var device = _dataStorageService.Devices.FirstOrDefault(d => d.Id == e.DeviceId); + if (device != null) + { + + device.IsRunning = e.NewStatus; + if (device.IsRunning) + { + _notificationService.ShowSuccess($"设备:{device.Name},连接成功。"); + } + else + { + _notificationService.ShowSuccess($"设备:{device.Name},已断开连接。"); + } + } + }); } /// @@ -44,7 +78,6 @@ public class DeviceDataService : IDeviceDataService { _dataStorageService.Devices.Add(_mapper.Map(deviceDto)); } - } /// diff --git a/DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs b/DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs index 9feee62..c8557f2 100644 --- a/DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs +++ b/DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs @@ -78,6 +78,11 @@ public partial class DeviceItemViewModel : ObservableObject } } + partial void OnIsRunningChanged(bool oldValue, bool newValue) + { + System.Console.WriteLine($"IsRunning changed from {oldValue} to {newValue} for device {Name}"); + } + /// /// 当IsActive属性改变时调用,用于发布设备状态改变事件 /// diff --git a/DMS.WPF/Views/DevicesView.xaml b/DMS.WPF/Views/DevicesView.xaml index 5666cbf..6a42db2 100644 --- a/DMS.WPF/Views/DevicesView.xaml +++ b/DMS.WPF/Views/DevicesView.xaml @@ -4,23 +4,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:hc="https://handyorg.github.io/handycontrol" - xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:vm="clr-namespace:DMS.WPF.ViewModels" + xmlns:localConverters="clr-namespace:DMS.WPF.Converters" d:DataContext="{d:DesignInstance vm:DevicesViewModel}" d:DesignHeight="300" d:DesignWidth="300" mc:Ignorable="d"> + + CornerRadius="8" + Background="{Binding IsRunning, Converter={StaticResource BooleanToBrushConverter}, ConverterParameter='#FFA8E063|{DynamicResource SystemControlBackgroundAltHighBrush}'}"> - - - + @@ -59,6 +52,7 @@ FontSize="20" FontWeight="SemiBold" Text="{Binding Name}" /> +