完成设备的启用和停用并更新界面

This commit is contained in:
2025-09-12 17:22:15 +08:00
parent d20b35185f
commit c173ab08d3
8 changed files with 217 additions and 15 deletions

View File

@@ -0,0 +1,45 @@
namespace DMS.Application.Events;
public class DeviceConnectChangedEventArgs
{
/// <summary>
/// 设备ID
/// </summary>
public int DeviceId { get; }
/// <summary>
/// 设备名称
/// </summary>
public string DeviceName { get; }
/// <summary>
/// 旧状态
/// </summary>
public bool OldStatus { get; }
/// <summary>
/// 新状态
/// </summary>
public bool NewStatus { get; }
/// <summary>
/// 状态改变时间
/// </summary>
public DateTime ChangeTime { get; }
/// <summary>
/// 初始化DeviceStatusChangedEventArgs类的新实例
/// </summary>
/// <param name="deviceId">设备ID</param>
/// <param name="deviceName">设备名称</param>
/// <param name="oldStatus">旧状态</param>
/// <param name="newStatus">新状态</param>
public DeviceConnectChangedEventArgs(int deviceId, string deviceName, bool oldStatus, bool newStatus)
{
DeviceId = deviceId;
DeviceName = deviceName;
OldStatus = oldStatus;
NewStatus = newStatus;
ChangeTime = DateTime.Now;
}
}

View File

@@ -55,4 +55,16 @@ public interface IEventService
void RaiseMqttConnectionChanged(object sender, MqttConnectionChangedEventArgs e); void RaiseMqttConnectionChanged(object sender, MqttConnectionChangedEventArgs e);
#endregion #endregion
/// <summary>
/// 设备运行改变事件
/// </summary>
event EventHandler<DeviceConnectChangedEventArgs> OnDeviceConnectChanged;
/// <summary>
/// 触发设备状态改变事件
/// </summary>
/// <param name="sender">事件发送者</param>
/// <param name="e">设备状态改变事件参数</param>
void RaiseDeviceConnectChanged(object sender, DeviceConnectChangedEventArgs e);
} }

View File

@@ -23,6 +23,12 @@ public class EventService : IEventService
/// </summary> /// </summary>
public event EventHandler<DeviceActiveChangedEventArgs> OnDeviceActiveChanged; public event EventHandler<DeviceActiveChangedEventArgs> OnDeviceActiveChanged;
/// <summary>
/// 设备运行改变事件
/// </summary>
public event EventHandler<DeviceConnectChangedEventArgs> OnDeviceConnectChanged;
/// <summary> /// <summary>
/// 触发设备状态改变事件 /// 触发设备状态改变事件
/// </summary> /// </summary>
@@ -40,6 +46,17 @@ public class EventService : IEventService
} }
} }
/// <summary>
/// 触发设备状态改变事件
/// </summary>
/// <param name="sender">事件发送者</param>
/// <param name="e">设备状态改变事件参数</param>
public void RaiseDeviceConnectChanged(object sender, DeviceConnectChangedEventArgs e)
{
OnDeviceConnectChanged?.Invoke(sender, e);
}
#endregion #endregion
#region #region

View File

@@ -187,11 +187,16 @@ namespace DMS.Infrastructure.Services
if (context.OpcUaService.IsConnected) if (context.OpcUaService.IsConnected)
{ {
context.IsConnected = true; context.IsConnected = true;
context.Device.IsRunning = true;
_eventService.RaiseDeviceConnectChanged(
this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, true));
await SetupSubscriptionsAsync(context, cancellationToken); await SetupSubscriptionsAsync(context, cancellationToken);
_logger.LogInformation("设备 {DeviceName} 连接成功", context.Device.Name); _logger.LogInformation("设备 {DeviceName} 连接成功", context.Device.Name);
} }
else else
{ {
_eventService.RaiseDeviceConnectChanged(
this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, false));
_logger.LogWarning("设备 {DeviceName} 连接失败", context.Device.Name); _logger.LogWarning("设备 {DeviceName} 连接失败", context.Device.Name);
} }
} }
@@ -200,6 +205,9 @@ namespace DMS.Infrastructure.Services
_logger.LogError(ex, "连接设备 {DeviceName} 时发生错误: {ErrorMessage}", _logger.LogError(ex, "连接设备 {DeviceName} 时发生错误: {ErrorMessage}",
context.Device.Name, ex.Message); context.Device.Name, ex.Message);
context.IsConnected = false; context.IsConnected = false;
context.Device.IsRunning = false;
_eventService.RaiseDeviceConnectChanged(
this, new DeviceConnectChangedEventArgs(context.Device.Id, context.Device.Name, false, false));
} }
finally finally
{ {
@@ -220,6 +228,9 @@ namespace DMS.Infrastructure.Services
_logger.LogInformation("正在断开设备 {DeviceName} 的连接", context.Device.Name); _logger.LogInformation("正在断开设备 {DeviceName} 的连接", context.Device.Name);
await context.OpcUaService.DisconnectAsync(); await context.OpcUaService.DisconnectAsync();
context.IsConnected = false; 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); _logger.LogInformation("设备 {DeviceName} 连接已断开", context.Device.Name);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -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();
}
}
}

View File

@@ -1,7 +1,9 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Windows.Threading;
using AutoMapper; using AutoMapper;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using DMS.Application.DTOs; using DMS.Application.DTOs;
using DMS.Application.Events;
using DMS.Application.Interfaces; using DMS.Application.Interfaces;
using DMS.WPF.Interfaces; using DMS.WPF.Interfaces;
using DMS.WPF.ViewModels.Items; using DMS.WPF.ViewModels.Items;
@@ -17,22 +19,54 @@ public class DeviceDataService : IDeviceDataService
private readonly IAppDataCenterService _appDataCenterService; private readonly IAppDataCenterService _appDataCenterService;
private readonly IAppDataStorageService _appDataStorageService; private readonly IAppDataStorageService _appDataStorageService;
private readonly IDataStorageService _dataStorageService; private readonly IDataStorageService _dataStorageService;
private readonly IEventService _eventService;
private readonly INotificationService _notificationService;
private readonly IMenuDataService _menuDataService; private readonly IMenuDataService _menuDataService;
private readonly IVariableDataService _variableDataService; private readonly IVariableDataService _variableDataService;
private readonly Dispatcher _uiDispatcher;
/// <summary> /// <summary>
/// DeviceDataService类的构造函数。 /// DeviceDataService类的构造函数。
/// </summary> /// </summary>
/// <param name="mapper">AutoMapper 实例。</param> /// <param name="mapper">AutoMapper 实例。</param>
/// <param name="appDataCenterService">数据服务中心实例。</param> /// <param name="appDataCenterService">数据服务中心实例。</param>
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; _mapper = mapper;
_appDataCenterService = appDataCenterService; _appDataCenterService = appDataCenterService;
_appDataStorageService = appDataStorageService; _appDataStorageService = appDataStorageService;
_dataStorageService = dataStorageService; _dataStorageService = dataStorageService;
_eventService = eventService;
_notificationService = notificationService;
_menuDataService = menuDataService; _menuDataService = menuDataService;
_variableDataService = variableDataService; _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},已断开连接。");
}
}
});
} }
/// <summary> /// <summary>
@@ -44,7 +78,6 @@ public class DeviceDataService : IDeviceDataService
{ {
_dataStorageService.Devices.Add(_mapper.Map<DeviceItemViewModel>(deviceDto)); _dataStorageService.Devices.Add(_mapper.Map<DeviceItemViewModel>(deviceDto));
} }
} }
/// <summary> /// <summary>

View File

@@ -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}");
}
/// <summary> /// <summary>
/// 当IsActive属性改变时调用用于发布设备状态改变事件 /// 当IsActive属性改变时调用用于发布设备状态改变事件
/// </summary> /// </summary>

View File

@@ -4,23 +4,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:vm="clr-namespace:DMS.WPF.ViewModels" xmlns:vm="clr-namespace:DMS.WPF.ViewModels"
xmlns:localConverters="clr-namespace:DMS.WPF.Converters"
d:DataContext="{d:DesignInstance vm:DevicesViewModel}" d:DataContext="{d:DesignInstance vm:DevicesViewModel}"
d:DesignHeight="300" d:DesignHeight="300"
d:DesignWidth="300" d:DesignWidth="300"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<localConverters:BooleanToBrushConverter x:Key="BooleanToBrushConverter" />
<DataTemplate x:Key="DeviceItemTemplate"> <DataTemplate x:Key="DeviceItemTemplate">
<Border <Border
Margin="5" Margin="5"
Padding="15" Padding="15"
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8"> CornerRadius="8"
Background="{Binding IsRunning, Converter={StaticResource BooleanToBrushConverter}, ConverterParameter='#FFA8E063|{DynamicResource SystemControlBackgroundAltHighBrush}'}">
<Border.Effect> <Border.Effect>
<DropShadowEffect <DropShadowEffect
BlurRadius="5" BlurRadius="5"
@@ -28,16 +29,8 @@
ShadowDepth="1" ShadowDepth="1"
Color="Black" /> Color="Black" />
</Border.Effect> </Border.Effect>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsRunning}" Value="True">
<Setter Property="Background" Value="Aquamarine" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid> <Grid>
@@ -59,6 +52,7 @@
FontSize="20" FontSize="20"
FontWeight="SemiBold" FontWeight="SemiBold"
Text="{Binding Name}" /> Text="{Binding Name}" />
</DockPanel> </DockPanel>
<!-- Row 1: Details with Icons --> <!-- Row 1: Details with Icons -->