Files
ACP/MonitorModule/ViewModels/MonitorViewModel.cs
2026-07-29 14:20:32 +08:00

852 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Common.Attributes;
using Model.Entity;
using Model.Models;
using NLog;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using Service.Interface;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using TSMasterCAN;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
namespace MonitorModule.ViewModels
{
public class MonitorViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
private IMonitorValueService _monitorValueService;
public bool KeepAlive => true;
public ScopedContext _scopedContext { get; set; } = null!;
public GlobalInfo _globalInfo { get; set; }
private string _testStatus = string.Empty;
public string TestStatus
{
get => _testStatus;
set => SetProperty(ref _testStatus, value);
}
public PlotModel Plot { get; }
/// <summary>已添加的监测通道列表</summary>
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 MonitorChannelVM? _selectedChannel;
public MonitorChannelVM? SelectedChannel
{
get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value);
}
private AvailableMethodItem? _selectedAvailableMethod;
public AvailableMethodItem? SelectedAvailableMethod
{
get => _selectedAvailableMethod;
set => SetProperty(ref _selectedAvailableMethod, value);
}
private string _statusMessage = "图表已就绪,暂无监测项";
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
#endregion
#region
public ICommand AddChannelCommand { get; }
public ICommand DeleteChannelCommand { get; }
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();
// 颜色调色盘
private static readonly OxyColor[] _palette =
{
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
};
private int _colorIndex;
/// <summary>设备数据广播器(全局单例)</summary>
private HardwareDataBroadcaster? _broadcaster;
/// <summary>CAN 信号广播器(全局单例)</summary>
private CANSignalBroadcaster? _canSignalBroadcaster;
/// <summary>用于 OxyPlot X 轴相对秒数</summary>
private readonly Stopwatch _stopwatch = new();
/// <summary>OxyPlot 刷新定时器(仅负责滚动 X 轴 + InvalidatePlot</summary>
private readonly DispatcherTimer _plotRefreshTimer;
/// <summary>事件订阅令牌,用于 Dispose 时取消订阅</summary>
private SubscriptionToken? _subscriptionToken;
private SubscriptionToken? _dbcLoadedToken;
private SubscriptionToken? _dbcUnloadedToken;
// ==========================================
// 批量入库核心结构
// ==========================================
private readonly ConcurrentQueue<MonitorValueEntity> _insertQueue = new();
private Task? _dbFlushTask;
private const int BulkInsertThreshold = 50;
private int _isFlushing = 0;
#endregion
public MonitorViewModel(IContainerExtension container) : base(container)
{
_globalInfo = container.Resolve<GlobalInfo>();
_monitorValueService = container.Resolve<IMonitorValueService>();
Plot = BuildEmptyPlot();
AddChannelCommand = new DelegateCommand(OnAddChannel);
DeleteChannelCommand = new DelegateCommand(OnDeleteChannel);
ResetViewCommand = new DelegateCommand(OnResetView);
RefreshDataCommand = new DelegateCommand(OnRefreshData);
RefreshCommand = new DelegateCommand(OnExpand);
SaveCommand = new DelegateCommand(OnSave);
// OxyPlot 刷新定时器1000ms只负责视觉更新
_plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromMilliseconds(1000)
};
_plotRefreshTimer.Tick += OnPlotRefreshTick;
}
public void Dispose()
{
_monitorCTS?.Cancel();
_monitorCTS?.Dispose();
_monitorCTS = null;
_plotRefreshTimer.Stop();
_plotRefreshTimer.Tick -= OnPlotRefreshTick;
_stopwatch.Stop();
// 取消 EventAggregator 订阅
if (_subscriptionToken != null)
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Unsubscribe(_subscriptionToken);
if (_dbcLoadedToken != null)
_eventAggregator.GetEvent<DBCLoadedEvent>().Unsubscribe(_dbcLoadedToken);
if (_dbcUnloadedToken != null)
_eventAggregator.GetEvent<DBCUnloadedEvent>().Unsubscribe(_dbcUnloadedToken);
if (_dbFlushTask != null)
{
try { _dbFlushTask.Wait(TimeSpan.FromSeconds(1.5)); } catch { }
}
// 3. 释放容器作用域
_scope?.Dispose();
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 广播触发。
/// 已通过 ThreadOption.UIThread 确保在 UI 线程执行。
/// 通过 HardwareFingerprint + MethodName 匹配监测通道。
/// </summary>
private void OnHardwareDataReceived(HardwareReportArgs args)
{
// 仅处理属于当前 Scope 的事件
if (string.IsNullOrEmpty(TestStatus) || args.Scope != TestStatus) return;
// 以硬件指纹 + 方法名唯一匹配通道(与逻辑名解耦)
var channel = Channels.FirstOrDefault(c =>
c.Fingerprint == args.HardwareFingerprint &&
c.MethodName == args.MethodName &&
c.IsMonitored);
if (channel == null) return;
// 记录数据点线程安全Record 内部 ConcurrentQueue + Series 操作)
channel.Record(args.Time, args.Value);
// 入队数据库批量写入
// CreateTime 使用 args.Time采样触发时刻而不是 DateTime.Now设备响应到达时刻
// 这样即使设备响应有延迟,数据库里的时间戳仍然按设定采样间隔分布。
var entity = new MonitorValueEntity
{
MonitorName = channel.DisplayName,
MonitorValue = args.Value,
Scope = args.Scope,
CreateTime = args.Time,
IsDel = 0
};
_insertQueue.Enqueue(entity);
if (_insertQueue.Count >= BulkInsertThreshold)
{
_ = Task.Run(async () => await DoFlushWorkAsync().ConfigureAwait(false));
}
}
#endregion
#region OxyPlot
private void OnPlotRefreshTick(object? sender, EventArgs e)
{
if (!_stopwatch.IsRunning) return;
var now = DateTime.Now;
var xAxis = Plot.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom);
if (xAxis != null)
{
xAxis.Minimum = DateTimeAxis.ToDouble(now.AddSeconds(-20));
xAxis.Maximum = DateTimeAxis.ToDouble(now.AddSeconds(0.5));
}
Plot.InvalidatePlot(true);
}
#endregion
#region
private void StartDbFlushWorker(CancellationToken token)
{
_dbFlushTask = Task.Run(async () =>
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
await DoFlushWorkAsync().ConfigureAwait(false);
}
catch (OperationCanceledException) { break; }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DB Worker Error] {ex.Message}");
}
}
await DoFlushWorkAsync().ConfigureAwait(false);
}, token);
}
private async Task DoFlushWorkAsync()
{
if (_insertQueue.IsEmpty) return;
if (Interlocked.CompareExchange(ref _isFlushing, 1, 0) != 0) return;
try
{
var listToInsert = new List<MonitorValueEntity>();
while (_insertQueue.TryDequeue(out var entity)) listToInsert.Add(entity);
if (listToInsert.Count > 0)
{
var result = await _monitorValueService.InsertRangeAsync(listToInsert).ConfigureAwait(false);
}
}
finally { Interlocked.Exchange(ref _isFlushing, 0); }
}
#endregion
#region Navigation
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
{
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
Plot.Title = $"监控 - {TestStatus}";
Plot.InvalidatePlot(false);
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve<ScopedContext>();
_deviceManager = _scope.Resolve<DeviceManager>();
_systemConfig = _scope.Resolve<SystemConfig>();
RaisePropertyChanged(nameof(Channels));
_broadcaster = _scope.Resolve<HardwareDataBroadcaster>();
_canSignalBroadcaster = _scope.Resolve<CANSignalBroadcaster>();
IsInitiated = true;
if (_monitorCTS == null) _monitorCTS = new CancellationTokenSource();
// 1. 启动后台数据库消费
StartDbFlushWorker(_monitorCTS.Token);
// 2. 扫描可监测方法(供 UI 选择)
DiscoverAvailableMethods();
// 3. 从配置自动恢复已保存的监测通道
RestoreChannelsFromConfig();
// 4. 启动广播器Discover + Start
_broadcaster.Discover();
_broadcaster.Start();
_canSignalBroadcaster.Discover();
_canSignalBroadcaster.Start();
// 5. 订阅 HardwareDataReportedEventUI 线程回调)
_subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
// 5b. 订阅 DBC 加载/卸载事件,动态发现或清除 CAN 信号
_dbcLoadedToken = _eventAggregator.GetEvent<DBCLoadedEvent>()
.Subscribe(OnDbcLoaded, ThreadOption.UIThread);
_dbcUnloadedToken = _eventAggregator.GetEvent<DBCUnloadedEvent>()
.Subscribe(OnDbcUnloaded, ThreadOption.UIThread);
// 5c. 根据 SystemConfig.ConfigurationList 刷新已加载 DBC 中的 CAN 信号
RefreshConfiguredCanSignals();
// 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start();
_plotRefreshTimer.Start();
}
}
#endregion
#region PlotModel
private static PlotModel BuildEmptyPlot()
{
var pm = new PlotModel
{
Title = string.Empty,
PlotAreaBorderColor = OxyColors.LightGray,
Background = OxyColors.White
};
pm.Axes.Add(new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = "时间",
StringFormat = "HH:mm:ss",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = "值",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Legends.Add(new Legend
{
LegendPosition = LegendPosition.RightTop,
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
LegendBorder = OxyColors.LightGray
});
return pm;
}
#endregion
#region UI
/// <summary>
/// 通过 GlobalInfo.HardwarePool 构建 设备实例引用→硬件指纹 的反向查找表。
/// </summary>
private Dictionary<object, string> BuildInstanceToFingerprint()
{
var map = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
foreach (var poolEntry in _globalInfo.HardwarePool)
{
var lazy = poolEntry.Value;
if (lazy.IsValueCreated && lazy.Value != null)
map[lazy.Value] = poolEntry.Key;
}
return map;
}
private void DiscoverAvailableMethods()
{
AvailableMethods.Clear();
if (_deviceManager?.DeviceMap == null || _deviceManager.DeviceMap.Count == 0)
{
StatusMessage = "当前工位无可用设备";
return;
}
var instanceToFp = BuildInstanceToFingerprint();
foreach (var kvp in _deviceManager.DeviceMap)
{
string deviceName = kvp.Key;
var device = kvp.Value;
var deviceType = device.GetType();
// 反查该实例的硬件指纹
if (!instanceToFp.TryGetValue(device, out string? fingerprint))
continue;
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m =>
{
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
if (m.ReturnType != typeof(Task<string>)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
});
foreach (var method in methods)
{
var attr = method.GetCustomAttribute<MonitorableAttribute>();
string displayName = !string.IsNullOrEmpty(attr?.Description)
? $"{deviceName}.{attr.Description}"
: $"{deviceName}.{method.Name}";
AvailableMethods.Add(new AvailableMethodItem
{
DeviceName = deviceName,
Fingerprint = fingerprint,
MethodName = method.Name,
DisplayName = displayName,
MethodInfo = method,
Device = device
});
}
}
StatusMessage = AvailableMethods.Count > 0
? $"发现 {AvailableMethods.Count} 个可监测项"
: "未发现可监测的设备方法";
}
/// <summary>
/// 从 SystemConfig.MonitorChannels 持久化列表自动还原监测通道。
/// </summary>
private void RestoreChannelsFromConfig()
{
if (_systemConfig?.MonitorChannels == null || _systemConfig.MonitorChannels.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} 个监测通道";
}
}
/// <summary>
/// 根据 SystemConfig.ConfigurationList 刷新所有已加载 DBC 中的 CAN 信号。
/// 只添加 ConfigurationList 中配置且实际存在于 DBC 的信号;不存在则清理。
/// </summary>
private void RefreshConfiguredCanSignals()
{
if (_systemConfig?.ConfigurationList == null || _systemConfig.ConfigurationList.Count == 0) return;
if (_deviceManager?.CANFD == null) return;
var msgDb = DBCParse.MsgDatabase;
foreach (var channel in _systemConfig.ConfigurationList.Select(c => c.Channel).Distinct())
{
if (channel < 0 || channel >= msgDb.Count) continue;
RefreshCanSignalsForChannel((uint)channel, msgDb[channel]);
}
}
/// <summary>
/// 刷新单个通道的 CAN 信号:以 ConfigurationList 为白名单,对照 DBC 实际存在性。
/// - DBC 中存在 → 加入 AvailableMethods若 MonitorChannels 中也有,则恢复 Channel。
/// - DBC 中不存在 → 从 AvailableMethods 移除;若已在 Channels 中,则删除。
/// </summary>
private void RefreshCanSignalsForChannel(uint channel, List<_Msg_> messages)
{
if (_systemConfig?.ConfigurationList == null) return;
string canDeviceFingerprint = _deviceManager?.GetCanDeviceFingerprint() ?? string.Empty;
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, channel);
var configs = _systemConfig.ConfigurationList.Where(c => c.Channel == (int)channel).ToList();
bool changed = false;
foreach (var cfg in configs)
{
if (string.IsNullOrEmpty(cfg.SignalName)) continue;
string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
bool existsInDbc = messages.Any(m =>
m.msg_id == cfg.MessageID &&
m.signal_Name != null &&
m.signal_Name.Contains(cfg.SignalName));
if (existsInDbc)
{
// 加入 AvailableMethods去重
if (!AvailableMethods.Any(m => m.Fingerprint == fingerprint && m.MethodName == methodName))
{
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
AvailableMethods.Add(new AvailableMethodItem
{
DeviceName = $"CAN{channel}",
Fingerprint = fingerprint,
MethodName = methodName,
DisplayName = displayName,
MethodInfo = null!,
Device = null!
});
changed = true;
}
// 如果 MonitorChannels 中有持久化记录,自动恢复 Channel
var monitorConfig = _systemConfig.MonitorChannels?.FirstOrDefault(m =>
m.Fingerprint == fingerprint && m.MethodName == methodName);
if (monitorConfig != null && !Channels.Any(c =>
c.Fingerprint == fingerprint && c.MethodName == methodName))
{
AddCanChannel(cfg, fingerprint, methodName, monitorConfig.IsDisplayed);
changed = true;
}
}
else
{
// DBC 中不存在:从 AvailableMethods 移除
var methodToRemove = AvailableMethods.FirstOrDefault(m =>
m.Fingerprint == fingerprint && m.MethodName == methodName);
if (methodToRemove != null)
{
AvailableMethods.Remove(methodToRemove);
changed = true;
}
// 如果该信号已经在 Channels或 MonitorChannels删除
var channelToRemove = Channels.FirstOrDefault(c =>
c.Fingerprint == fingerprint && c.MethodName == methodName);
if (channelToRemove != null)
{
if (channelToRemove.Series != null) Plot.Series.Remove(channelToRemove.Series);
Channels.Remove(channelToRemove);
changed = true;
}
}
}
if (changed)
{
Plot.InvalidatePlot(true);
StatusMessage = $"CAN{channel} 已刷新配置信号";
}
}
/// <summary>
/// DBCLoadedEvent 回调DBC 加载后刷新该通道的配置信号。
/// </summary>
private void OnDbcLoaded(DBCLoadedArgs args)
{
if (args.Scope != TestStatus) return;
if (_deviceManager?.CANFD == null) return;
int channel = (int)args.Channel;
var msgDb = DBCParse.MsgDatabase;
if (channel < 0 || channel >= msgDb.Count) return;
RefreshCanSignalsForChannel(args.Channel, msgDb[channel]);
}
/// <summary>
/// DBCUnloadedEvent 回调:移除对应通道的 CAN 信号AvailableMethods + Channels
/// </summary>
private void OnDbcUnloaded(DBCUnloadedArgs args)
{
if (args.Scope != TestStatus) return;
string canDeviceFingerprint = _deviceManager?.GetCanDeviceFingerprint() ?? string.Empty;
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel);
// 从 AvailableMethods 中移除
var toRemoveMethods = AvailableMethods.Where(m => m.Fingerprint == fingerprint).ToList();
foreach (var m in toRemoveMethods)
AvailableMethods.Remove(m);
// 从 Channels 中移除(并清理 Plot
var toRemoveChannels = Channels.Where(c => c.Fingerprint == fingerprint).ToList();
foreach (var c in toRemoveChannels)
{
if (c.Series != null) Plot.Series.Remove(c.Series);
Channels.Remove(c);
}
if (toRemoveMethods.Count > 0 || toRemoveChannels.Count > 0)
{
Plot.InvalidatePlot(true);
StatusMessage = $"CAN{args.Channel} DBC 已卸载,移除 {toRemoveMethods.Count} 个信号";
}
}
/// <summary>
/// 将单个 CAN 信号添加为监测通道。
/// </summary>
private void AddCanChannel(CANSignalConfig cfg, string fingerprint, string methodName, bool isDisplayed)
{
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
var channel = new MonitorChannelVM
{
DeviceName = $"CAN{cfg.Channel}",
Fingerprint = fingerprint,
MethodName = methodName,
DisplayName = displayName,
Color = color,
IsMonitored = true,
IsDisplayed = isDisplayed
};
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);
}
#endregion
#region
private void OnAddChannel()
{
var method = SelectedAvailableMethod;
if (method == null)
{
StatusMessage = "请先在列表中选择一个监测项";
return;
}
// 以指纹+方法名去重,防止同一物理设备重复添加
if (Channels.Any(c => c.Fingerprint == method.Fingerprint && c.MethodName == method.MethodName))
{
StatusMessage = $"监测项 [{method.DisplayName}] 已存在";
return;
}
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 = true
};
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);
SelectedChannel = channel;
Plot.InvalidatePlot(true);
StatusMessage = $"已添加监测项 [{channel.DisplayName}],当前共 {Channels.Count} 项";
}
private void OnDeleteChannel()
{
var target = SelectedChannel ?? Channels.LastOrDefault();
if (target == null)
{
StatusMessage = "无可删除的监测项";
return;
}
if (target.Series != null) Plot.Series.Remove(target.Series);
Channels.Remove(target);
SelectedChannel = Channels.LastOrDefault();
Plot.InvalidatePlot(true);
StatusMessage = $"已删除监测项 [{target.DisplayName}],剩余 {Channels.Count} 项";
}
private void OnResetView()
{
Plot.ResetAllAxes();
Plot.InvalidatePlot(false);
StatusMessage = "视图已按数据范围复原";
}
private void OnRefreshData()
{
Plot.InvalidatePlot(true);
StatusMessage = $"已刷新({DateTime.Now:HH:mm:ss}";
}
private void OnExpand()
{
if (string.IsNullOrEmpty(TestStatus)) return;
_globalInfo.CurrentScope = TestStatus;
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
}
#endregion
#region /
public void OnChannelDisplayChanged(MonitorChannelVM channel)
{
if (channel.IsDisplayed)
{
if (channel.Series == null)
{
channel.Series = new LineSeries
{
Title = channel.DisplayName,
Color = channel.Color,
StrokeThickness = 1.5
};
var recent = channel.DataPoints.ToArray();
var startIdx = Math.Max(0, recent.Length - 10000);
for (int i = startIdx; i < recent.Length; i++)
channel.Series.Points.Add(new DataPoint(recent[i].Time.ToOADate(), recent[i].DisplayValue));
Plot.Series.Add(channel.Series);
Plot.InvalidatePlot(true);
}
}
else
{
if (channel.Series != null)
{
Plot.Series.Remove(channel.Series);
channel.Series = null;
Plot.InvalidatePlot(true);
}
}
}
public void OnChannelMathChanged(MonitorChannelVM channel)
{
if (channel.Series == null) return;
var points = channel.DataPoints.ToArray();
channel.Series.Points.Clear();
var startIdx = Math.Max(0, points.Length - 10000);
for (int i = startIdx; i < points.Length; i++)
channel.Series.Points.Add(new DataPoint(points[i].Time.ToOADate(), points[i].DisplayValue));
Plot.InvalidatePlot(true);
}
#endregion
}
}