监控配置保存

This commit is contained in:
hsc
2026-07-09 11:34:03 +08:00
parent f5145d10e4
commit b21c1901e1
9 changed files with 146 additions and 99 deletions

View File

@@ -13,51 +13,7 @@
<UserControl.Resources> <UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/> <converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- 普通操作按钮 -->
<Style x:Key="CmdBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="12,0"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
</Style>
<!-- 危险/警告操作按钮(橙色/红色) -->
<Style x:Key="WarnBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="12,0"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
<Setter Property="Background" Value="#EF6C00"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<!-- 数字输入框 -->
<Style x:Key="NumInput" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="32"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
</Style>
<!-- 测量值只读显示 -->
<Style x:Key="MeasureBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
<Setter Property="Width" Value="110"/>
<Setter Property="Height" Value="32"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
<Setter Property="Background" Value="#F5F5F5"/>
</Style>
<!-- 标签 -->
<Style x:Key="ParamLabel" TargetType="TextBlock">
<Setter Property="Width" Value="80"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="0,0,4,0"/>
</Style>
</UserControl.Resources> </UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">

View File

@@ -12,47 +12,6 @@
<UserControl.Resources> <UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/> <converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style x:Key="CmdBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="12,0"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
</Style>
<Style x:Key="WarnBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="12,0"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
<Setter Property="Background" Value="#EF6C00"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style x:Key="NumInput" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="32"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
</Style>
<Style x:Key="MeasureBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
<Setter Property="Width" Value="110"/>
<Setter Property="Height" Value="32"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="4,0"/>
<Setter Property="Background" Value="#F5F5F5"/>
</Style>
<Style x:Key="ParamLabel" TargetType="TextBlock">
<Setter Property="Width" Value="80"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="0,0,4,0"/>
</Style>
</UserControl.Resources> </UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">

View File

@@ -1,6 +1,7 @@
using Common.Attributes; using Common.Attributes;
using Model.Entity; using Model.Entity;
using Model.Models; using Model.Models;
using NLog;
using OxyPlot; using OxyPlot;
using OxyPlot.Axes; using OxyPlot.Axes;
using OxyPlot.Legends; using OxyPlot.Legends;
@@ -19,6 +20,7 @@ using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
using UIShare.GlobalVariable; using UIShare.GlobalVariable;
using UIShare.PubEvent; using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase; using UIShare.ViewModelBase;
@@ -42,13 +44,25 @@ namespace MonitorModule.ViewModels
public PlotModel Plot { get; } public PlotModel Plot { get; }
/// <summary>已添加的监测通道列表</summary> /// <summary>已添加的监测通道列表</summary>
public ObservableCollection<MonitorChannel> Channels { get; } = new(); public ObservableCollection<MonitorChannelVM> Channels
{
get => _systemConfig?.Channels ?? new ObservableCollection<MonitorChannelVM>();
set
{
if (_systemConfig != null && _systemConfig.Channels != value)
{
_systemConfig.Channels = value;
RaisePropertyChanged();
}
}
}
/// <summary>可添加的设备方法列表(供用户选择)</summary> /// <summary>可添加的设备方法列表(供用户选择)</summary>
public ObservableCollection<AvailableMethodItem> AvailableMethods { get; } = new(); public ObservableCollection<AvailableMethodItem> AvailableMethods { get; } = new();
private MonitorChannel? _selectedChannel; private MonitorChannelVM? _selectedChannel;
public MonitorChannel? SelectedChannel public MonitorChannelVM? SelectedChannel
{ {
get => _selectedChannel; get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value); set => SetProperty(ref _selectedChannel, value);
@@ -75,12 +89,14 @@ namespace MonitorModule.ViewModels
public ICommand ResetViewCommand { get; } public ICommand ResetViewCommand { get; }
public ICommand RefreshDataCommand { get; } public ICommand RefreshDataCommand { get; }
public ICommand RefreshCommand { get; } public ICommand RefreshCommand { get; }
public ICommand SaveCommand { get; }
#endregion #endregion
#region #region
private bool IsInitiated = false; private bool IsInitiated = false;
private IScopedProvider? _scope; private IScopedProvider? _scope;
private DeviceManager? _deviceManager; private DeviceManager? _deviceManager;
private SystemConfig? _systemConfig;
private CancellationTokenSource? _monitorCTS = new(); private CancellationTokenSource? _monitorCTS = new();
// 颜色调色盘 // 颜色调色盘
@@ -125,6 +141,7 @@ namespace MonitorModule.ViewModels
ResetViewCommand = new DelegateCommand(OnResetView); ResetViewCommand = new DelegateCommand(OnResetView);
RefreshDataCommand = new DelegateCommand(OnRefreshData); RefreshDataCommand = new DelegateCommand(OnRefreshData);
RefreshCommand = new DelegateCommand(OnExpand); RefreshCommand = new DelegateCommand(OnExpand);
SaveCommand = new DelegateCommand(OnSave);
// OxyPlot 刷新定时器1000ms只负责视觉更新 // OxyPlot 刷新定时器1000ms只负责视觉更新
_plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background) _plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background)
@@ -134,6 +151,8 @@ namespace MonitorModule.ViewModels
_plotRefreshTimer.Tick += OnPlotRefreshTick; _plotRefreshTimer.Tick += OnPlotRefreshTick;
} }
public void Dispose() public void Dispose()
{ {
_monitorCTS?.Cancel(); _monitorCTS?.Cancel();
@@ -159,7 +178,21 @@ namespace MonitorModule.ViewModels
Channels.Clear(); Channels.Clear();
AvailableMethods.Clear(); AvailableMethods.Clear();
} }
private void OnSave()
{
// 将当前 Channels 序列化为 MonitorChannelConfig 列表保存到 MonitorChannels
if (_systemConfig != null)
{
_systemConfig.MonitorChannels = new ObservableCollection<MonitorChannelConfig>(Channels
.Select(c => new MonitorChannelConfig
{
Fingerprint = c.Fingerprint,
MethodName = c.MethodName,
IsDisplayed = c.IsDisplayed
}));
}
ConfigService.Save(_systemConfig);
}
#region 广 #region 广
/// <summary> /// <summary>
/// 由 HardwareDataBroadcaster 通过 EventAggregator 广播触发。 /// 由 HardwareDataBroadcaster 通过 EventAggregator 广播触发。
@@ -275,6 +308,8 @@ namespace MonitorModule.ViewModels
_scope = _globalInfo.ScopeDic[TestStatus]; _scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve<ScopedContext>(); _scopedContext = _scope.Resolve<ScopedContext>();
_deviceManager = _scope.Resolve<DeviceManager>(); _deviceManager = _scope.Resolve<DeviceManager>();
_systemConfig = _scope.Resolve<SystemConfig>();
RaisePropertyChanged(nameof(Channels));
_broadcaster = _scope.Resolve<HardwareDataBroadcaster>(); _broadcaster = _scope.Resolve<HardwareDataBroadcaster>();
IsInitiated = true; IsInitiated = true;
@@ -286,15 +321,18 @@ namespace MonitorModule.ViewModels
// 2. 扫描可监测方法(供 UI 选择) // 2. 扫描可监测方法(供 UI 选择)
DiscoverAvailableMethods(); DiscoverAvailableMethods();
// 3. 启动广播器Discover + Start // 3. 从配置自动恢复已保存的监测通道
RestoreChannelsFromConfig();
// 4. 启动广播器Discover + Start
_broadcaster.Discover(); _broadcaster.Discover();
_broadcaster.Start(); _broadcaster.Start();
// 4. 订阅 HardwareDataReportedEventUI 线程回调) // 5. 订阅 HardwareDataReportedEventUI 线程回调)
_subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>() _subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread); .Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
// 5. 启动 OxyPlot 视觉刷新定时器 // 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start(); _stopwatch.Start();
_plotRefreshTimer.Start(); _plotRefreshTimer.Start();
} }
@@ -404,6 +442,72 @@ namespace MonitorModule.ViewModels
? $"发现 {AvailableMethods.Count} 个可监测项" ? $"发现 {AvailableMethods.Count} 个可监测项"
: "未发现可监测的设备方法"; : "未发现可监测的设备方法";
} }
/// <summary>
/// 从 SystemConfig.MonitorChannels 持久化列表自动还原监测通道。
/// </summary>
private void RestoreChannelsFromConfig()
{
if (_systemConfig?.MonitorChannels == null || _systemConfig.MonitorChannels.Count == 0) return;
if (AvailableMethods.Count == 0) return;
int restored = 0;
foreach (var config in _systemConfig.MonitorChannels)
{
if (string.IsNullOrWhiteSpace(config.Fingerprint) ||
string.IsNullOrWhiteSpace(config.MethodName)) continue;
// 已在 Channels 中存在则跳过
if (Channels.Any(c => c.Fingerprint == config.Fingerprint && c.MethodName == config.MethodName))
continue;
// 在 AvailableMethods 中查找匹配项
var method = AvailableMethods.FirstOrDefault(m =>
m.Fingerprint == config.Fingerprint && m.MethodName == config.MethodName);
if (method == null) continue;
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
var channel = new MonitorChannelVM
{
DeviceName = method.DeviceName,
Fingerprint = method.Fingerprint,
MethodName = method.MethodName,
DisplayName = method.DisplayName,
Color = color,
IsMonitored = true,
IsDisplayed = config.IsDisplayed
};
// 仅当 IsDisplayed=true 时才创建 LineSeries
if (channel.IsDisplayed)
{
channel.Series = new LineSeries
{
Title = channel.DisplayName,
Color = color,
StrokeThickness = 1.5
};
Plot.Series.Add(channel.Series);
}
channel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
OnChannelDisplayChanged(channel);
};
Channels.Add(channel);
restored++;
}
if (restored > 0)
{
Plot.InvalidatePlot(true);
StatusMessage = $"已从配置自动恢复 {restored} 个监测通道";
}
}
#endregion #endregion
#region #region
@@ -426,7 +530,7 @@ namespace MonitorModule.ViewModels
var color = _palette[_colorIndex % _palette.Length]; var color = _palette[_colorIndex % _palette.Length];
_colorIndex++; _colorIndex++;
var channel = new MonitorChannel var channel = new MonitorChannelVM
{ {
DeviceName = method.DeviceName, DeviceName = method.DeviceName,
Fingerprint = method.Fingerprint, Fingerprint = method.Fingerprint,
@@ -447,7 +551,7 @@ namespace MonitorModule.ViewModels
channel.PropertyChanged += (s, e) => channel.PropertyChanged += (s, e) =>
{ {
if (e.PropertyName == nameof(MonitorChannel.IsDisplayed)) if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
OnChannelDisplayChanged(channel); OnChannelDisplayChanged(channel);
}; };
@@ -497,7 +601,7 @@ namespace MonitorModule.ViewModels
#endregion #endregion
#region / #region /
public void OnChannelDisplayChanged(MonitorChannel channel) public void OnChannelDisplayChanged(MonitorChannelVM channel)
{ {
if (channel.IsDisplayed) if (channel.IsDisplayed)
{ {
@@ -530,7 +634,7 @@ namespace MonitorModule.ViewModels
} }
} }
public void OnChannelMathChanged(MonitorChannel channel) public void OnChannelMathChanged(MonitorChannelVM channel)
{ {
if (channel.Series == null) return; if (channel.Series == null) return;
var points = channel.DataPoints.ToArray(); var points = channel.DataPoints.ToArray();

View File

@@ -89,6 +89,10 @@
Command="{Binding RefreshDataCommand}" Command="{Binding RefreshDataCommand}"
Padding="12,4" Margin="6,0,0,0" Padding="12,4" Margin="6,0,0,0"
ToolTip="重新绘制图表"/> ToolTip="重新绘制图表"/>
<Button Content=" 保存"
Command="{Binding SaveCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="保存到配置文件"/>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@@ -33,6 +33,12 @@ namespace UIShare.GlobalVariable
public ObservableCollection<CANSignalConfig> ConfigurationList = new(); public ObservableCollection<CANSignalConfig> ConfigurationList = new();
public ObservableCollection<AutoDBCLoadItem> dbcAutoLoadList = new(); public ObservableCollection<AutoDBCLoadItem> dbcAutoLoadList = new();
public ObservableCollection<ValueLimitVM> ValueLimitList = new(); public ObservableCollection<ValueLimitVM> ValueLimitList = new();
[JsonIgnore]
public ObservableCollection<MonitorChannelVM> Channels = new();
/// <summary>
/// 监测通道持久化配置列表(包含 Fingerprint、MethodName、IsDisplayed
/// </summary>
public ObservableCollection<MonitorChannelConfig> MonitorChannels = new();
public ZLGCANFD CANFD = new(); public ZLGCANFD CANFD = new();
[JsonIgnore] [JsonIgnore]
public ObservableCollection<ParameterVM> ParameterList = new() public ObservableCollection<ParameterVM> ParameterList = new()

View File

@@ -15,6 +15,7 @@
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" /> <PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" /> <PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" /> <PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" /> <ProjectReference Include="..\Common\Common.csproj" />

View File

@@ -4,7 +4,7 @@ using System.Collections.Concurrent;
using Prism.Mvvm; using Prism.Mvvm;
using NCalc; using NCalc;
namespace MonitorModule.ViewModels namespace UIShare.UIViewModel
{ {
/// <summary> /// <summary>
/// 监测通道:一个设备方法对应一个通道。 /// 监测通道:一个设备方法对应一个通道。
@@ -12,7 +12,7 @@ namespace MonitorModule.ViewModels
/// - IsMonitored=true 时始终记录 DataPoints /// - IsMonitored=true 时始终记录 DataPoints
/// - IsDisplayed=true 时才创建 LineSeries 并绘制 /// - IsDisplayed=true 时才创建 LineSeries 并绘制
/// </summary> /// </summary>
public class MonitorChannel : BindableBase public class MonitorChannelVM : BindableBase
{ {
// ===== 标识 ===== // ===== 标识 =====
public string DeviceName { get; init; } = string.Empty; public string DeviceName { get; init; } = string.Empty;

View File

@@ -0,0 +1,17 @@
namespace UIShare.UIViewModel
{
/// <summary>
/// 监测通道持久化配置(用于 JSON 保存/恢复)。
/// </summary>
public class MonitorChannelConfig
{
/// <summary>硬件指纹(物理设备唯一标识)</summary>
public string Fingerprint { get; set; } = string.Empty;
/// <summary>监测方法名</summary>
public string MethodName { get; set; } = string.Empty;
/// <summary>是否在图表上显示该通道的线图</summary>
public bool IsDisplayed { get; set; } = true;
}
}