using MembranePoreTester.Communication; using MembranePoreTester.Helpers; using MembranePoreTester.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Win32; using OxyPlot; using OxyPlot.Axes; using OxyPlot.Legends; using OxyPlot.Series; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Net; using System.Windows; using System.Windows.Input; namespace MembranePoreTester.ViewModels { public class PoreDistributionViewModel : ViewModelBase, IStationViewModel { #region public class PressureModeItem { public string Text { get; set; } public int Value { get; set; } public override string ToString() { return Text; // 直接返回要显示的文本 } } private PressureModeItem _selectedPressureMode; private List _pressureModeList; public List PressureModeList => _pressureModeList ??= new List { new PressureModeItem { Text = "小流量", Value = 1 }, new PressureModeItem { Text = "大流量", Value = 0 } }; public PressureModeItem SelectedPressureMode { get => _selectedPressureMode; set { if (SetProperty(ref _selectedPressureMode, value)) { Task.Run(async () => await WriteFlowModeAsync(value?.Text ?? "小流量")); } } } #endregion // 添加字段 private System.Windows.Threading.DispatcherTimer _autoCollectTimer; private bool _isCollecting = false; // 添加公共方法供工位调用 //public void StartCollecting() //{ // System.Diagnostics.Debug.WriteLine($"StartCollecting 工位{StationId}, IsActive={IsActive}"); // //if (!IsActive) // //{ // // // 如果当前不在孔分布界面,不启动采集 // // return; // //} // if (_isCollecting) return; // _isCollecting = true; // _autoCollectTimer = new System.Windows.Threading.DispatcherTimer // { // Interval = TimeSpan.FromSeconds(1) // }; // _autoCollectTimer.Tick += AutoCollectTimer_Tick; // _autoCollectTimer.Start(); //} public void StartCollecting() { // 不再依赖 IsActive,定时器始终启动 if (_isCollecting) return; _isCollecting = true; _autoCollectTimer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _autoCollectTimer.Tick += AutoCollectTimer_Tick; _autoCollectTimer.Start(); } public void StopCollecting() { _isCollecting = false; _autoCollectTimer?.Stop(); _autoCollectTimer = null; } private async void AutoCollectTimer_Tick(object sender, EventArgs e) { //if (!IsActive) return; try { // 1. 读取当前压力 float rawPressure = await _plcService.ReadPressureAsync(StationId); double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2); // 2. 读取当前工位的加压上限(实时) ushort upperLimitAddress = StationId == 1 ? _plcConfig.PressureUpperLimit : StationId == 2 ? _plcConfig.PressureUpperLimit2 : _plcConfig.PressureUpperLimit3; double pressureUpperLimit = await _plcService.ReadFloatAsync(upperLimitAddress); // 3. 如果压力已达到或超过上限,停止采集 if (pressure >= pressureUpperLimit * 1000 - 10 * 1000 && pressure > 0) { StopCollecting(); return; // 不再添加当前数据点 } // 2. 读取当前模式对应的流量 double flow = 0; if (TestMode.Contains("湿膜")) { float rawFlow = await _plcService.ReadWetFlowAsync(StationId); flow = Math.Round(ConvertFlowByMode(rawFlow), 3); } else { float rawFlow = await _plcService.ReadDryFlowAsync(StationId); flow = Math.Round(ConvertFlowByMode(rawFlow), 3); } float Pressure = await _plcService.ReadPressureAsync(StationId); CurrentPressure = Math.Round(rawPressure, 2); if (pressure <= 0 || flow <= 0) return; CurrentFlow = flow; // 3. 查找是否已有相同压力点(按两位小数匹配),有则更新对应流量,否则新增 var existing = Record.DataPoints.FirstOrDefault(p => Math.Abs(p.Pressure - pressure) < 0.001); if (existing != null) { if (TestMode != null && TestMode.Contains("湿膜")) existing.WetFlow = flow; else existing.DryFlow = flow; } else { var newPoint = new Models.DataPoint { Pressure = Math.Round(pressure, 2), WetFlow = TestMode != null && TestMode.Contains("湿膜") ? flow : 0, DryFlow = TestMode != null && TestMode.Contains("干膜") ? flow : 0 }; Record.DataPoints.Add(newPoint); } // 4. 刷新曲线 UpdatePlot(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}"); } } /// /// 清空所有数据点,重置表格和曲线 /// public void ClearData() { // 清空数据点集合 Record.DataPoints.Clear(); PlotModel = new PlotModel(); // 重置曲线 UpdatePlot(); // 重置计算结果 AveragePoreSize = 0; RangePercentage = 0; // 可选:显示提示 System.Diagnostics.Debug.WriteLine("孔分布数据已清空"); } private bool _isActive; public bool IsActive { get => _isActive; set => SetProperty(ref _isActive, value); } private PoreDistributionRecord _record = new(); private TestLiquid _selectedLiquid; private bool _isCustomLiquid; private double _customSurfaceTension = 30.0; private double _lowerPore = 0.05; private double _upperPore = 0.65; private double _rangePercentage; // 添加字段 private readonly IPlcService _plcService; private readonly PlcConfiguration _plcConfig; private Models.DataPoint _selectedDataPoint; // 添加属性 public Models.DataPoint SelectedDataPoint { get => _selectedDataPoint; set => SetProperty(ref _selectedDataPoint, value); } public ICommand ReadPlcCommand { get; } public PoreDistributionRecord Record { get => _record; set { if (SetProperty(ref _record, value)) { // 当 Record 替换时(例如从数据库加载),重新订阅其 DataPoints 集合 HookDataPointsCollection(_record?.DataPoints); // 同步液体选择 if (_record?.Liquid != null) { var matchedLiquid = Liquids.FirstOrDefault(l => l.Name == _record.Liquid.Name); SelectedLiquid = matchedLiquid ?? _record.Liquid; } else { SelectedLiquid = Liquids.FirstOrDefault(); } } } } public IReadOnlyList Liquids => TestLiquid.Predefined; public List PressureUnits => new() { "Pa", "cmHg", "psi" }; public List MembraneTypes => new() { "平板膜", "中空纤维膜" }; public TestLiquid SelectedLiquid { get => _selectedLiquid; set { if (SetProperty(ref _selectedLiquid, value)) { Record.Liquid = value; } } } public bool IsCustomLiquid { get => _isCustomLiquid; set { if (SetProperty(ref _isCustomLiquid, value)) { UpdateCustomLiquid(); } } } public double CustomSurfaceTension { get => _customSurfaceTension; set { if (SetProperty(ref _customSurfaceTension, value)) { UpdateCustomLiquid(); } } } public double LowerPore { get => _lowerPore; set => SetProperty(ref _lowerPore, value); } public double UpperPore { get => _upperPore; set => SetProperty(ref _upperPore, value); } public double RangePercentage { get => _rangePercentage; set => SetProperty(ref _rangePercentage, value); } public ICommand AddDataPointCommand { get; } public ICommand RemoveDataPointCommand { get; } public ICommand CalculateCommand { get; } public ICommand GenerateReportCommand { get; } private System.Windows.Threading.DispatcherTimer _timer; // 添加定时器字段 public PoreDistributionViewModel() { _plcService = App.PlcService; _plcConfig = App.PlcConfig; AddDataPointCommand = new RelayCommand(AddDataPoint); RemoveDataPointCommand = new RelayCommand(RemoveDataPoint, () => Record.DataPoints.Any()); CalculateCommand = new RelayCommand(Calculate); GenerateReportCommand = new RelayCommand(GenerateReport); SelectedLiquid = Liquids[0]; Record.SampleType = "中空纤维膜"; Record.PressureUnit = PressureUnits[0]; // 默认 "Pa" ReadPlcCommand = new RelayCommand(async () => await ReadPlcAsync()); SaveCommand = new RelayCommand(SaveToDatabase); ExportCommand = new RelayCommand(ExportToExcel); OpenFlowCalibCommand = new RelayCommand(OpenFlowCalibration); OpenFlowCalibCommand2 = new RelayCommand(OpenFlowCalibration2); // 订阅集合变更并为每个项订阅属性变化,保证 DataGrid 和 Plot 在项内容变化时同步刷新 HookDataPointsCollection(Record.DataPoints); ClearAllCommand = new RelayCommand(ClearAllData); RemoveDataPointCommand = new RelayCommand(RemoveSelectedDataPoint, () => SelectedDataPoint != null); // 启动定时器,每秒读取一次 _timer = new System.Windows.Threading.DispatcherTimer(); _timer.Interval = TimeSpan.FromSeconds(1); _timer.Tick += async (s, e) => { if (StationId > 0 && !IsDisposed) { await ReadSpeedRateAsync(); } }; _timer.Start(); // 延迟2秒后读取,确保连接稳定 Task.Delay(2000).ContinueWith(async _ => { await ReadPressureModeAsync(); }, TaskScheduler.Default); } private void ClearAllData() { ClearData(); } private string _speedRate1; public string SpeedRate1 { get => _speedRate1; set => SetProperty(ref _speedRate1, value); } private async Task ReadSpeedRateAsync() { try { float speedRate = 0; ushort address = 0; switch (StationId) { case 1: address = _plcConfig.HPCoeff11; speedRate = await _plcService.ReadFloatAsync(address); SpeedRate1 = speedRate.ToString("F3"); break; case 2: address = _plcConfig.HPCoeff12; speedRate = await _plcService.ReadFloatAsync(address); SpeedRate1 = speedRate.ToString("F3"); break; case 3: address = _plcConfig.HPCoeff13; speedRate = await _plcService.ReadFloatAsync(address); SpeedRate1 = speedRate.ToString("F3"); break; } // 如果需要刷新 Record 中的绑定(可选) OnPropertyChanged(nameof(SpeedRate1)); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取加压速率失败: {ex.Message}"); } } private async Task ReadPressureModeAsync() { await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () => { try { ushort[] values = await _plcService.ReadHoldingRegistersAsync(StationId == 1 ? _plcConfig.FlowModeRegister : StationId == 2 ? _plcConfig.FlowModeRegister2 : _plcConfig.FlowModeRegister3, 1); ushort val = values[0]; string newValue = val == 0 ? "大流量" : "小流量"; Application.Current.Dispatcher.Invoke(() => { // 更新选中项 HighLowPressure = newValue; }); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取流量模式失败: {ex.Message}"); } }); } public string HighLowPressure { get => _selectedPressureMode?.Text ?? "小流量"; set { var mode = PressureModeList.FirstOrDefault(m => m.Text == value); if (mode != null) { SelectedPressureMode = mode; } } } private async Task ReadPlcAsync() { await SafeExecuteAsync($"ManualRead_Station{StationId}", async () => { // 始终读取压力 float rawPressure = await _plcService.ReadPressureAsync(StationId); double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2); if (SelectedDataPoint != null) { // 更新选中行 SelectedDataPoint.Pressure = pressure; if (TestMode == "湿膜") { float rawWet = await _plcService.ReadWetFlowAsync(StationId); SelectedDataPoint.WetFlow = ConvertFlowByMode(rawWet); } else { float rawDry = await _plcService.ReadDryFlowAsync(StationId); SelectedDataPoint.DryFlow = ConvertFlowByMode(rawDry); } } else { // 新增一行 var newPoint = new Models.DataPoint { Pressure = pressure }; if (TestMode == "湿膜") { float rawWet = await _plcService.ReadWetFlowAsync(StationId); newPoint.WetFlow = ConvertFlowByMode(rawWet); } else { float rawDry = await _plcService.ReadDryFlowAsync(StationId); newPoint.DryFlow = ConvertFlowByMode(rawDry); } Record.DataPoints.Add(newPoint); } }); } private void AddDataPoint() { Record.DataPoints.Add(new Models.DataPoint()); } private void RemoveDataPoint() { if (Record.DataPoints.Any()) Record.DataPoints.RemoveAt(Record.DataPoints.Count - 1); } private void UpdateCustomLiquid() { if (IsCustomLiquid) { Record.Liquid = new TestLiquid { Name = "自定义", SurfaceTension = CustomSurfaceTension }; } } // 监听 DataPoints 集合与其子项的属性变更,确保 UI 同步更新 private void HookDataPointsCollection(ObservableCollection collection) { if (collection == null) return; // 先解绑已有(避免重复订阅) collection.CollectionChanged -= DataPoints_CollectionChanged; collection.CollectionChanged += DataPoints_CollectionChanged; // 为已有项订阅 PropertyChanged foreach (var item in collection) { if (item is INotifyPropertyChanged inpc) { inpc.PropertyChanged -= DataPoint_PropertyChanged; inpc.PropertyChanged += DataPoint_PropertyChanged; } } } private void DataPoints_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (var old in e.OldItems) { if (old is INotifyPropertyChanged inpc) inpc.PropertyChanged -= DataPoint_PropertyChanged; } } if (e.NewItems != null) { foreach (var nw in e.NewItems) { if (nw is INotifyPropertyChanged inpc) { inpc.PropertyChanged -= DataPoint_PropertyChanged; inpc.PropertyChanged += DataPoint_PropertyChanged; } } } // 集合结构变化时刷新曲线 UpdatePlot(); } private void DataPoint_PropertyChanged(object? sender, PropertyChangedEventArgs e) { // 某个数据点属性改变时也刷新曲线 UpdatePlot(); } // 添加私有字段和公共属性 private double _averagePoreSize; public double AveragePoreSize { get => _averagePoreSize; set => SetProperty(ref _averagePoreSize, value); } //修改 Calculate 方法: private void Calculate() { // 先清洗数据并替换到绑定的集合中,确保DataGrid和曲线显示清洗后的数据 var originalPoints = Record.DataPoints.ToList(); System.Diagnostics.Debug.WriteLine("=== 清洗前的数据点 ==="); foreach (var p in originalPoints) { System.Diagnostics.Debug.WriteLine($"P={p.Pressure}, Wet={p.WetFlow}, Dry={p.DryFlow}"); } //var cleanedPoints = CleanDataPoints(originalPoints); //if (cleanedPoints.Count < 2) //{ // MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。"); // return; //} //int invalidCount = originalPoints.Count - cleanedPoints.Count; // 用清洗后的点替换 ObservableCollection 的内容(触发UI更新) Record.DataPoints.Clear(); foreach (var p in originalPoints) Record.DataPoints.Add(p); // 刷新曲线以显示清洗后的数据 UpdatePlot(); // 使用清洗后的集合进行计算 AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore( Record.DataPoints, Record.PressureUnit, Record.Liquid); RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage( Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore); //if (invalidCount > 0) //{ // MessageBox.Show($"已自动过滤 {invalidCount} 个无效数据点"); // System.Diagnostics.Debug.WriteLine($"已自动过滤 {invalidCount} 个无效数据点"); //} } //private void Calculate() //{ // // 直接使用原始数据,不清洗 // if (Record.DataPoints.Count < 2) // { // MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。"); // return; // } // // 注意:不清洗,也不改变 Record.DataPoints 的顺序(但计算函数内部会自己排序) // // 直接计算 // AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore( // Record.DataPoints, Record.PressureUnit, Record.Liquid); // RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage( // Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore); // // 可选:提示用户当前使用了原始数据(不清洗) // // MessageBox.Show("已使用原始数据(未清洗)进行计算"); //} //private List CleanDataPoints(IEnumerable points) //{ // return points // .Where(p => p.Pressure > 0.001) // 移除压力接近0的点 // .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) // 移除双零流量点 // .GroupBy(p => Math.Round(p.Pressure, 2)) // 按压力四舍五入分组(避免重复压力) // .Select(g => new Models.DataPoint // { // Pressure = g.Key, // WetFlow = g.Max(x => x.WetFlow), // 取最大值(避免0覆盖有效值) // DryFlow = g.Max(x => x.DryFlow) // }) // .OrderBy(p => p.Pressure) // .ToList(); //} private List CleanDataPoints(IEnumerable points) { // 第一步:基础清洗(移除压力≤0、双零流量、合并相同压力取最大流量) var cleaned = points .Where(p => p.Pressure > 0.001) .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) .GroupBy(p => Math.Round(p.Pressure, 2)) .Select(g => new Models.DataPoint { Pressure = g.Key, WetFlow = g.Max(x => x.WetFlow), DryFlow = g.Max(x => x.DryFlow) }) .OrderBy(p => p.Pressure) .ToList(); if (cleaned.Count < 4) return cleaned; // 点数太少,不进行高级剔除 // 第二步:仅对湿膜流量进行异常孤立点剔除(干膜保持原样,因为干膜可能有很多0和少量非零) var result = new List(); const double threshold = 3.0; // 孤立高点阈值(倍),比之前放宽 for (int i = 0; i < cleaned.Count; i++) { var current = cleaned[i]; bool isAbnormal = false; // 仅检查中间的点(非首尾),且只检查湿膜流量 if (i > 0 && i < cleaned.Count - 1) { double prevWet = cleaned[i - 1].WetFlow; double nextWet = cleaned[i + 1].WetFlow; double maxNeighbor = Math.Max(prevWet, nextWet); double minNeighbor = Math.Min(prevWet, nextWet); // 如果当前湿膜流量远大于前后邻居的最大值(孤立高峰),且前后邻居都不为0 if (maxNeighbor > 0 && current.WetFlow > maxNeighbor * threshold) isAbnormal = true; // 如果当前湿膜流量远小于前后邻居的最小值(孤立低谷),一般很少见,但保留 if (minNeighbor > 0 && current.WetFlow < minNeighbor / threshold) isAbnormal = true; } // 不剔除干膜中的非零点(即使孤立也不删) if (!isAbnormal) result.Add(current); } // 如果全部被误判为异常,则回退到原始清洗结果 return result.Count == 0 ? cleaned : result; } //private void GenerateReport() //{ // ReportGenerator.GeneratePoreDistributionReport(Record); //} private void GenerateReport() { // 可选:先清洗数据再生成报告(但不改变原集合) var cleanedPoints = CleanDataPoints(Record.DataPoints); if (cleanedPoints.Count < 2) { MessageBox.Show("有效数据点不足,无法生成报告。"); return; } // 临时替换 Record 中的数据点用于报告(不影响界面) var originalPoints = Record.DataPoints.ToList(); Record.DataPoints.Clear(); foreach (var p in cleanedPoints) Record.DataPoints.Add(p); ReportGenerator.GeneratePoreDistributionReport(Record, PlotModel); // 恢复原始数据 Record.DataPoints.Clear(); foreach (var p in originalPoints) Record.DataPoints.Add(p); //// 清空现有数据 //Record.DataPoints.Clear(); //Random rand = new Random(); //// 生成湿膜数据(模拟压力从0到100,流量先增后减) //for (double pressure = 0; pressure <= 100; pressure += 5) //{ // // 模拟流量曲线:先上升后下降,峰值在40-60之间 // double wetFlow = 0; // if (pressure < 30) // wetFlow = pressure * 1.5; // 上升段 // else if (pressure < 60) // wetFlow = 45 - (pressure - 30) * 0.5; // 下降段 // else // wetFlow = 30 - (pressure - 60) * 0.8; // 继续下降 // // 添加随机波动 (±15%) // wetFlow = wetFlow * (0.85 + rand.NextDouble() * 0.3); // wetFlow = Math.Round(Math.Max(0, wetFlow), 2); // // 干膜数据(稍低于湿膜) // double dryFlow = wetFlow * (0.6 + rand.NextDouble() * 0.3); // dryFlow = Math.Round(dryFlow, 2); // Record.DataPoints.Add(new Models.DataPoint // { // Pressure = Math.Round(pressure, 2), // WetFlow = wetFlow, // DryFlow = dryFlow // }); //} //// 设置一些测试基本参数 //Record.SampleType = "平板膜"; //Record.SampleSpec = "φ47mm"; //Record.RoomTemperature = 22.5; //Record.SoakingTime = 2.0; //Record.Liquid = TestLiquid.Predefined.FirstOrDefault(); //Record.LiquidManufacturer = "测试厂家"; //Record.PressureUnit = "kPa"; //Record.BubblePointPressure = 45.6; //Record.AveragePoreSize = 0.856; //Record.TestDate = DateTime.Now; //Record.Tester = "测试员"; //// 刷新曲线显示 //UpdatePlot(); //ReportGenerator.GeneratePoreDistributionReport(Record, PlotModel); } private void OpenFlowCalibration() { // 弹出确认对话框 var result = MessageBox.Show( "确定要进行流量校准吗?此操作将向PLC写入校准信号。", "确认校准", MessageBoxButton.YesNo, MessageBoxImage.Question); ushort address = new ushort(); switch (StationId) { case 1: { address = _plcConfig.BigFlow1; break; } case 2: { address = _plcConfig.BigFlow2; break; } case 3: { address = _plcConfig.BigFlow3; break; } } Task.Run(async () => { await _plcService.WriteCoilAsync(address, true); //MessageBox.Show("校准成功!"); }); } private void OpenFlowCalibration2() { // 弹出确认对话框 var result = MessageBox.Show( "确定要进行流量校准吗?此操作将向PLC写入校准信号。", "确认校准", MessageBoxButton.YesNo, MessageBoxImage.Question); ushort address = new ushort(); switch (StationId) { case 1: { address = _plcConfig.SmallFlow1; break; } case 2: { address = _plcConfig.SmallFlow2; break; } case 3: { address = _plcConfig.SmallFlow3; break; } } Task.Run(async () => { await _plcService.WriteCoilAsync(address, true); //MessageBox.Show("校准成功!"); }); } private int _stationId; public int StationId { get => _stationId; set => SetProperty(ref _stationId, value); } // 初始化方法:在 MainViewModel 创建 StationItem 后调用,确保 StationId 已设置 public void Initialize(int stationId) { StationId = stationId; // 延迟调用读取模式,确保PLC连接稳定 Task.Run(async () => { await Task.Delay(200); await ReadPressureModeAsync(); }); } public void SaveToDatabase() { var entity = new PoreDistributionEntity { StationId = this.StationId, TestDate = Record.TestDate, Tester = Record.Tester ?? "系统操作员", SampleType = Record.SampleType ?? "膜类型缺省值", SampleSpec = Record.SampleSpec ?? "缺省值", RoomTemperature = Record.RoomTemperature, SoakingTime = Record.SoakingTime, LiquidName = Record.Liquid?.Name, LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0, LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值", PressureUnit = Record.PressureUnit, BubblePointPressure = Record.BubblePointPressure, AveragePoreSize = AveragePoreSize, // 使用计算后的属性 DataPoints = Record.DataPoints.Select(dp => new DataPointEntity { Pressure = dp.Pressure, WetFlow = dp.WetFlow, DryFlow = dp.DryFlow }).ToList() }; using var db = new AppDbContext(); db.PoreDistributionRecords.Add(entity); db.SaveChanges(); MessageBox.Show("保存成功!", "提示"); } public void LoadFromDatabase(int recordId) { using var db = new AppDbContext(); var entity = db.PoreDistributionRecords .Include(p => p.DataPoints) .FirstOrDefault(p => p.Id == recordId); if (entity == null) return; Record.SampleType = entity.SampleType ?? "中空纤维膜"; Record.SampleSpec = entity.SampleSpec; Record.RoomTemperature = entity.RoomTemperature; Record.SoakingTime = entity.SoakingTime; Record.Liquid = TestLiquid.Predefined.FirstOrDefault(l => l.Name == entity.LiquidName) ?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension }; Record.LiquidManufacturer = entity.LiquidManufacturer; Record.PressureUnit = entity.PressureUnit; Record.BubblePointPressure = entity.BubblePointPressure; Record.TestDate = entity.TestDate; Record.Tester = entity.Tester; Record.DataPoints.Clear(); foreach (var dp in entity.DataPoints) { Record.DataPoints.Add(new Models.DataPoint { Pressure = dp.Pressure, WetFlow = dp.WetFlow, DryFlow = dp.DryFlow }); } // 同步液体选择 if (Record.Liquid != null) { var matchedLiquid = Liquids.FirstOrDefault(l => l.Name == Record.Liquid.Name); SelectedLiquid = matchedLiquid ?? Record.Liquid; } else { SelectedLiquid = Liquids.FirstOrDefault(); } // 重新计算平均孔径和分布(触发计算命令) Calculate(); } public ICommand SaveCommand { get; } public ICommand OpenFlowCalibCommand { get; } public ICommand OpenFlowCalibCommand2 { get; } public ICommand ExportCommand { get; } private void ExportToExcel() { // 使用当前数据点(已通过手动删除或自动清洗,保证有效性) var dataPoints = Record.DataPoints.ToList(); if (dataPoints.Count == 0) { MessageBox.Show("没有数据可导出。", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } var saveFileDialog = new SaveFileDialog { Filter = "Excel文件|*.xlsx", FileName = $"孔分布_工位{StationId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx" }; if (saveFileDialog.ShowDialog() == true) { var entity = new PoreDistributionEntity { StationId = this.StationId, TestDate = Record.TestDate, Tester = Record.Tester ?? "系统操作员", SampleType = Record.SampleType, SampleSpec = Record.SampleSpec ?? "缺省值", RoomTemperature = Record.RoomTemperature, SoakingTime = Record.SoakingTime, LiquidName = Record.Liquid?.Name, LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0, LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值", PressureUnit = Record.PressureUnit, BubblePointPressure = Record.BubblePointPressure, AveragePoreSize = AveragePoreSize, // 已计算的平均孔径 DataPoints = dataPoints.Select(dp => new DataPointEntity { Pressure = dp.Pressure, WetFlow = dp.WetFlow, DryFlow = dp.DryFlow }).ToList() }; ExportHelper.ExportPoreDistribution(entity, saveFileDialog.FileName); MessageBox.Show("导出成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } } private string _testMode = "湿膜"; // 默认湿膜 public string TestMode { get => _testMode; set { if (SetProperty(ref _testMode, value)) { // 切换测试模式时刷新曲线和相关逻辑 UpdatePlot(); } } } private OxyPlot.PlotModel _plotModel; public OxyPlot.PlotModel PlotModel { get => _plotModel; set => SetProperty(ref _plotModel, value); } // 用于保护绘图更新,避免并发导致渲染丢失 private readonly object _plotLock = new(); private async Task WriteFlowModeAsync(string mode) { ushort val = mode.ToString().Contains("大流量") ? (ushort)0 : (ushort)1; try { if (StationId == 1) await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister, val); else if (StationId == 2) await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister2, val); else if (StationId == 3) await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister3, val); } catch (Exception ex) { MessageBox.Show($"写流量模式失败: {ex.Message}"); } } //private void UpdatePlot() //{ // // 确保在 UI 线程执行 // if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess()) // { // Application.Current.Dispatcher.BeginInvoke((Action)UpdatePlot); // return; // } // var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList(); // if (sorted.Count == 0) // { // PlotModel = null; // return; // } // var model = new PlotModel // { // Title = "流量-压力曲线", // TitleFontSize = 14, // TitleFontWeight = 1 // }; // model.Axes.Add(new LinearAxis // { // Position = AxisPosition.Bottom, // Title = "压力 (kPa)", // TitleFontSize = 12 // }); // model.Axes.Add(new LinearAxis // { // Position = AxisPosition.Left, // Title = "流量 (L/min)", // TitleFontSize = 12, // Minimum = 0, // MajorGridlineStyle = LineStyle.Solid, // MinorGridlineStyle = LineStyle.Dot // }); // // 根据当前模式绘制对应的曲线(只绘制流量 > 0 的点) // if (TestMode != null && TestMode.Contains("湿膜")) // { // var wetSeries = new LineSeries // { // Title = "湿膜流量 (Wet Flow)", // Color = OxyColors.Blue, // MarkerType = MarkerType.None, // StrokeThickness = 1 // }; // foreach (var dp in sorted.Where(p => p.WetFlow > 0)) // wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow)); // if (wetSeries.Points.Count > 0) // model.Series.Add(wetSeries); // } // else if (TestMode != null && TestMode.Contains("干膜")) // { // var drySeries = new LineSeries // { // Title = "干膜流量 (Dry Flow)", // Color = OxyColors.Red, // MarkerType = MarkerType.None, // StrokeThickness = 1 // }; // foreach (var dp in sorted.Where(p => p.DryFlow > 0)) // drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow)); // if (drySeries.Points.Count > 0) // model.Series.Add(drySeries); // } // else // { // // “全部”模式:同时显示两条曲线,但仍过滤掉 0 值点 // var wetSeries = new LineSeries // { // Title = "湿膜流量 (Wet Flow)", // Color = OxyColors.Blue, // MarkerType = MarkerType.None, // StrokeThickness = 1 // }; // var drySeries = new LineSeries // { // Title = "干膜流量 (Dry Flow)", // Color = OxyColors.Red, // MarkerType = MarkerType.None, // StrokeThickness = 1 // }; // foreach (var dp in sorted) // { // if (dp.WetFlow > 0) wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow)); // if (dp.DryFlow > 0) drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow)); // } // if (wetSeries.Points.Count > 0) model.Series.Add(wetSeries); // if (drySeries.Points.Count > 0) model.Series.Add(drySeries); // } // PlotModel = model; //} public void RefreshPlot() { UpdatePlot(); } private void UpdatePlot() { // 确保在 UI 线程执行 if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess()) { Application.Current.Dispatcher.BeginInvoke((Action)UpdatePlot); return; } var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList(); //if (sorted.Count == 0) //{ // PlotModel = null; // return; //} var model = new PlotModel { Title = "流量-压力曲线", TitleFontSize = 14, TitleFontWeight = 1 }; model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "压力 (Pa)", TitleFontSize = 12 }); model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "流量 (L/min)", TitleFontSize = 12, Minimum = 0, MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot }); // 干膜半流量曲线(绿色虚线,每个压力点干膜流量的一半) var halfDrySeries = new LineSeries { Title = "干膜半流量 (Half of Dry Flow)", Color = OxyColors.Green, MarkerType = MarkerType.None, StrokeThickness = 1, LineStyle = LineStyle.Dash }; foreach (var dp in sorted.Where(p => p.DryFlow > 0)) { halfDrySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow / 2.0)); } // 根据当前模式绘制对应的曲线(只绘制流量 > 0 的点) if (TestMode != null && TestMode.Contains("湿膜")) { var wetSeries = new LineSeries { Title = "湿膜流量 (Wet Flow)", Color = OxyColors.Blue, MarkerType = MarkerType.None, StrokeThickness = 1 }; foreach (var dp in sorted.Where(p => p.WetFlow > 0)) wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow)); if (wetSeries.Points.Count > 0) model.Series.Add(wetSeries); // 湿膜模式下也显示半流量线(便于观察交点) if (halfDrySeries.Points.Count > 0) model.Series.Add(halfDrySeries); } else if (TestMode != null && TestMode.Contains("干膜")) { var drySeries = new LineSeries { Title = "干膜流量 (Dry Flow)", Color = OxyColors.Red, MarkerType = MarkerType.None, StrokeThickness = 1 }; foreach (var dp in sorted.Where(p => p.DryFlow > 0)) drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow)); if (drySeries.Points.Count > 0) model.Series.Add(drySeries); if (halfDrySeries.Points.Count > 0) model.Series.Add(halfDrySeries); } else { // “全部”模式:同时显示两条曲线,但仍过滤掉 0 值点 var wetSeries = new LineSeries { Title = "湿膜流量 (Wet Flow)", Color = OxyColors.Blue, MarkerType = MarkerType.None, StrokeThickness = 1 }; var drySeries = new LineSeries { Title = "干膜流量 (Dry Flow)", Color = OxyColors.Red, MarkerType = MarkerType.None, StrokeThickness = 1 }; foreach (var dp in sorted) { if (dp.WetFlow > 0) wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow)); if (dp.DryFlow > 0) drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow)); } if (wetSeries.Points.Count > 0) model.Series.Add(wetSeries); if (drySeries.Points.Count > 0) model.Series.Add(drySeries); if (halfDrySeries.Points.Count > 0) model.Series.Add(halfDrySeries); } PlotModel = model; } public ICommand ClearAllCommand { get; } private double _currentFlow; public double CurrentFlow { get => _currentFlow; set => SetProperty(ref _currentFlow, value); } private double _currentPressure; public double CurrentPressure { get => _currentPressure; set => SetProperty(ref _currentPressure, value); } // 修改命令初始化(构造函数中) // 实现删除方法 private void RemoveSelectedDataPoint() { if (SelectedDataPoint != null) { Record.DataPoints.Remove(SelectedDataPoint); SelectedDataPoint = null; // 清除选中状态 UpdatePlot(); // 刷新曲线 } } public List TestModes { get; } = new List {"全部", "湿膜", "干膜" }; private double ConvertFlowByMode(double rawFlow) { // 如果当前选择的是小流量模式,则需要除以1000(因为PLC可能存储的是mL/min) if (SelectedPressureMode != null && SelectedPressureMode.Text.Contains("小流量")) return rawFlow / 1000.0; else return rawFlow; } } }