From dced00183d0982f6422929ccc664cff5e8ff90da Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Fri, 13 Mar 2026 10:42:41 +0800 Subject: [PATCH] =?UTF-8?q?20260313=20=E5=AE=A2=E6=88=B7=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CSI-H238M/CSI-H238M/App.xaml.cs | 4 +- CSI-H238M/CSI-H238M/Models/AppConfig.cs | 2 + CSI-H238M/CSI-H238M/Models/Model.cs | 34 +++ .../CSI-H238M/Services/PdfReportService.cs | 34 ++- CSI-H238M/CSI-H238M/ViewModels/ViewModel.cs | 207 +++++++++++++++++- CSI-H238M/CSI-H238M/Views/HistoryPage.xaml | 8 +- CSI-H238M/CSI-H238M/Views/SettingsPage.xaml | 38 +++- CSI-H238M/CSI-H238M/Views/TestPage.xaml | 14 +- 8 files changed, 310 insertions(+), 31 deletions(-) diff --git a/CSI-H238M/CSI-H238M/App.xaml.cs b/CSI-H238M/CSI-H238M/App.xaml.cs index 12b5256..9bd3fe8 100644 --- a/CSI-H238M/CSI-H238M/App.xaml.cs +++ b/CSI-H238M/CSI-H238M/App.xaml.cs @@ -107,7 +107,9 @@ namespace CSI_H238M // 保存配置(如果需要) try { - _config?.Save(AppConfig.GetDefaultConfigPath()); + string configPath = AppConfig.GetDefaultConfigPath(); + _config = AppConfig.Load(configPath); + _config?.Save(configPath); } catch { diff --git a/CSI-H238M/CSI-H238M/Models/AppConfig.cs b/CSI-H238M/CSI-H238M/Models/AppConfig.cs index d8e126d..09e86ec 100644 --- a/CSI-H238M/CSI-H238M/Models/AppConfig.cs +++ b/CSI-H238M/CSI-H238M/Models/AppConfig.cs @@ -42,6 +42,8 @@ namespace COFTester.Models /// public string Language { get; set; } = "zh-CN"; + public string ForceDisplayUnit { get; set; } = "N"; + /// /// 自動保存測試結果 /// diff --git a/CSI-H238M/CSI-H238M/Models/Model.cs b/CSI-H238M/CSI-H238M/Models/Model.cs index 93dc880..6fdf704 100644 --- a/CSI-H238M/CSI-H238M/Models/Model.cs +++ b/CSI-H238M/CSI-H238M/Models/Model.cs @@ -240,8 +240,10 @@ namespace COFTester.Models /// public class TestResult : INotifyPropertyChanged { + private const double StandardGravity = 9.80665; private bool _isVisible = true; private string _sampleNumber = string.Empty; + private string _forceDisplayUnit = "N"; public Guid TestId { get; set; } = Guid.NewGuid(); // 測試唯一ID public DateTime TestDateTime { get; set; } = DateTime.Now; // 測試時間 @@ -267,6 +269,31 @@ namespace COFTester.Models /// /// 试样编号 /// + public string ForceDisplayUnit + { + get => _forceDisplayUnit; + set + { + string normalizedUnit = string.Equals(value, "g", StringComparison.OrdinalIgnoreCase) ? "g" : "N"; + if (_forceDisplayUnit == normalizedUnit) + { + return; + } + + _forceDisplayUnit = normalizedUnit; + OnPropertyChanged(); + OnPropertyChanged(nameof(DisplayedMaxForce)); + OnPropertyChanged(nameof(DisplayedAvgKineticForce)); + OnPropertyChanged(nameof(DisplayedPeelStrength25mm)); + } + } + + public double DisplayedMaxForce => ConvertForceForDisplay(MaxForce); + + public double DisplayedAvgKineticForce => ConvertForceForDisplay(AvgKineticForce); + + public double DisplayedPeelStrength25mm => ConvertForceForDisplay(PeelStrength_N_25mm); + public string SampleNumber { get => _sampleNumber; @@ -282,6 +309,13 @@ namespace COFTester.Models set { _isVisible = value; OnPropertyChanged(); } } + private double ConvertForceForDisplay(double forceInNewton) + { + return ForceDisplayUnit == "g" + ? forceInNewton * 1000.0 / StandardGravity + : forceInNewton; + } + public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? name = null) { diff --git a/CSI-H238M/CSI-H238M/Services/PdfReportService.cs b/CSI-H238M/CSI-H238M/Services/PdfReportService.cs index 9904c21..e529f30 100644 --- a/CSI-H238M/CSI-H238M/Services/PdfReportService.cs +++ b/CSI-H238M/CSI-H238M/Services/PdfReportService.cs @@ -166,6 +166,7 @@ namespace COFTester.Services /// public class PdfReportService { + private const double StandardGravity = 9.80665; private const double PageWidth = 595; // A4宽度(点) private const double PageHeight = 842; // A4高度(点) private const double MarginLeft = 50; @@ -195,10 +196,11 @@ namespace COFTester.Services /// 测试记录 /// 测试参数 /// 是否使用中文(默认根据当前语言) - public void GenerateReport(string filePath, IEnumerable testRecords, TestParameters parameters, bool? isChinese = null) + public void GenerateReport(string filePath, IEnumerable testRecords, TestParameters parameters, string forceDisplayUnit = "N", bool? isChinese = null) { // 如果未指定语言,根据当前语言资源判断 bool useChinese = isChinese ?? (Resources.LanguageResources.Instance.CurrentLanguage == "zh-CN"); + string normalizedForceUnit = NormalizeForceDisplayUnit(forceDisplayUnit); if (testRecords == null || !testRecords.Any()) throw new ArgumentException(useChinese ? "测试记录不能为空" : "Test records cannot be empty"); @@ -232,13 +234,13 @@ namespace COFTester.Services yPosition = DrawHeader(gfx, yPosition, useChinese); System.Diagnostics.Debug.WriteLine("标题已绘制"); - yPosition = DrawTestInfo(gfx, yPosition, parameters, useChinese); + yPosition = DrawTestInfo(gfx, yPosition, parameters, normalizedForceUnit, useChinese); System.Diagnostics.Debug.WriteLine("测试信息已绘制"); yPosition = DrawStatistics(gfx, yPosition, testRecords, useChinese); System.Diagnostics.Debug.WriteLine("统计信息已绘制"); - yPosition = DrawDetailedResults(gfx, yPosition, testRecords, document, gfx, page, useChinese); + yPosition = DrawDetailedResults(gfx, yPosition, testRecords, normalizedForceUnit, document, gfx, page, useChinese); System.Diagnostics.Debug.WriteLine("详细结果已绘制"); // 保存文档 @@ -283,7 +285,7 @@ namespace COFTester.Services /// /// 绘制测试信息 /// - private double DrawTestInfo(XGraphics gfx, double yPosition, TestParameters parameters, bool isChinese) + private double DrawTestInfo(XGraphics gfx, double yPosition, TestParameters parameters, string forceDisplayUnit, bool isChinese) { string fontFamily = GetFontFamily(isChinese); XFont sectionFont = new XFont(fontFamily, 14, XFontStyleEx.Bold); @@ -299,6 +301,7 @@ namespace COFTester.Services { $"测试标准: {parameters.Standard}", $"滑块质量: {parameters.SledMass:F1} g", + $"力值单位: {forceDisplayUnit}", $"测试速度: {parameters.TestSpeed:F1} mm/min", $"测试行程: {parameters.TestStroke:F1} mm", $"操作员: {parameters.Operator}" @@ -306,6 +309,7 @@ namespace COFTester.Services { $"Test Standard: {parameters.Standard}", $"Sled Mass: {parameters.SledMass:F1} g", + $"Force Unit: {forceDisplayUnit}", $"Test Speed: {parameters.TestSpeed:F1} mm/min", $"Test Stroke: {parameters.TestStroke:F1} mm", $"Operator: {parameters.Operator}" @@ -403,7 +407,7 @@ namespace COFTester.Services /// 绘制详细测试结果 /// private double DrawDetailedResults(XGraphics gfx, double yPosition, IEnumerable testRecords, - PdfDocument document, XGraphics currentGfx, PdfPage currentPage, bool isChinese) + string forceDisplayUnit, PdfDocument document, XGraphics currentGfx, PdfPage currentPage, bool isChinese) { string fontFamily = GetFontFamily(isChinese); XFont sectionFont = new XFont(fontFamily, 14, XFontStyleEx.Bold); @@ -434,8 +438,8 @@ namespace COFTester.Services // 表头 string[] headers = isChinese ? - new[] { "编号", "测试时间", "静摩擦", "动摩擦", "最大力", "平均力" } : - new[] { "No.", "Test Time", "Static", "Kinetic", "Max F", "Avg F" }; + new[] { "编号", "测试时间", "静摩擦", "动摩擦", $"最大力({forceDisplayUnit})", $"平均力({forceDisplayUnit})" } : + new[] { "No.", "Test Time", "Static", "Kinetic", $"Max F ({forceDisplayUnit})", $"Avg F ({forceDisplayUnit})" }; double currentX = tableX; for (int i = 0; i < headers.Length; i++) @@ -474,8 +478,8 @@ namespace COFTester.Services record.TestDateTime.ToString("MM-dd HH:mm"), record.StaticCOF.ToString("F4"), record.KineticCOF.ToString("F4"), - record.MaxForce.ToString("F2") + " N", - record.AvgKineticForce.ToString("F2") + " N" + ConvertForceForDisplay(record.MaxForce, forceDisplayUnit).ToString("F2") + " " + forceDisplayUnit, + ConvertForceForDisplay(record.AvgKineticForce, forceDisplayUnit).ToString("F2") + " " + forceDisplayUnit }; for (int i = 0; i < rowData.Length; i++) @@ -555,6 +559,18 @@ namespace COFTester.Services new XRect(0, footerY, PageWidth, 20), XStringFormats.TopCenter); } + + private static string NormalizeForceDisplayUnit(string? unit) + { + return string.Equals(unit, "g", StringComparison.OrdinalIgnoreCase) ? "g" : "N"; + } + + private static double ConvertForceForDisplay(double forceInNewton, string forceDisplayUnit) + { + return NormalizeForceDisplayUnit(forceDisplayUnit) == "g" + ? forceInNewton * 1000.0 / StandardGravity + : forceInNewton; + } /// /// 计算标准差 diff --git a/CSI-H238M/CSI-H238M/ViewModels/ViewModel.cs b/CSI-H238M/CSI-H238M/ViewModels/ViewModel.cs index 66fcdbf..1256cf0 100644 --- a/CSI-H238M/CSI-H238M/ViewModels/ViewModel.cs +++ b/CSI-H238M/CSI-H238M/ViewModels/ViewModel.cs @@ -9,6 +9,7 @@ using System.Windows.Threading; using System.Windows; using System.Threading.Tasks; using System.Threading; +using System.Globalization; using COFTester.Models; using COFTester.Services; using COFTester.Resources; @@ -33,6 +34,7 @@ namespace COFTester.ViewModels private bool _isUiDataUpdateQueued; private long _lastPlotRefreshTick; private const int PlotRefreshIntervalMs = 100; + private const double StandardGravity = 9.80665; private double _currentForce; private double _currentDisp; @@ -52,6 +54,8 @@ namespace COFTester.ViewModels private bool _stopRequestedByUser = false; private bool _acceptIncomingTestData = false; private int _testSessionId = 0; + private string _selectedSledMassPreset = "200"; + private string _forceDisplayUnit = "N"; private string _selectedDirection = ""; // 选中的方向:Up/Down/Right/Left private string _resetButtonText; // 复位按钮文本 private string _testButtonText; // 测试按钮文本 @@ -100,6 +104,9 @@ namespace COFTester.ViewModels UpdateCustomParametersCommand = new AsyncRelayCommand(UpdateCustomParametersAsync, () => IsConnected && SelectedStandard == "Custom"); Parameters = _config.DefaultTestParameters ?? new TestParameters(); + Parameters.PropertyChanged += OnParametersPropertyChanged; + _forceDisplayUnit = NormalizeForceDisplayUnit(_config.ForceDisplayUnit); + SyncSledMassPresetFromValue(); TestRecords = new ObservableCollection(); TestRecords.CollectionChanged += (s, e) => { @@ -289,6 +296,69 @@ namespace COFTester.ViewModels /// 语言资源实例 /// public LanguageResources Lang => LanguageResources.Instance; + + public string ForceDisplayUnit + { + get => _forceDisplayUnit; + set + { + var normalizedUnit = NormalizeForceDisplayUnit(value); + if (_forceDisplayUnit == normalizedUnit) + { + return; + } + + _forceDisplayUnit = normalizedUnit; + _config.ForceDisplayUnit = normalizedUnit; + OnPropertyChanged(); + NotifyForceDisplayChanged(); + PersistSettings(); + RefreshPlotForDisplaySettings(); + } + } + + public string ForceDisplayLabel => + Lang.CurrentLanguage == "zh-CN" ? $"力值 ({ForceDisplayUnit})" : $"Force ({ForceDisplayUnit})"; + + public string ForceAxisLabel => + Lang.CurrentLanguage == "zh-CN" ? $"力值 ({ForceDisplayUnit})" : $"Force ({ForceDisplayUnit})"; + + public string AvgPeelForceLabel => + Lang.CurrentLanguage == "zh-CN" ? $"平均剥离力 ({ForceDisplayUnit})" : $"Avg Peel Force ({ForceDisplayUnit})"; + + public string PeelStrengthLabel => + Lang.CurrentLanguage == "zh-CN" ? $"剥离强度 ({ForceDisplayUnit}/25mm)" : $"Peel Strength ({ForceDisplayUnit}/25mm)"; + + public string MaxForceLabel => + Lang.CurrentLanguage == "zh-CN" ? $"最大力值 ({ForceDisplayUnit})" : $"Max Force ({ForceDisplayUnit})"; + + public string AvgForceDisplayLabel => + Lang.CurrentLanguage == "zh-CN" ? $"平均力值 ({ForceDisplayUnit})" : $"Avg Force ({ForceDisplayUnit})"; + + public string SelectedSledMassPreset + { + get => _selectedSledMassPreset; + set + { + var normalizedPreset = string.IsNullOrWhiteSpace(value) ? "Custom" : value; + if (_selectedSledMassPreset == normalizedPreset) + { + return; + } + + _selectedSledMassPreset = normalizedPreset; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsCustomSledMass)); + + if (double.TryParse(normalizedPreset, NumberStyles.Float, CultureInfo.InvariantCulture, out double presetMass)) + { + Parameters.SledMass = presetMass; + PersistSettings(); + } + } + } + + public bool IsCustomSledMass => SelectedSledMassPreset == "Custom"; /// /// 初始化 ScottPlot 图表 @@ -310,7 +380,7 @@ namespace COFTester.ViewModels // 设置坐标轴标签(使用已设置的中文字体) _wpfPlot.Plot.XLabel(Lang.DisplacementAxis); - _wpfPlot.Plot.YLabel(Lang.ForceAxis); + _wpfPlot.Plot.YLabel(ForceAxisLabel); // 设置Y轴最小值为0 _wpfPlot.Plot.Axes.SetLimitsY(0, 10); @@ -389,7 +459,7 @@ namespace COFTester.ViewModels else { var xData = _realTimePoints.Select(p => p.Displacement).ToArray(); - var yData = _realTimePoints.Select(p => p.Force).ToArray(); + var yData = _realTimePoints.Select(p => ConvertForceForDisplay(p.Force)).ToArray(); // 创建新的散点图 _scatterPlot = _wpfPlot.Plot.Add.Scatter(xData, yData); @@ -509,9 +579,16 @@ namespace COFTester.ViewModels public double CurrentForce { get => _currentForce; - set { _currentForce = value; OnPropertyChanged(); } + set + { + _currentForce = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(DisplayedCurrentForce)); + } } + public double DisplayedCurrentForce => ConvertForceForDisplay(CurrentForce); + public double CurrentDisp { get => _currentDisp; @@ -545,9 +622,21 @@ namespace COFTester.ViewModels public TestResult? LatestResult { get => _latestResult; - set { _latestResult = value; OnPropertyChanged(); } + set + { + _latestResult = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(DisplayedLatestAvgKineticForce)); + OnPropertyChanged(nameof(DisplayedLatestPeelStrength)); + } } + public double? DisplayedLatestAvgKineticForce => + LatestResult == null ? null : ConvertForceForDisplay(LatestResult.AvgKineticForce); + + public double? DisplayedLatestPeelStrength => + LatestResult == null ? null : ConvertForceForDisplay(LatestResult.PeelStrength_N_25mm); + public string StatusMessage { get => _statusMessage; @@ -688,6 +777,7 @@ namespace COFTester.ViewModels }; // 添加到测试记录集合 + result.ForceDisplayUnit = ForceDisplayUnit; TestRecords.Add(result); LatestResult = result; @@ -1552,7 +1642,7 @@ namespace COFTester.ViewModels return; var xData = result.RawData.Select(p => p.Displacement).ToArray(); - var yData = result.RawData.Select(p => p.Force).ToArray(); + var yData = result.RawData.Select(p => ConvertForceForDisplay(p.Force)).ToArray(); var scatter = _wpfPlot.Plot.Add.Scatter(xData, yData); scatter.LineWidth = 2; @@ -1656,7 +1746,7 @@ namespace COFTester.ViewModels try { var pdfService = new Services.PdfReportService(); - pdfService.GenerateReport(filePath, TestRecords, Parameters); + pdfService.GenerateReport(filePath, TestRecords, Parameters, ForceDisplayUnit); } catch (Exception ex) { @@ -1689,9 +1779,16 @@ namespace COFTester.ViewModels if (_wpfPlot != null) { _wpfPlot.Plot.XLabel(Lang.DisplacementAxis); - _wpfPlot.Plot.YLabel(Lang.ForceAxis); + _wpfPlot.Plot.YLabel(ForceAxisLabel); _wpfPlot.Refresh(); } + + OnPropertyChanged(nameof(ForceDisplayLabel)); + OnPropertyChanged(nameof(ForceAxisLabel)); + OnPropertyChanged(nameof(AvgPeelForceLabel)); + OnPropertyChanged(nameof(PeelStrengthLabel)); + OnPropertyChanged(nameof(MaxForceLabel)); + OnPropertyChanged(nameof(AvgForceDisplayLabel)); // 更新当前状态消息 if (_statusMessage == "系统就绪" || _statusMessage == "System Ready") @@ -1711,6 +1808,101 @@ namespace COFTester.ViewModels TestButtonText = Lang.StartTest; ResetButtonText = Lang.ResetSystem; } + + PersistSettings(); + } + + private void OnParametersPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TestParameters.SledMass)) + { + SyncSledMassPresetFromValue(); + PersistSettings(); + } + } + + private void SyncSledMassPresetFromValue() + { + string preset = Math.Abs(Parameters.SledMass - 200.0) < 0.0001 ? "200" + : Math.Abs(Parameters.SledMass - 300.0) < 0.0001 ? "300" + : Math.Abs(Parameters.SledMass - 500.0) < 0.0001 ? "500" + : "Custom"; + + if (_selectedSledMassPreset != preset) + { + _selectedSledMassPreset = preset; + OnPropertyChanged(nameof(SelectedSledMassPreset)); + OnPropertyChanged(nameof(IsCustomSledMass)); + } + } + + private double ConvertForceForDisplay(double forceInNewton) + { + return ForceDisplayUnit == "g" + ? forceInNewton * 1000.0 / StandardGravity + : forceInNewton; + } + + private string NormalizeForceDisplayUnit(string? unit) + { + return string.Equals(unit, "g", StringComparison.OrdinalIgnoreCase) ? "g" : "N"; + } + + private void NotifyForceDisplayChanged() + { + OnPropertyChanged(nameof(DisplayedCurrentForce)); + OnPropertyChanged(nameof(DisplayedLatestAvgKineticForce)); + OnPropertyChanged(nameof(DisplayedLatestPeelStrength)); + OnPropertyChanged(nameof(ForceDisplayLabel)); + OnPropertyChanged(nameof(ForceAxisLabel)); + OnPropertyChanged(nameof(AvgPeelForceLabel)); + OnPropertyChanged(nameof(PeelStrengthLabel)); + OnPropertyChanged(nameof(MaxForceLabel)); + OnPropertyChanged(nameof(AvgForceDisplayLabel)); + + foreach (var record in TestRecords) + { + record.ForceDisplayUnit = ForceDisplayUnit; + } + } + + private void RefreshPlotForDisplaySettings() + { + if (_wpfPlot == null) + { + return; + } + + _scatterPlot = null; + _testCurves.Clear(); + InitializeScottPlot(); + + foreach (var record in TestRecords) + { + AddCurveToPlot(record); + } + + if (_realTimePoints.Count > 0) + { + UpdateScottPlot(); + } + + UpdateAllCurvesVisibility(); + } + + private void PersistSettings() + { + try + { + _config.DefaultTestParameters = Parameters; + _config.Language = Lang.CurrentLanguage; + _config.ForceDisplayUnit = ForceDisplayUnit; + _config.Save(AppConfig.GetDefaultConfigPath()); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[ViewModel] 保存配置失败: {ex.Message}"); + } } private void OpenConfig() @@ -1865,6 +2057,7 @@ namespace COFTester.ViewModels _daqService.DataReceived -= OnDataReceived; _daqService.TestFinished -= OnTestFinished; _daqService.ErrorOccurred -= OnErrorOccurred; + Parameters.PropertyChanged -= OnParametersPropertyChanged; // 停止測試 if (_isTesting) diff --git a/CSI-H238M/CSI-H238M/Views/HistoryPage.xaml b/CSI-H238M/CSI-H238M/Views/HistoryPage.xaml index ebe66cb..ebb5527 100644 --- a/CSI-H238M/CSI-H238M/Views/HistoryPage.xaml +++ b/CSI-H238M/CSI-H238M/Views/HistoryPage.xaml @@ -69,17 +69,17 @@ - + - + - + - + diff --git a/CSI-H238M/CSI-H238M/Views/SettingsPage.xaml b/CSI-H238M/CSI-H238M/Views/SettingsPage.xaml index 1c5e7b7..75d8f5d 100644 --- a/CSI-H238M/CSI-H238M/Views/SettingsPage.xaml +++ b/CSI-H238M/CSI-H238M/Views/SettingsPage.xaml @@ -53,17 +53,49 @@ - - + - + + + + + + + + + + + + + + + + diff --git a/CSI-H238M/CSI-H238M/Views/TestPage.xaml b/CSI-H238M/CSI-H238M/Views/TestPage.xaml index 8c2b28f..7e65436 100644 --- a/CSI-H238M/CSI-H238M/Views/TestPage.xaml +++ b/CSI-H238M/CSI-H238M/Views/TestPage.xaml @@ -88,13 +88,13 @@ - + - + - + @@ -151,13 +151,13 @@ - - + + - - + +