回读图像功能优化
This commit is contained in:
@@ -106,6 +106,7 @@ namespace ACP
|
||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
||||
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
||||
containerRegistry.RegisterSingleton<GlobalConfig>();
|
||||
//注册AutoMapper
|
||||
var config = new MapperConfiguration(
|
||||
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace ACP.ViewModels
|
||||
private readonly INotificationManager _notificationManager;
|
||||
private readonly IModuleManager _moduleManager;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
private readonly GlobalConfig _globalConfig;
|
||||
|
||||
// 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
|
||||
private readonly DispatcherTimer _uiRefreshTimer;
|
||||
@@ -165,6 +166,21 @@ namespace ACP.ViewModels
|
||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
||||
_globalConfig = containerProvider.Resolve<GlobalConfig>();
|
||||
if (ConfigService.IsExit("GlobalConfig"))
|
||||
{
|
||||
string filePath = System.IO.Path.Combine(_globalConfig.SystemPath, "GlobalConfig.json");
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
string json = System.IO.File.ReadAllText(filePath);
|
||||
Newtonsoft.Json.JsonConvert.PopulateObject(json, _globalConfig);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_globalConfig = new GlobalConfig();
|
||||
ConfigService.SaveGlobalConfig(_globalConfig);
|
||||
}
|
||||
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
||||
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
|
||||
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
using CurveModule.Views;
|
||||
using CurveModule.Views.Dialogs;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CurveModule
|
||||
@@ -14,6 +15,7 @@ namespace CurveModule
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
||||
containerRegistry.RegisterForNavigation<CurveStatisticsView>("CurveStatistics");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Logger;
|
||||
using OxyPlot;
|
||||
using OxyPlot.Annotations;
|
||||
using OxyPlot.Axes;
|
||||
using OxyPlot.Legends;
|
||||
using OxyPlot.Series;
|
||||
@@ -11,7 +12,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using CurveModule.ViewModels.Dialogs;
|
||||
using UIShare.ViewModelBase;
|
||||
using UIShare.GlobalVariable;
|
||||
|
||||
namespace CurveModule.ViewModels
|
||||
{
|
||||
@@ -20,6 +23,9 @@ namespace CurveModule.ViewModels
|
||||
/// </summary>
|
||||
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
private GlobalConfig _globalConfig;
|
||||
#endregion
|
||||
#region 属性
|
||||
public bool KeepAlive => true;
|
||||
|
||||
@@ -47,6 +53,11 @@ namespace CurveModule.ViewModels
|
||||
public ICommand LoadCsvCommand { get; }
|
||||
public ICommand ResetViewCommand { get; }
|
||||
public ICommand ClearCommand { get; }
|
||||
public ICommand StatisticsCommand { get; }
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand LoadCommand { get; }
|
||||
public ICommand CaptureCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
@@ -58,39 +69,84 @@ namespace CurveModule.ViewModels
|
||||
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
||||
};
|
||||
private int _colorIndex;
|
||||
|
||||
// 双光标截取
|
||||
private LineAnnotation? _cursor1;
|
||||
private LineAnnotation? _cursor2;
|
||||
private LineAnnotation? _draggingCursor;
|
||||
private List<MonitorRecord> _allRecords = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>截取按钮文本(随状态切换)</summary>
|
||||
private string _captureButtonText = "开始截取图形";
|
||||
public string CaptureButtonText
|
||||
{
|
||||
get => _captureButtonText;
|
||||
set => SetProperty(ref _captureButtonText, value);
|
||||
}
|
||||
|
||||
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_globalConfig = containerProvider.Resolve<GlobalConfig>();
|
||||
Plot = BuildEmptyPlot();
|
||||
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
|
||||
LoadCsvCommand = new DelegateCommand<string>(OnLoadCsv);
|
||||
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||
ClearCommand = new DelegateCommand(OnClear);
|
||||
StatisticsCommand = new DelegateCommand(OnStatistics);
|
||||
SaveCommand = new DelegateCommand(OnSave);
|
||||
LoadCommand = new DelegateCommand(Onload);
|
||||
CaptureCommand = new DelegateCommand(OnToggleCapture);
|
||||
}
|
||||
|
||||
|
||||
#region 命令处理
|
||||
private void OnSave()
|
||||
{
|
||||
ConfigService.SaveGlobalConfig(_globalConfig);
|
||||
}
|
||||
private void Onload()
|
||||
{
|
||||
if (File.Exists(_globalConfig.DefaultCSVFilePath))
|
||||
{
|
||||
OnLoadCsv(_globalConfig.DefaultCSVFilePath);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
|
||||
/// </summary>
|
||||
private void OnLoadCsv()
|
||||
|
||||
private void OnLoadCsv(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
// 1. 统一处理 filename
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
Title = "选择要加载的监测数据 CSV 文件"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
Title = "选择要加载的监测数据 CSV 文件"
|
||||
};
|
||||
|
||||
if (dlg.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
// 将弹窗选中的路径统一赋给 filename
|
||||
filename = dlg.FileName;
|
||||
_globalConfig.DefaultCSVFilePath = filename;
|
||||
}
|
||||
|
||||
// 2. 统一解析数据
|
||||
List<MonitorRecord> records = ParseCsv(filename);
|
||||
_allRecords = records;
|
||||
|
||||
var records = ParseCsv(dlg.FileName);
|
||||
if (records.Count == 0)
|
||||
{
|
||||
StatusMessage = "CSV 文件为空或格式不正确";
|
||||
return;
|
||||
}
|
||||
|
||||
// 按 (MonitorName, Scope) 分组
|
||||
// 3. 按 (MonitorName, Scope) 分组
|
||||
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
|
||||
.OrderBy(g => g.Key.MonitorName)
|
||||
.ThenBy(g => g.Key.Scope);
|
||||
@@ -138,8 +194,10 @@ namespace CurveModule.ViewModels
|
||||
}
|
||||
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = $"已加载 {Path.GetFileName(dlg.FileName)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
|
||||
LoggerHelper.Info($"曲线回读:加载 {dlg.FileName},{Channels.Count} 条曲线,{records.Count} 个数据点");
|
||||
|
||||
// 4. 统一使用 filename 变量进行日志和状态更新
|
||||
StatusMessage = $"已加载 {Path.GetFileName(filename)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
|
||||
LoggerHelper.Info($"曲线回读:加载 {filename},{Channels.Count} 条曲线,{records.Count} 个数据点");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -157,10 +215,43 @@ namespace CurveModule.ViewModels
|
||||
|
||||
private void OnClear()
|
||||
{
|
||||
RemoveCursors();
|
||||
CaptureButtonText = "开始截取图形";
|
||||
ClearPlotAndChannels();
|
||||
_allRecords.Clear();
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = "已清空,请重新加载 CSV 文件";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对每条曲线计算数学统计(最大、最小、平均、斜率、积分),弹窗显示。
|
||||
/// </summary>
|
||||
private void OnStatistics()
|
||||
{
|
||||
if (Channels.Count == 0)
|
||||
{
|
||||
ShowInfoMessageBox("请先加载 CSV 文件", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var results = new ObservableCollection<CurveStatisticsResult>();
|
||||
foreach (var channel in Channels)
|
||||
{
|
||||
if (channel.Series == null || channel.Series.Points.Count == 0) continue;
|
||||
results.Add(ComputeStatistics(channel));
|
||||
}
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
ShowInfoMessageBox("没有可统计的曲线数据", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = new DialogParameters();
|
||||
parameters.Add("Results", results);
|
||||
_dialogService.ShowDialog("CurveStatistics", parameters, _ => { });
|
||||
StatusMessage = $"已计算 {results.Count} 条曲线的数学统计";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CSV 解析
|
||||
@@ -302,6 +393,79 @@ namespace CurveModule.ViewModels
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
/// <summary>
|
||||
/// 对单条曲线计算数学统计:最大值、最小值、平均值、线性回归斜率、梯形积分。
|
||||
/// 斜率单位:值/秒;积分单位:值×秒。
|
||||
/// </summary>
|
||||
private static CurveStatisticsResult ComputeStatistics(CurveRecallChannelVM channel)
|
||||
{
|
||||
var points = channel.Series!.Points;
|
||||
int n = points.Count;
|
||||
|
||||
if (n == 0)
|
||||
return new CurveStatisticsResult
|
||||
{
|
||||
DisplayName = channel.DisplayName,
|
||||
PointCount = 0
|
||||
};
|
||||
|
||||
double max = double.MinValue;
|
||||
double min = double.MaxValue;
|
||||
double sumY = 0;
|
||||
|
||||
// 线性回归累计量
|
||||
double sumX = 0, sumXY = 0, sumX2 = 0;
|
||||
|
||||
// 梯形积分累计量
|
||||
double integral = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
double x = points[i].X; // OADate 天数
|
||||
double y = points[i].Y;
|
||||
|
||||
if (y > max) max = y;
|
||||
if (y < min) min = y;
|
||||
sumY += y;
|
||||
|
||||
sumX += x;
|
||||
sumXY += x * y;
|
||||
sumX2 += x * x;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
double dx = x - points[i - 1].X;
|
||||
integral += dx * (y + points[i - 1].Y) / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
double average = sumY / n;
|
||||
|
||||
// 线性回归斜率(单位:值 / OADate 天)
|
||||
double slopePerDay = 0;
|
||||
double denominator = n * sumX2 - sumX * sumX;
|
||||
if (Math.Abs(denominator) > 1e-30)
|
||||
{
|
||||
slopePerDay = (n * sumXY - sumX * sumY) / denominator;
|
||||
}
|
||||
// 转换为 值/秒
|
||||
double slopePerSecond = slopePerDay / 86400.0;
|
||||
|
||||
// 积分从 OADate·值 转换为 秒·值
|
||||
double integralSeconds = integral * 86400.0;
|
||||
|
||||
return new CurveStatisticsResult
|
||||
{
|
||||
DisplayName = channel.DisplayName,
|
||||
PointCount = n,
|
||||
Max = max,
|
||||
Min = min,
|
||||
Average = average,
|
||||
Slope = slopePerSecond,
|
||||
Integral = integralSeconds
|
||||
};
|
||||
}
|
||||
|
||||
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
|
||||
@@ -334,6 +498,194 @@ namespace CurveModule.ViewModels
|
||||
Channels.Clear();
|
||||
_colorIndex = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 双光标截取
|
||||
/// <summary>
|
||||
/// 切换截取模式:添加/移除双光标,结束时导出选中范围 CSV。
|
||||
/// </summary>
|
||||
private void OnToggleCapture()
|
||||
{
|
||||
if (_cursor1 == null)
|
||||
{
|
||||
CreateCursors();
|
||||
CaptureButtonText = "结束截取图形";
|
||||
}
|
||||
else
|
||||
{
|
||||
double x1 = _cursor1.X;
|
||||
double x2 = _cursor2!.X;
|
||||
double minX = Math.Min(x1, x2);
|
||||
double maxX = Math.Max(x1, x2);
|
||||
|
||||
RemoveCursors();
|
||||
CaptureButtonText = "开始截取图形";
|
||||
|
||||
ExportCapturedCsv(minX, maxX);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在图表上添加两条可拖拽的竖直光标。
|
||||
/// </summary>
|
||||
private void CreateCursors()
|
||||
{
|
||||
if (Channels.Count == 0) return;
|
||||
|
||||
var dateAxis = Plot.Axes.OfType<DateTimeAxis>().FirstOrDefault();
|
||||
if (dateAxis == null) return;
|
||||
|
||||
double xMin = dateAxis.ActualMinimum;
|
||||
double xMax = dateAxis.ActualMaximum;
|
||||
double range = xMax - xMin;
|
||||
if (range <= 0) return;
|
||||
|
||||
_cursor1 = new LineAnnotation
|
||||
{
|
||||
Type = LineAnnotationType.Vertical,
|
||||
X = xMin + range * 0.3,
|
||||
Color = OxyColors.SteelBlue,
|
||||
StrokeThickness = 2,
|
||||
LineStyle = LineStyle.Dash,
|
||||
Text = "▼",
|
||||
TextColor = OxyColors.SteelBlue,
|
||||
TextHorizontalAlignment = HorizontalAlignment.Center,
|
||||
TextVerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
_cursor2 = new LineAnnotation
|
||||
{
|
||||
Type = LineAnnotationType.Vertical,
|
||||
X = xMin + range * 0.7,
|
||||
Color = OxyColors.SteelBlue,
|
||||
StrokeThickness = 2,
|
||||
LineStyle = LineStyle.Dash,
|
||||
Text = "▼",
|
||||
TextColor = OxyColors.SteelBlue,
|
||||
TextHorizontalAlignment = HorizontalAlignment.Center,
|
||||
TextVerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
_cursor1.MouseDown += OnCursorMouseDown;
|
||||
_cursor1.MouseUp += OnCursorMouseUp;
|
||||
_cursor2.MouseDown += OnCursorMouseDown;
|
||||
_cursor2.MouseUp += OnCursorMouseUp;
|
||||
Plot.MouseDown += OnPlotMouseMove;
|
||||
|
||||
Plot.Annotations.Add(_cursor1);
|
||||
Plot.Annotations.Add(_cursor2);
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = "已添加双光标,拖拽竖线选择范围,完成后点击「结束截取图形」";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除双光标并清理事件。
|
||||
/// </summary>
|
||||
private void RemoveCursors()
|
||||
{
|
||||
if (_cursor1 != null)
|
||||
{
|
||||
_cursor1.MouseDown -= OnCursorMouseDown;
|
||||
_cursor1.MouseUp -= OnCursorMouseUp;
|
||||
Plot.Annotations.Remove(_cursor1);
|
||||
_cursor1 = null;
|
||||
}
|
||||
if (_cursor2 != null)
|
||||
{
|
||||
_cursor2.MouseDown -= OnCursorMouseDown;
|
||||
_cursor2.MouseUp -= OnCursorMouseUp;
|
||||
Plot.Annotations.Remove(_cursor2);
|
||||
_cursor2 = null;
|
||||
}
|
||||
Plot.MouseDown -= OnPlotMouseMove;
|
||||
_draggingCursor = null;
|
||||
Plot.InvalidatePlot(true);
|
||||
}
|
||||
|
||||
private void OnCursorMouseDown(object sender, OxyMouseDownEventArgs e)
|
||||
{
|
||||
if (sender is LineAnnotation line)
|
||||
_draggingCursor = line;
|
||||
}
|
||||
|
||||
private void OnCursorMouseUp(object sender, OxyMouseEventArgs e)
|
||||
{
|
||||
_draggingCursor = null;
|
||||
}
|
||||
|
||||
private void OnPlotMouseMove(object sender, OxyMouseDownEventArgs e)
|
||||
{
|
||||
if (_draggingCursor == null) return;
|
||||
|
||||
var position = ((OxyMouseEventArgs)e).Position;
|
||||
var axis = Plot.Axes.OfType<DateTimeAxis>().FirstOrDefault();
|
||||
if (axis == null) return;
|
||||
|
||||
double dataX = axis.InverseTransform(position.X, position.Y, null).X;
|
||||
|
||||
// 限制光标不超出数据范围
|
||||
double xMin = axis.ActualMinimum;
|
||||
double xMax = axis.ActualMaximum;
|
||||
dataX = Math.Max(xMin, Math.Min(xMax, dataX));
|
||||
|
||||
_draggingCursor.X = dataX;
|
||||
_draggingCursor.Text = $"▼ {DateTime.FromOADate(dataX):HH:mm:ss}";
|
||||
|
||||
Plot.InvalidatePlot(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出双光标选定时间范围内的原始记录到 CSV。
|
||||
/// </summary>
|
||||
private void ExportCapturedCsv(double minXOaDate, double maxXOaDate)
|
||||
{
|
||||
if (_allRecords.Count == 0)
|
||||
{
|
||||
ShowInfoMessageBox("没有加载数据,无法导出", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var startTime = DateTime.FromOADate(minXOaDate);
|
||||
var endTime = DateTime.FromOADate(maxXOaDate);
|
||||
|
||||
var filtered = _allRecords
|
||||
.Where(r => r.CreateTime >= startTime && r.CreateTime <= endTime)
|
||||
.OrderBy(r => r.CreateTime)
|
||||
.ToList();
|
||||
|
||||
if (filtered.Count == 0)
|
||||
{
|
||||
ShowInfoMessageBox("选定范围内没有数据", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv",
|
||||
Title = "导出截取范围数据",
|
||||
FileName = $"captured_{DateTime.Now:yyyyMMdd_HHmmss}.csv"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
using var sw = new StreamWriter(dlg.FileName, false, Encoding.UTF8);
|
||||
sw.WriteLine("MonitorName,MonitorValue,Scope,CreateTime");
|
||||
foreach (var r in filtered)
|
||||
{
|
||||
sw.WriteLine($"{EscapeCsvField(r.MonitorName)},{r.MonitorValue},{EscapeCsvField(r.Scope)},{r.CreateTime:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
}
|
||||
|
||||
StatusMessage = $"已导出 {filtered.Count} 条记录到 {Path.GetFileName(dlg.FileName)}";
|
||||
LoggerHelper.Info($"导出截取范围 CSV:{dlg.FileName},{filtered.Count} 条记录");
|
||||
}
|
||||
|
||||
private static string EscapeCsvField(string field)
|
||||
{
|
||||
if (string.IsNullOrEmpty(field)) return "";
|
||||
if (field.Contains(',') || field.Contains('"') || field.Contains('\n'))
|
||||
return $"\"{field.Replace("\"", "\"\"")}\"";
|
||||
return field;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
@@ -343,6 +695,7 @@ namespace CurveModule.ViewModels
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RemoveCursors();
|
||||
ClearPlotAndChannels();
|
||||
}
|
||||
#endregion
|
||||
|
||||
95
CurveModule/ViewModels/Dialogs/CurveStatisticsViewModel.cs
Normal file
95
CurveModule/ViewModels/Dialogs/CurveStatisticsViewModel.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Prism.Commands;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace CurveModule.ViewModels.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线统计结果弹窗 ViewModel。
|
||||
/// </summary>
|
||||
public class CurveStatisticsViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
private string _title = "曲线数学统计";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<CurveStatisticsResult> Results { get; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand CloseCommand { get; }
|
||||
#endregion
|
||||
|
||||
public CurveStatisticsViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
CloseCommand = new DelegateCommand(Close);
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
RequestClose.Invoke(ButtonResult.OK);
|
||||
}
|
||||
|
||||
#region Prism Dialog 规范
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||
}
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||
if (parameters.ContainsKey("Results"))
|
||||
{
|
||||
var list = parameters.GetValue<ObservableCollection<CurveStatisticsResult>>("Results");
|
||||
Results.Clear();
|
||||
foreach (var item in list)
|
||||
Results.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单条曲线的数学统计结果。
|
||||
/// </summary>
|
||||
public class CurveStatisticsResult
|
||||
{
|
||||
/// <summary>曲线名称</summary>
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>数据点数</summary>
|
||||
public int PointCount { get; set; }
|
||||
|
||||
/// <summary>最大值</summary>
|
||||
public double Max { get; set; }
|
||||
|
||||
/// <summary>最小值</summary>
|
||||
public double Min { get; set; }
|
||||
|
||||
/// <summary>平均值</summary>
|
||||
public double Average { get; set; }
|
||||
|
||||
/// <summary>线性回归斜率(单位/秒)</summary>
|
||||
public double Slope { get; set; }
|
||||
|
||||
/// <summary>梯形积分值(单位·秒)</summary>
|
||||
public double Integral { get; set; }
|
||||
|
||||
public string MaxText => Max.ToString("G6");
|
||||
public string MinText => Min.ToString("G6");
|
||||
public string AverageText => Average.ToString("G6");
|
||||
public string SlopeText => Slope.ToString("G6");
|
||||
public string IntegralText => Integral.ToString("G6");
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,18 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="1200">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<UserControl.Resources>
|
||||
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||
</UserControl.Resources>
|
||||
@@ -46,6 +52,19 @@
|
||||
Command="{Binding ClearCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="清空已加载的曲线数据"/>
|
||||
<Button Content="∑ 数学统计"
|
||||
Command="{Binding StatisticsCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="对每条曲线计算最大值、最小值、平均值、斜率、积分"/>
|
||||
<Button Content=" 保存"
|
||||
Command="{Binding SaveCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="保存配置"/>
|
||||
<Button Content="{Binding CaptureButtonText}"
|
||||
Command="{Binding CaptureCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="添加双光标选择时间范围,导出选定范围的 CSV"/>
|
||||
<Button Click="Button_Click" Content="截图" Margin="6,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -140,6 +159,7 @@
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<oxy:PlotView Model="{Binding Plot}"
|
||||
x:Name="Chart"
|
||||
Background="Transparent"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@@ -24,5 +24,32 @@ namespace CurveModule.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
DrawingVisual drawingVisual = new DrawingVisual();
|
||||
using (DrawingContext context = drawingVisual.RenderOpen())
|
||||
{
|
||||
VisualBrush brush = new VisualBrush(Chart) { Stretch = Stretch.None };
|
||||
context.DrawRectangle(brush, null, new Rect(0, 0, Chart.ActualWidth, Chart.ActualHeight));
|
||||
context.Close();
|
||||
}
|
||||
RenderTargetBitmap targetBitmap = new RenderTargetBitmap((int)Chart.ActualWidth, (int)Chart.ActualHeight, 96d, 96d, PixelFormats.Default);
|
||||
targetBitmap.Render(drawingVisual);
|
||||
PngBitmapEncoder saveEncoder = new PngBitmapEncoder();
|
||||
saveEncoder.Frames.Add(BitmapFrame.Create(targetBitmap));
|
||||
string tempFile = @$"D:\ACP\图片\{DateTime.Now:yyyyMMdd_HHmmss}.png";
|
||||
System.IO.FileStream fs = System.IO.File.Open(tempFile, System.IO.FileMode.OpenOrCreate);
|
||||
saveEncoder.Save(fs);
|
||||
fs.Close();
|
||||
MessageBox.Show($"导出图片成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"导出图片失败:{ex.Message}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
89
CurveModule/Views/Dialogs/CurveStatisticsView.xaml
Normal file
89
CurveModule/Views/Dialogs/CurveStatisticsView.xaml
Normal file
@@ -0,0 +1,89 @@
|
||||
<UserControl x:Class="CurveModule.Views.Dialogs.CurveStatisticsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
Background="White"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Width="900"
|
||||
Height="520"
|
||||
mc:Ignorable="d">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<Grid>
|
||||
<GroupBox Padding="10,8,10,0"
|
||||
Header="{Binding Title}"
|
||||
helpers:WindowDragHelper.EnableWindowDrag="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DataGrid Grid.Row="0"
|
||||
ItemsSource="{Binding Results}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
CanUserSortColumns="True"
|
||||
Background="Transparent"
|
||||
SelectionMode="Single"
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="Horizontal"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#DDD"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="曲线名称"
|
||||
Binding="{Binding DisplayName}"
|
||||
Width="180"/>
|
||||
<DataGridTextColumn Header="数据点数"
|
||||
Binding="{Binding PointCount}"
|
||||
Width="70"/>
|
||||
<DataGridTextColumn Header="最大值"
|
||||
Binding="{Binding MaxText}"
|
||||
Width="100"/>
|
||||
<DataGridTextColumn Header="最小值"
|
||||
Binding="{Binding MinText}"
|
||||
Width="100"/>
|
||||
<DataGridTextColumn Header="平均值"
|
||||
Binding="{Binding AverageText}"
|
||||
Width="100"/>
|
||||
<DataGridTextColumn Header="斜率 (/s)"
|
||||
Binding="{Binding SlopeText}"
|
||||
Width="120">
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="ToolTip" Value="线性回归斜率(值/秒)"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="积分 (·s)"
|
||||
Binding="{Binding IntegralText}"
|
||||
Width="130">
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="ToolTip" Value="梯形法积分值(单位×秒)"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="5,8,5,12">
|
||||
<Button Content="关闭"
|
||||
Width="80"
|
||||
Command="{Binding CloseCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
12
CurveModule/Views/Dialogs/CurveStatisticsView.xaml.cs
Normal file
12
CurveModule/Views/Dialogs/CurveStatisticsView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace CurveModule.Views.Dialogs
|
||||
{
|
||||
public partial class CurveStatisticsView : UserControl
|
||||
{
|
||||
public CurveStatisticsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,37 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存全局配置
|
||||
/// </summary>
|
||||
public static void SaveGlobalConfig(GlobalConfig config)
|
||||
{
|
||||
if (config == null) return;
|
||||
|
||||
lock (_fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(config.SystemPath))
|
||||
Directory.CreateDirectory(config.SystemPath);
|
||||
|
||||
string configPath = Path.Combine(config.SystemPath, "GlobalConfig.json");
|
||||
|
||||
string json = JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
});
|
||||
|
||||
File.WriteAllText(configPath, json);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD)。
|
||||
/// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。
|
||||
|
||||
17
UIShare/GlobalVariable/GlobalConfig.cs
Normal file
17
UIShare/GlobalVariable/GlobalConfig.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.GlobalVariable
|
||||
{
|
||||
public class GlobalConfig
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string SystemPath { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ACP");
|
||||
public string DefaultCSVFilePath { get; set; } = "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user