监控配置保存

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>
<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>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">

View File

@@ -12,47 +12,6 @@
<UserControl.Resources>
<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>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">

View File

@@ -1,6 +1,7 @@
using Common.Attributes;
using Model.Entity;
using Model.Models;
using NLog;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
@@ -19,6 +20,7 @@ using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
@@ -42,13 +44,25 @@ namespace MonitorModule.ViewModels
public PlotModel Plot { get; }
/// <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>
public ObservableCollection<AvailableMethodItem> AvailableMethods { get; } = new();
private MonitorChannel? _selectedChannel;
public MonitorChannel? SelectedChannel
private MonitorChannelVM? _selectedChannel;
public MonitorChannelVM? SelectedChannel
{
get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value);
@@ -75,12 +89,14 @@ namespace MonitorModule.ViewModels
public ICommand ResetViewCommand { get; }
public ICommand RefreshDataCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SaveCommand { get; }
#endregion
#region
private bool IsInitiated = false;
private IScopedProvider? _scope;
private DeviceManager? _deviceManager;
private SystemConfig? _systemConfig;
private CancellationTokenSource? _monitorCTS = new();
// 颜色调色盘
@@ -125,6 +141,7 @@ namespace MonitorModule.ViewModels
ResetViewCommand = new DelegateCommand(OnResetView);
RefreshDataCommand = new DelegateCommand(OnRefreshData);
RefreshCommand = new DelegateCommand(OnExpand);
SaveCommand = new DelegateCommand(OnSave);
// OxyPlot 刷新定时器1000ms只负责视觉更新
_plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background)
@@ -134,6 +151,8 @@ namespace MonitorModule.ViewModels
_plotRefreshTimer.Tick += OnPlotRefreshTick;
}
public void Dispose()
{
_monitorCTS?.Cancel();
@@ -159,7 +178,21 @@ namespace MonitorModule.ViewModels
Channels.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 广
/// <summary>
/// 由 HardwareDataBroadcaster 通过 EventAggregator 广播触发。
@@ -275,6 +308,8 @@ namespace MonitorModule.ViewModels
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve<ScopedContext>();
_deviceManager = _scope.Resolve<DeviceManager>();
_systemConfig = _scope.Resolve<SystemConfig>();
RaisePropertyChanged(nameof(Channels));
_broadcaster = _scope.Resolve<HardwareDataBroadcaster>();
IsInitiated = true;
@@ -286,15 +321,18 @@ namespace MonitorModule.ViewModels
// 2. 扫描可监测方法(供 UI 选择)
DiscoverAvailableMethods();
// 3. 启动广播器Discover + Start
// 3. 从配置自动恢复已保存的监测通道
RestoreChannelsFromConfig();
// 4. 启动广播器Discover + Start
_broadcaster.Discover();
_broadcaster.Start();
// 4. 订阅 HardwareDataReportedEventUI 线程回调)
// 5. 订阅 HardwareDataReportedEventUI 线程回调)
_subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
// 5. 启动 OxyPlot 视觉刷新定时器
// 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start();
_plotRefreshTimer.Start();
}
@@ -404,6 +442,72 @@ namespace MonitorModule.ViewModels
? $"发现 {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
#region
@@ -426,7 +530,7 @@ namespace MonitorModule.ViewModels
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
var channel = new MonitorChannel
var channel = new MonitorChannelVM
{
DeviceName = method.DeviceName,
Fingerprint = method.Fingerprint,
@@ -447,7 +551,7 @@ namespace MonitorModule.ViewModels
channel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(MonitorChannel.IsDisplayed))
if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
OnChannelDisplayChanged(channel);
};
@@ -497,7 +601,7 @@ namespace MonitorModule.ViewModels
#endregion
#region /
public void OnChannelDisplayChanged(MonitorChannel channel)
public void OnChannelDisplayChanged(MonitorChannelVM channel)
{
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;
var points = channel.DataPoints.ToArray();

View File

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

View File

@@ -33,6 +33,12 @@ namespace UIShare.GlobalVariable
public ObservableCollection<CANSignalConfig> ConfigurationList = new();
public ObservableCollection<AutoDBCLoadItem> dbcAutoLoadList = 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();
[JsonIgnore]
public ObservableCollection<ParameterVM> ParameterList = new()

View File

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

View File

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