Compare commits
6 Commits
9437c6010a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 264cee4e1a | |||
| c08d568570 | |||
| 4a6393ba8d | |||
| 05c38a7dab | |||
| dd0bd405ee | |||
| 01a07f8811 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8
App.xaml
8
App.xaml
@@ -1,7 +1,13 @@
|
||||
<Application x:Class="MembranePoreTester.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="Views/MainWindow.xaml">
|
||||
StartupUri="Views/TestView.xaml">
|
||||
<Application.Resources>
|
||||
<!-- 全局样式定义 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,54 +0,0 @@
|
||||
using OfficeOpenXml;
|
||||
using System.IO;
|
||||
|
||||
public static class ExportHelper
|
||||
{
|
||||
public static void ExportBubblePoint(BubblePointEntity entity, string filePath)
|
||||
{
|
||||
using var package = new ExcelPackage();
|
||||
var sheet = package.Workbook.Worksheets.Add("泡点法测试");
|
||||
sheet.Cells["A1"].Value = "工位"; sheet.Cells["B1"].Value = entity.StationId;
|
||||
sheet.Cells["A2"].Value = "测试日期"; sheet.Cells["B2"].Value = entity.TestDate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
sheet.Cells["A3"].Value = "测试者"; sheet.Cells["B3"].Value = entity.Tester;
|
||||
sheet.Cells["A4"].Value = "样品类型"; sheet.Cells["B4"].Value = entity.SampleType;
|
||||
sheet.Cells["A5"].Value = "规格"; sheet.Cells["B5"].Value = entity.SampleSpec;
|
||||
sheet.Cells["A6"].Value = "室温(°C)"; sheet.Cells["B6"].Value = entity.RoomTemperature;
|
||||
sheet.Cells["A7"].Value = "浸润时间(h)"; sheet.Cells["B7"].Value = entity.SoakingTime;
|
||||
sheet.Cells["A8"].Value = "测试液体"; sheet.Cells["B8"].Value = entity.LiquidName;
|
||||
sheet.Cells["A9"].Value = "液体生产厂家"; sheet.Cells["B9"].Value = entity.LiquidManufacturer;
|
||||
sheet.Cells["A10"].Value = "泡点压力"; sheet.Cells["B10"].Value = $"{entity.BubblePointPressure} {entity.PressureUnit}";
|
||||
sheet.Cells["A11"].Value = "最大孔径(μm)"; sheet.Cells["B11"].Value = entity.MaxPoreSize;
|
||||
|
||||
package.SaveAs(new FileInfo(filePath));
|
||||
}
|
||||
|
||||
public static void ExportPoreDistribution(PoreDistributionEntity entity, string filePath)
|
||||
{
|
||||
using var package = new ExcelPackage();
|
||||
var sheet = package.Workbook.Worksheets.Add("孔分布测试");
|
||||
sheet.Cells["A1"].Value = "工位"; sheet.Cells["B1"].Value = entity.StationId;
|
||||
sheet.Cells["A2"].Value = "测试日期"; sheet.Cells["B2"].Value = entity.TestDate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
sheet.Cells["A3"].Value = "测试者"; sheet.Cells["B3"].Value = entity.Tester;
|
||||
sheet.Cells["A4"].Value = "样品类型"; sheet.Cells["B4"].Value = entity.SampleType;
|
||||
sheet.Cells["A5"].Value = "规格"; sheet.Cells["B5"].Value = entity.SampleSpec;
|
||||
sheet.Cells["A6"].Value = "室温(°C)"; sheet.Cells["B6"].Value = entity.RoomTemperature;
|
||||
sheet.Cells["A7"].Value = "浸润时间(h)"; sheet.Cells["B7"].Value = entity.SoakingTime;
|
||||
sheet.Cells["A8"].Value = "测试液体"; sheet.Cells["B8"].Value = entity.LiquidName;
|
||||
sheet.Cells["A9"].Value = "液体生产厂家"; sheet.Cells["B9"].Value = entity.LiquidManufacturer;
|
||||
sheet.Cells["A10"].Value = "泡点压力"; sheet.Cells["B10"].Value = $"{entity.BubblePointPressure} {entity.PressureUnit}";
|
||||
sheet.Cells["A11"].Value = "平均孔径(μm)"; sheet.Cells["B11"].Value = entity.AveragePoreSize;
|
||||
|
||||
// 数据点表格
|
||||
sheet.Cells["A13"].Value = "压力"; sheet.Cells["B13"].Value = "湿膜流量(L/min)"; sheet.Cells["C13"].Value = "干膜流量(L/min)";
|
||||
int row = 14;
|
||||
foreach (var dp in entity.DataPoints)
|
||||
{
|
||||
sheet.Cells[$"A{row}"].Value = dp.Pressure;
|
||||
sheet.Cells[$"B{row}"].Value = dp.WetFlow;
|
||||
sheet.Cells[$"C{row}"].Value = dp.DryFlow;
|
||||
row++;
|
||||
}
|
||||
|
||||
package.SaveAs(new FileInfo(filePath));
|
||||
}
|
||||
}
|
||||
@@ -17,39 +17,50 @@
|
||||
public ushort PressureRegisterStation1 { get; set; }
|
||||
public ushort PressureRegisterStation2 { get; set; }
|
||||
public ushort PressureRegisterStation3 { get; set; }
|
||||
public ushort PressureModeRegister { get; set; }
|
||||
public ushort PressCoil { get; set; }
|
||||
public ushort StartCoil { get; set; }
|
||||
public ushort EnableCoil { get; set; }
|
||||
public ushort StopCoil { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public ushort PressureUpperLimit { get; set; } = 300;
|
||||
public ushort PressureRate { get; set; } = 280;
|
||||
public ushort PressureCoeff { get; set; } = 282;
|
||||
public ushort HPCoeff1 { get; set; } = 3120;
|
||||
public ushort LPCoeff1 { get; set; } = 3122;
|
||||
public ushort HPCoeff2 { get; set; } = 3124;
|
||||
public ushort LPCoeff2 { get; set; } = 3126;
|
||||
public ushort HPCoeff3 { get; set; } = 3128;
|
||||
public ushort LPCoeff3 { get; set; } = 3130;
|
||||
public ushort LargeFlowCoeff1 { get; set; } = 3048;
|
||||
public ushort SmallFlowCoeff1 { get; set; } = 380;
|
||||
public ushort LargeFlowCoeff2 { get; set; } = 1218;
|
||||
public ushort SmallFlowCoeff2 { get; set; } = 1318;
|
||||
public ushort LargeFlowCoeff3 { get; set; } = 1418;
|
||||
public ushort SmallFlowCoeff3 { get; set; } = 1468;
|
||||
public ushort FlowModeRegister1 { get; set; } = 5; // 工位1流量模式 (0=大,1=小)
|
||||
public ushort FlowModeRegister2 { get; set; } = 6;
|
||||
public ushort FlowModeRegister3 { get; set; } = 7;
|
||||
// 校准系数地址(需与PLC约定)
|
||||
public ushort PressureCalibZero { get; set; } = 3200;
|
||||
public ushort PressureCalibSpan { get; set; } = 3202;
|
||||
public ushort FlowCalibZero { get; set; } = 3204;
|
||||
public ushort FlowCalibSpan { get; set; } = 3206;
|
||||
|
||||
|
||||
// 设备参数地址(示例,请根据实际PLC地址修改)
|
||||
public ushort UpperLampData1 { get; set; } = 350;
|
||||
public ushort UpperLampData2 { get; set; } = 351;
|
||||
public ushort UpperLampData3 { get; set; } = 352;
|
||||
public ushort UpperLampData4 { get; set; } = 353;
|
||||
public ushort UpperLampData5 { get; set; } = 354;
|
||||
public ushort UpperLampData6 { get; set; } = 355;
|
||||
|
||||
public ushort LowerLampData1 { get; set; } = 356;
|
||||
public ushort LowerLampData2 { get; set; } = 357;
|
||||
public ushort LowerLampData3 { get; set; } = 358;
|
||||
public ushort LowerLampData4 { get; set; } = 359;
|
||||
public ushort LowerLampData5 { get; set; } = 360;
|
||||
public ushort LowerLampData6 { get; set; } = 361;
|
||||
|
||||
public ushort LeftEyeAreaCoeff { get; set; } = 212;
|
||||
public ushort RightEyeAreaCoeff { get; set; } = 214;
|
||||
public ushort SaveRateCorrectionCoeff { get; set; } = 428;
|
||||
|
||||
public ushort MiddleLamp1 { get; set; } = 362;
|
||||
public ushort MiddleLamp2 { get; set; } = 363;
|
||||
public ushort MiddleLamp3 { get; set; } = 364;
|
||||
public ushort MiddleLamp4 { get; set; } = 365;
|
||||
public ushort MiddleLamp5 { get; set; } = 366;
|
||||
public ushort MiddleLamp6 { get; set; } = 367;
|
||||
public ushort MiddleLamp7 { get; set; } = 368;
|
||||
|
||||
public ushort MotorLimit { get; set; } = 330;
|
||||
public ushort ResetCompensation { get; set; } = 340; // 新增复位补偿
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +69,11 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum ParameterDataType
|
||||
{
|
||||
Int16, // 16位整数
|
||||
Float32 // 32位浮点数
|
||||
}
|
||||
|
||||
|
||||
public interface IPlcService
|
||||
@@ -73,7 +88,12 @@
|
||||
|
||||
|
||||
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
|
||||
|
||||
Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
|
||||
|
||||
float UshortToFloat(ushort P1, ushort P2);
|
||||
|
||||
Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using MembranePoreTester.Models;
|
||||
|
||||
namespace MembranePoreTester.Helpers
|
||||
{
|
||||
public static class PoreCalculator
|
||||
{
|
||||
public static double PressureToPore(double pressure, string unit, TestLiquid liquid)
|
||||
{
|
||||
if (liquid == null) return 0;
|
||||
return unit switch
|
||||
{
|
||||
"Pa" => liquid.C_Pa / pressure,
|
||||
"cmHg" => liquid.C_cmHg / pressure,
|
||||
"psi" => liquid.C_psi / pressure,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
public static double PoreToPressure(double pore, string unit, TestLiquid liquid)
|
||||
{
|
||||
if (liquid == null) return 0;
|
||||
return unit switch
|
||||
{
|
||||
"Pa" => liquid.C_Pa / pore,
|
||||
"cmHg" => liquid.C_cmHg / pore,
|
||||
"psi" => liquid.C_psi / pore,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using MembranePoreTester.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MembranePoreTester.Helpers
|
||||
{
|
||||
public static class PoreDistributionAnalysis
|
||||
{
|
||||
public static double CalculateAveragePore(
|
||||
IEnumerable<DataPoint> points,
|
||||
string unit,
|
||||
TestLiquid liquid)
|
||||
{
|
||||
var sorted = points.OrderBy(p => p.Pressure).ToList();
|
||||
if (sorted.Count < 2) return 0;
|
||||
|
||||
double[] pressures = sorted.Select(p => p.Pressure).ToArray();
|
||||
double[] dryFlows = sorted.Select(p => p.DryFlow).ToArray();
|
||||
double[] wetFlows = sorted.Select(p => p.WetFlow).ToArray();
|
||||
|
||||
double halfDryFlow = dryFlows.Max() / 2.0;
|
||||
|
||||
// 找到湿膜流量等于 halfDryFlow 的压力(交点)
|
||||
for (int i = 0; i < wetFlows.Length - 1; i++)
|
||||
{
|
||||
if ((wetFlows[i] <= halfDryFlow && wetFlows[i + 1] >= halfDryFlow) ||
|
||||
(wetFlows[i] >= halfDryFlow && wetFlows[i + 1] <= halfDryFlow))
|
||||
{
|
||||
double p = Interpolation.Linear(
|
||||
new[] { wetFlows[i], wetFlows[i + 1] },
|
||||
new[] { pressures[i], pressures[i + 1] },
|
||||
halfDryFlow);
|
||||
return PoreCalculator.PressureToPore(p, unit, liquid);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double CalculatePoreRangePercentage(
|
||||
IEnumerable<DataPoint> points,
|
||||
string unit,
|
||||
TestLiquid liquid,
|
||||
double lowerPore,
|
||||
double upperPore)
|
||||
{
|
||||
var sorted = points.OrderBy(p => p.Pressure).ToList();
|
||||
if (sorted.Count < 2) return 0;
|
||||
|
||||
double[] pressures = sorted.Select(p => p.Pressure).ToArray();
|
||||
double[] wetFlows = sorted.Select(p => p.WetFlow).ToArray();
|
||||
double[] dryFlows = sorted.Select(p => p.DryFlow).ToArray();
|
||||
|
||||
// 大孔径对应低压,小孔径对应高压
|
||||
double pLower = PoreCalculator.PoreToPressure(upperPore, unit, liquid);
|
||||
double pUpper = PoreCalculator.PoreToPressure(lowerPore, unit, liquid);
|
||||
|
||||
// 插值
|
||||
double qWetLower = Interpolation.Linear(pressures, wetFlows, pLower);
|
||||
double qDryLower = Interpolation.Linear(pressures, dryFlows, pLower);
|
||||
double qWetUpper = Interpolation.Linear(pressures, wetFlows, pUpper);
|
||||
double qDryUpper = Interpolation.Linear(pressures, dryFlows, pUpper);
|
||||
|
||||
double ratioLow = qWetLower / qDryLower;
|
||||
double ratioHigh = qWetUpper / qDryUpper;
|
||||
|
||||
return (ratioHigh - ratioLow) * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
using MembranePoreTester.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Xps.Packaging;
|
||||
|
||||
namespace MembranePoreTester.Helpers
|
||||
{
|
||||
public static class ReportGenerator
|
||||
{
|
||||
public static void GenerateBubblePointReport(BubblePointRecord record)
|
||||
{
|
||||
FlowDocument doc = CreateBubblePointDocument(record);
|
||||
ShowPrintPreview(doc, "泡点法测试报告");
|
||||
}
|
||||
|
||||
public static void GeneratePoreDistributionReport(PoreDistributionRecord record)
|
||||
{
|
||||
FlowDocument doc = CreatePoreDistributionDocument(record);
|
||||
ShowPrintPreview(doc, "孔分布测试报告");
|
||||
}
|
||||
|
||||
private static FlowDocument CreateBubblePointDocument(BubblePointRecord record)
|
||||
{
|
||||
FlowDocument doc = new FlowDocument();
|
||||
doc.PageWidth = 800; // 适应打印
|
||||
|
||||
// 标题
|
||||
doc.Blocks.Add(new Paragraph(new Run("泡点法测试报告"))
|
||||
{
|
||||
FontSize = 24,
|
||||
FontWeight = FontWeights.Bold,
|
||||
TextAlignment = TextAlignment.Center
|
||||
});
|
||||
|
||||
// 日期/测试者
|
||||
doc.Blocks.Add(new Paragraph(new Run($"测试日期: {record.TestDate:yyyy-MM-dd} 测试者: {record.Tester}"))
|
||||
{
|
||||
TextAlignment = TextAlignment.Right
|
||||
});
|
||||
|
||||
// 样品信息表格
|
||||
Table table = new Table();
|
||||
table.Columns.Add(new TableColumn { Width = new GridLength(150) });
|
||||
table.Columns.Add(new TableColumn { Width = new GridLength(200) });
|
||||
|
||||
TableRowGroup group = new TableRowGroup();
|
||||
AddRow(group, "膜类型", record.SampleType);
|
||||
AddRow(group, "规格", record.SampleSpec);
|
||||
AddRow(group, "室温(°C)", record.RoomTemperature.ToString("F1"));
|
||||
AddRow(group, "浸润时间(h)", record.SoakingTime.ToString("F1"));
|
||||
AddRow(group, "测试液体", record.Liquid?.Name);
|
||||
AddRow(group, "液体生产厂家", record.LiquidManufacturer);
|
||||
AddRow(group, "泡点压力", $"{record.BubblePointPressure} {record.PressureUnit}");
|
||||
AddRow(group, "最大孔径(μm)", record.MaxPoreSize.ToString("F3"));
|
||||
|
||||
table.RowGroups.Add(group);
|
||||
doc.Blocks.Add(table);
|
||||
|
||||
doc.Blocks.Add(new Paragraph(new Run("本报告依据 GB/T 32361-2015 生成。"))
|
||||
{
|
||||
FontStyle = FontStyles.Italic,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0)
|
||||
});
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static FlowDocument CreatePoreDistributionDocument(PoreDistributionRecord record)
|
||||
{
|
||||
FlowDocument doc = new FlowDocument();
|
||||
doc.PageWidth = 800;
|
||||
|
||||
// 标题
|
||||
doc.Blocks.Add(new Paragraph(new Run("孔分布测试报告"))
|
||||
{
|
||||
FontSize = 24,
|
||||
FontWeight = FontWeights.Bold,
|
||||
TextAlignment = TextAlignment.Center
|
||||
});
|
||||
|
||||
doc.Blocks.Add(new Paragraph(new Run($"测试日期: {record.TestDate:yyyy-MM-dd} 测试者: {record.Tester}"))
|
||||
{
|
||||
TextAlignment = TextAlignment.Right
|
||||
});
|
||||
|
||||
// 样品信息表格
|
||||
Table infoTable = new Table();
|
||||
infoTable.Columns.Add(new TableColumn { Width = new GridLength(150) });
|
||||
infoTable.Columns.Add(new TableColumn { Width = new GridLength(200) });
|
||||
TableRowGroup group = new TableRowGroup();
|
||||
AddRow(group, "膜类型", record.SampleType);
|
||||
AddRow(group, "规格", record.SampleSpec);
|
||||
AddRow(group, "室温(°C)", record.RoomTemperature.ToString("F1"));
|
||||
AddRow(group, "浸润时间(h)", record.SoakingTime.ToString("F1"));
|
||||
AddRow(group, "测试液体", record.Liquid?.Name);
|
||||
AddRow(group, "液体生产厂家", record.LiquidManufacturer);
|
||||
infoTable.RowGroups.Add(group);
|
||||
doc.Blocks.Add(infoTable);
|
||||
|
||||
// 计算结果
|
||||
doc.Blocks.Add(new Paragraph(new Run("测试结果"))
|
||||
{
|
||||
FontSize = 18,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 10, 0, 5)
|
||||
});
|
||||
|
||||
Table resultTable = new Table();
|
||||
resultTable.Columns.Add(new TableColumn { Width = new GridLength(150) });
|
||||
resultTable.Columns.Add(new TableColumn { Width = new GridLength(200) });
|
||||
TableRowGroup resultGroup = new TableRowGroup();
|
||||
AddRow(resultGroup, "泡点压力", $"{record.BubblePointPressure} {record.PressureUnit}");
|
||||
AddRow(resultGroup, "平均孔径(μm)", record.AveragePoreSize.ToString("F3"));
|
||||
resultTable.RowGroups.Add(resultGroup);
|
||||
doc.Blocks.Add(resultTable);
|
||||
|
||||
// 数据点列表
|
||||
doc.Blocks.Add(new Paragraph(new Run("压力-流量数据"))
|
||||
{
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 10, 0, 5)
|
||||
});
|
||||
|
||||
Table dataTable = new Table();
|
||||
dataTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
dataTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
dataTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
dataTable.CellSpacing = 2;
|
||||
|
||||
TableRowGroup dataGroup = new TableRowGroup();
|
||||
|
||||
// 表头
|
||||
TableRow headerRow = new TableRow();
|
||||
headerRow.Cells.Add(new TableCell(new Paragraph(new Run("压力"))));
|
||||
headerRow.Cells.Add(new TableCell(new Paragraph(new Run("湿膜流量(L/min)"))));
|
||||
headerRow.Cells.Add(new TableCell(new Paragraph(new Run("干膜流量(L/min)"))));
|
||||
headerRow.FontWeight = FontWeights.Bold;
|
||||
dataGroup.Rows.Add(headerRow);
|
||||
|
||||
foreach (var dp in record.DataPoints)
|
||||
{
|
||||
TableRow row = new TableRow();
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(dp.Pressure.ToString("F2")))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(dp.WetFlow.ToString("F3")))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(dp.DryFlow.ToString("F3")))));
|
||||
dataGroup.Rows.Add(row);
|
||||
}
|
||||
|
||||
dataTable.RowGroups.Add(dataGroup);
|
||||
doc.Blocks.Add(dataTable);
|
||||
|
||||
// 孔分布结果
|
||||
if (record.PoreDistributions != null && record.PoreDistributions.Any())
|
||||
{
|
||||
doc.Blocks.Add(new Paragraph(new Run("孔分布"))
|
||||
{
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 10, 0, 5)
|
||||
});
|
||||
|
||||
Table distTable = new Table();
|
||||
distTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
distTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
distTable.Columns.Add(new TableColumn { Width = new GridLength(80) });
|
||||
|
||||
TableRowGroup distGroup = new TableRowGroup();
|
||||
|
||||
TableRow distHeader = new TableRow();
|
||||
distHeader.Cells.Add(new TableCell(new Paragraph(new Run("孔径下限(μm)"))));
|
||||
distHeader.Cells.Add(new TableCell(new Paragraph(new Run("孔径上限(μm)"))));
|
||||
distHeader.Cells.Add(new TableCell(new Paragraph(new Run("百分比(%)"))));
|
||||
distHeader.FontWeight = FontWeights.Bold;
|
||||
distGroup.Rows.Add(distHeader);
|
||||
|
||||
foreach (var pd in record.PoreDistributions)
|
||||
{
|
||||
TableRow row = new TableRow();
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(pd.LowerPore.ToString("F3")))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(pd.UpperPore.ToString("F3")))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(pd.Percentage.ToString("F1")))));
|
||||
distGroup.Rows.Add(row);
|
||||
}
|
||||
|
||||
distTable.RowGroups.Add(distGroup);
|
||||
doc.Blocks.Add(distTable);
|
||||
}
|
||||
|
||||
doc.Blocks.Add(new Paragraph(new Run("本报告依据 GB/T 32361-2015 生成。"))
|
||||
{
|
||||
FontStyle = FontStyles.Italic,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Margin = new Thickness(0, 20, 0, 0)
|
||||
});
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static void AddRow(TableRowGroup group, string label, string value)
|
||||
{
|
||||
TableRow row = new TableRow();
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(label))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(value ?? ""))));
|
||||
group.Rows.Add(row);
|
||||
}
|
||||
|
||||
private static void ShowPrintPreview(FlowDocument document, string title)
|
||||
{
|
||||
PrintDialog printDialog = new PrintDialog();
|
||||
if (printDialog.ShowDialog() == true)
|
||||
{
|
||||
// 调整文档页面大小以匹配打印机
|
||||
document.PageHeight = printDialog.PrintableAreaHeight;
|
||||
document.PageWidth = printDialog.PrintableAreaWidth;
|
||||
document.PagePadding = new Thickness(50);
|
||||
|
||||
IDocumentPaginatorSource dps = document;
|
||||
printDialog.PrintDocument(dps.DocumentPaginator, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public class BubblePointRecord
|
||||
{
|
||||
public string SampleType { get; set; } // 平板膜/中空纤维膜
|
||||
public string SampleSpec { get; set; } // 规格
|
||||
public double RoomTemperature { get; set; } // 室温(°C)
|
||||
public double SoakingTime { get; set; } // 浸润时间(小时)
|
||||
public TestLiquid Liquid { get; set; } // 测试液体
|
||||
public string LiquidManufacturer { get; set; } // 生产厂家
|
||||
public double BubblePointPressure { get; set; } // 泡点压力(数值)
|
||||
public string PressureUnit { get; set; } // Pa/cmHg/psi
|
||||
public DateTime TestDate { get; set; } = DateTime.Now;
|
||||
public string Tester { get; set; }
|
||||
|
||||
public double MaxPoreSize => CalculateMaxPore();
|
||||
|
||||
private double CalculateMaxPore()
|
||||
{
|
||||
if (Liquid == null) return 0;
|
||||
return PressureUnit switch
|
||||
{
|
||||
"Pa" => Liquid.C_Pa / BubblePointPressure,
|
||||
"cmHg" => Liquid.C_cmHg / BubblePointPressure,
|
||||
"psi" => Liquid.C_psi / BubblePointPressure,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public class DataPoint
|
||||
{
|
||||
public double Pressure { get; set; } // 压力
|
||||
public double WetFlow { get; set; } // 湿膜流量(L/min)
|
||||
public double DryFlow { get; set; } // 干膜流量(L/min)
|
||||
|
||||
}
|
||||
}
|
||||
38
Models/DeviceDataModel.cs
Normal file
38
Models/DeviceDataModel.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public partial class DeviceDataModel : ObservableObject
|
||||
{
|
||||
// 灯条数据
|
||||
[ObservableProperty] private double _upperLampData1;
|
||||
[ObservableProperty] private double _lowerLampData1;
|
||||
[ObservableProperty] private double _upperLampData2;
|
||||
[ObservableProperty] private double _lowerLampData2;
|
||||
[ObservableProperty] private double _upperLampData3;
|
||||
[ObservableProperty] private double _lowerLampData3;
|
||||
[ObservableProperty] private double _upperLampData4;
|
||||
[ObservableProperty] private double _lowerLampData4;
|
||||
[ObservableProperty] private double _upperLampData5;
|
||||
[ObservableProperty] private double _lowerLampData5;
|
||||
[ObservableProperty] private double _upperLampData6;
|
||||
[ObservableProperty] private double _lowerLampData6;
|
||||
|
||||
// 面积系数
|
||||
[ObservableProperty] private double _leftEyeAreaCoeff;
|
||||
[ObservableProperty] private double _rightEyeAreaCoeff;
|
||||
[ObservableProperty] private double _saveRateCorrectionCoeff;
|
||||
|
||||
// 中灯
|
||||
[ObservableProperty] private double _middleLamp1;
|
||||
[ObservableProperty] private double _middleLamp2;
|
||||
[ObservableProperty] private double _middleLamp3;
|
||||
[ObservableProperty] private double _middleLamp4;
|
||||
[ObservableProperty] private double _middleLamp5;
|
||||
[ObservableProperty] private double _middleLamp6;
|
||||
[ObservableProperty] private double _middleLamp7;
|
||||
|
||||
// 电机限位
|
||||
[ObservableProperty] private double _motorLimit;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 孔分布测试的记录模型,存储一次孔分布测试的所有输入数据和计算结果。
|
||||
/// 对应 GB/T 32361-2015 标准中的平均流量法测试。
|
||||
/// </summary>
|
||||
public class PoreDistributionRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 膜类型:平板膜 或 中空纤维膜。
|
||||
/// </summary>
|
||||
public string SampleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 样品规格,如直径、厚度或型号等。
|
||||
/// </summary>
|
||||
public string SampleSpec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测试时的室温,单位:摄氏度(°C)。
|
||||
/// </summary>
|
||||
public double RoomTemperature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 样品在测试液体中的浸润时间,单位:小时(h)。
|
||||
/// </summary>
|
||||
public double SoakingTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测试使用的液体对象,包含液体名称、表面张力等信息。
|
||||
/// </summary>
|
||||
public TestLiquid Liquid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测试液体的生产厂家,用于溯源。
|
||||
/// </summary>
|
||||
public string LiquidManufacturer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 压力单位:Pa、cmHg 或 psi。所有压力相关输入均以此单位为准。
|
||||
/// </summary>
|
||||
public string PressureUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 压力-流量数据点集合,每个点包含压力、湿膜流量、干膜流量。
|
||||
/// 使用 ObservableCollection 以便在界面添加/删除时自动更新。
|
||||
/// </summary>
|
||||
public ObservableCollection<DataPoint> DataPoints { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 测试日期,默认为当前日期时间。
|
||||
/// </summary>
|
||||
public DateTime TestDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 测试人员姓名。
|
||||
/// </summary>
|
||||
public string Tester { get; set; }
|
||||
|
||||
// ---------- 计算结果(由计算逻辑填充) ----------
|
||||
|
||||
/// <summary>
|
||||
/// 泡点压力(由用户单独记录或从数据点推断),用于最大孔径计算。
|
||||
/// </summary>
|
||||
public double BubblePointPressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算出的平均孔径,单位:微米(μm)。
|
||||
/// 对应标准中平均流量法的平均孔径结果。
|
||||
/// </summary>
|
||||
public double AveragePoreSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 孔分布计算结果列表,每个元素表示一个孔径区间的流量百分比。
|
||||
/// 对应标准中孔分布的多个区间结果。
|
||||
/// </summary>
|
||||
public List<PoreDistributionResult> PoreDistributions { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public class PoreDistributionResult
|
||||
{
|
||||
public double LowerPore { get; set; } // 孔径下限(μm)
|
||||
public double UpperPore { get; set; } // 孔径上限(μm)
|
||||
public double Percentage { get; set; } // 流量百分比
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public class TestLiquid
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public double SurfaceTension { get; set; } // mN/m, 25°C
|
||||
|
||||
// Cγ 值
|
||||
public double C_Pa => 2860 * SurfaceTension;
|
||||
public double C_cmHg => 2.15 * SurfaceTension;
|
||||
public double C_psi => 0.415 * SurfaceTension;
|
||||
|
||||
public static List<TestLiquid> Predefined => new()
|
||||
{
|
||||
new TestLiquid { Name = "水", SurfaceTension = 72.0 },
|
||||
new TestLiquid { Name = "石油馏分", SurfaceTension = 30.0 },
|
||||
new TestLiquid { Name = "乙醇", SurfaceTension = 22.3 },
|
||||
new TestLiquid { Name = "液态石蜡", SurfaceTension = 34.7 },
|
||||
new TestLiquid { Name = "BSD16", SurfaceTension = 16.0 } // 新增
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using MembranePoreTester.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
|
||||
namespace MembranePoreTester.Report
|
||||
{
|
||||
public class Report
|
||||
{
|
||||
public static void GenerateBubblePointReport(BubblePointRecord record)
|
||||
{
|
||||
FlowDocument doc = new FlowDocument();
|
||||
doc.PageWidth = 800; // 适应打印
|
||||
|
||||
Section header = new Section();
|
||||
header.Blocks.Add(new Paragraph(new Run("泡点法测试报告"))
|
||||
{ FontSize = 24, FontWeight = FontWeights.Bold, TextAlignment = TextAlignment.Center });
|
||||
header.Blocks.Add(new Paragraph(new Run($"测试日期: {record.TestDate:yyyy-MM-dd} 测试者: {record.Tester}"))
|
||||
{ TextAlignment = TextAlignment.Right });
|
||||
|
||||
// 样品信息表格
|
||||
Table table = new Table();
|
||||
table.Columns.Add(new TableColumn { Width = new GridLength(150) });
|
||||
table.Columns.Add(new TableColumn { Width = new GridLength(200) });
|
||||
table.RowGroups.Add(new TableRowGroup());
|
||||
|
||||
AddRow(table, "膜类型", record.SampleType);
|
||||
AddRow(table, "规格", record.SampleSpec);
|
||||
AddRow(table, "室温(°C)", record.RoomTemperature.ToString("F1"));
|
||||
AddRow(table, "浸润时间(h)", record.SoakingTime.ToString("F1"));
|
||||
AddRow(table, "测试液体", record.Liquid?.Name);
|
||||
AddRow(table, "液体生产厂家", record.LiquidManufacturer);
|
||||
AddRow(table, "泡点压力", $"{record.BubblePointPressure} {record.PressureUnit}");
|
||||
AddRow(table, "最大孔径(μm)", record.MaxPoreSize.ToString("F3"));
|
||||
|
||||
doc.Blocks.Add(table);
|
||||
doc.Blocks.Add(new Paragraph(new Run("本报告依据GB/T 32361-2015生成。")) { FontStyle = FontStyles.Italic });
|
||||
|
||||
// 打印预览
|
||||
PrintDialog printDialog = new PrintDialog();
|
||||
if (printDialog.ShowDialog() == true)
|
||||
{
|
||||
IDocumentPaginatorSource dps = doc;
|
||||
printDialog.PrintDocument(dps.DocumentPaginator, "泡点法测试报告");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRow(Table table, string label, string value)
|
||||
{
|
||||
TableRow row = new TableRow();
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(label))));
|
||||
row.Cells.Add(new TableCell(new Paragraph(new Run(value ?? ""))));
|
||||
table.RowGroups[0].Rows.Add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Service/HardwareService.cs
Normal file
50
Service/HardwareService.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using MembranePoreTester.Models;
|
||||
|
||||
namespace MembranePoreTester.Services
|
||||
{
|
||||
public class HardwareService : IHardwareService
|
||||
{
|
||||
public DeviceDataModel LoadDeviceData()
|
||||
{
|
||||
// TODO: 实现硬件读取逻辑
|
||||
throw new NotImplementedException("请在此处实现硬件读取逻辑");
|
||||
}
|
||||
|
||||
public void SaveDeviceData(DeviceDataModel data)
|
||||
{
|
||||
// TODO: 实现硬件保存逻辑
|
||||
throw new NotImplementedException("请在此处实现硬件保存逻辑");
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
// TODO: 实现复位指令
|
||||
throw new NotImplementedException("请在此处实现复位指令");
|
||||
}
|
||||
|
||||
public void OpenLeftEye()
|
||||
{
|
||||
// TODO: 实现左眼开指令
|
||||
throw new NotImplementedException("请在此处实现左眼开指令");
|
||||
}
|
||||
|
||||
public void OpenRightEye()
|
||||
{
|
||||
// TODO: 实现右眼开指令
|
||||
throw new NotImplementedException("请在此处实现右眼开指令");
|
||||
}
|
||||
|
||||
public void ReverseMotor()
|
||||
{
|
||||
// TODO: 实现电机反转
|
||||
throw new NotImplementedException("请在此处实现电机反转");
|
||||
}
|
||||
|
||||
public void ForwardMotor()
|
||||
{
|
||||
// TODO: 实现电机正转
|
||||
throw new NotImplementedException("请在此处实现电机正转");
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Service/IHardwareService.cs
Normal file
22
Service/IHardwareService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using MembranePoreTester.Models;
|
||||
|
||||
namespace MembranePoreTester.Services
|
||||
{
|
||||
public interface IHardwareService
|
||||
{
|
||||
// 读取设备全部参数
|
||||
DeviceDataModel LoadDeviceData();
|
||||
|
||||
// 保存设备全部参数
|
||||
void SaveDeviceData(DeviceDataModel data);
|
||||
|
||||
// 控制命令
|
||||
void Reset();
|
||||
void OpenLeftEye();
|
||||
void OpenRightEye();
|
||||
void ReverseMotor();
|
||||
void ForwardMotor();
|
||||
|
||||
// 可扩展其他方法...
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,51 @@ namespace MembranePoreTester.Communication
|
||||
return await ReadFloatAsync(startAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ushort转为float类型
|
||||
/// </summary>
|
||||
/// <param name="P1"></param>
|
||||
/// <param name="P2"></param>
|
||||
/// <returns>float型数据</returns>
|
||||
public float UshortToFloat(ushort P1, ushort P2)
|
||||
{
|
||||
int intSign, intSignRest, intExponent, intExponentRest;
|
||||
float faResult, faDigit;
|
||||
intSign = P1 / 32768;
|
||||
intSignRest = P1 % 32768;
|
||||
intExponent = intSignRest / 128;
|
||||
intExponentRest = intSignRest % 128;
|
||||
faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
|
||||
faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
|
||||
return faResult;
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
await Task.Delay(100);
|
||||
await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Float转为Ushort数组发送
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns>返回ushort数组</returns>
|
||||
public ushort[] SplitFloatToUShortArray(float value)
|
||||
{
|
||||
byte[] floatBytes = BitConverter.GetBytes(value);
|
||||
ushort[] ushortArray = new ushort[floatBytes.Length / 2];
|
||||
|
||||
for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
|
||||
{
|
||||
ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
|
||||
}
|
||||
|
||||
return ushortArray;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
112
Styles.xaml
Normal file
112
Styles.xaml
Normal file
@@ -0,0 +1,112 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- 主窗口背景 -->
|
||||
<SolidColorBrush x:Key="WindowBackground" Color="#1E1E2F" />
|
||||
|
||||
<!-- 卡片背景 -->
|
||||
<SolidColorBrush x:Key="CardBackground" Color="#2D2D3A" />
|
||||
|
||||
<!-- 按钮样式 -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#3A6EA5" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="12,6" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#2C5A8C" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#1F3F60" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- 文本框样式 -->
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Background" Value="#3A3A4A" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderBrush" Value="#555" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="CaretBrush" Value="White" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter Property="BorderBrush" Value="#3A6EA5" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- 标签样式 -->
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="Foreground" Value="#DDD" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- GroupBox 样式 -->
|
||||
<Style TargetType="GroupBox">
|
||||
<Setter Property="BorderBrush" Value="#3A6EA5" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="{StaticResource CardBackground}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="Margin" Value="8" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupBox">
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="8">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="0"
|
||||
Background="{TemplateBinding BorderBrush}"
|
||||
CornerRadius="4,4,0,0"
|
||||
Margin="-8,-8,-8,0"
|
||||
Padding="8,4">
|
||||
<ContentPresenter ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
TextElement.Foreground="White"
|
||||
TextElement.FontWeight="Bold"/>
|
||||
</Border>
|
||||
<ContentPresenter Grid.Row="1" Margin="0,8,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,232 +0,0 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using MembranePoreTester.Helpers;
|
||||
using MembranePoreTester.Models;
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public class BubblePointViewModel : ViewModelBase
|
||||
{
|
||||
private BubblePointRecord _record = new();
|
||||
private TestLiquid _selectedLiquid;
|
||||
private bool _isCustomLiquid;
|
||||
private double _customSurfaceTension = 30.0;
|
||||
|
||||
|
||||
// 添加字段
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _plcConfig;
|
||||
|
||||
public ICommand ReadPlcCommand { get; }
|
||||
|
||||
public BubblePointRecord Record
|
||||
{
|
||||
get => _record;
|
||||
set => SetProperty(ref _record, value);
|
||||
}
|
||||
|
||||
public List<TestLiquid> Liquids => TestLiquid.Predefined;
|
||||
public List<string> PressureUnits => new() { "Pa", "cmHg", "psi" };
|
||||
public List<string> MembraneTypes => new() { "平板膜", "中空纤维膜" };
|
||||
|
||||
public TestLiquid SelectedLiquid
|
||||
{
|
||||
get => _selectedLiquid;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedLiquid, value))
|
||||
{
|
||||
Record.Liquid = value;
|
||||
OnPropertyChanged(nameof(Record.MaxPoreSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCustomLiquid
|
||||
{
|
||||
get => _isCustomLiquid;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isCustomLiquid, value))
|
||||
{
|
||||
UpdateCustomLiquid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double CustomSurfaceTension
|
||||
{
|
||||
get => _customSurfaceTension;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _customSurfaceTension, value))
|
||||
{
|
||||
UpdateCustomLiquid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCustomLiquid()
|
||||
{
|
||||
if (IsCustomLiquid)
|
||||
{
|
||||
Record.Liquid = new TestLiquid
|
||||
{
|
||||
Name = "自定义",
|
||||
SurfaceTension = CustomSurfaceTension
|
||||
};
|
||||
OnPropertyChanged(nameof(Record.MaxPoreSize));
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand CalculateCommand { get; }
|
||||
public ICommand GenerateReportCommand { get; }
|
||||
|
||||
public ICommand OpenPressureCalibCommand { get; }
|
||||
|
||||
public BubblePointViewModel()
|
||||
{
|
||||
|
||||
_plcService = App.PlcService;
|
||||
_plcConfig = App.PlcConfig;
|
||||
CalculateCommand = new RelayCommand(Calculate);
|
||||
GenerateReportCommand = new RelayCommand(GenerateReport);
|
||||
SelectedLiquid = Liquids[0];
|
||||
ReadPlcCommand = new RelayCommand(async () => await ReadPlcAsync());
|
||||
SaveCommand = new RelayCommand(SaveToDatabase);
|
||||
ExportCommand = new RelayCommand(ExportToExcel);
|
||||
OpenPressureCalibCommand = new RelayCommand(OpenPressureCalibration);
|
||||
}
|
||||
|
||||
|
||||
private async Task ReadPlcAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
float rawPressure = await _plcService.ReadPressureAsync(StationId);
|
||||
Record.BubblePointPressure = rawPressure * _plcConfig.PressureFactor;
|
||||
OnPropertyChanged(nameof(Record.BubblePointPressure));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public double MaxPoreSize => Record.MaxPoreSize;
|
||||
|
||||
private void Calculate()
|
||||
{
|
||||
// 触发最大孔径重新计算(通过绑定已自动刷新)
|
||||
OnPropertyChanged(nameof(Record.MaxPoreSize));
|
||||
}
|
||||
|
||||
private void GenerateReport()
|
||||
{
|
||||
ReportGenerator.GenerateBubblePointReport(Record);
|
||||
}
|
||||
|
||||
public void UpdateBubblePointPressure(double pressure)
|
||||
{
|
||||
Record.BubblePointPressure = pressure;
|
||||
OnPropertyChanged(nameof(Record.BubblePointPressure));
|
||||
OnPropertyChanged(nameof(MaxPoreSize)); // 最大孔径依赖于压力
|
||||
}
|
||||
|
||||
|
||||
|
||||
private int _stationId;
|
||||
public int StationId
|
||||
{
|
||||
get => _stationId;
|
||||
set => SetProperty(ref _stationId, value);
|
||||
}
|
||||
|
||||
// 保存当前记录到数据库
|
||||
public void SaveToDatabase()
|
||||
{
|
||||
var entity = new BubblePointEntity
|
||||
{
|
||||
StationId = 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,
|
||||
BubblePointPressure = Record.BubblePointPressure,
|
||||
PressureUnit = Record.PressureUnit,
|
||||
MaxPoreSize = Record.MaxPoreSize
|
||||
};
|
||||
using var db = new AppDbContext();
|
||||
db.BubblePointRecords.Add(entity);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
public void LoadFromDatabase(int recordId)
|
||||
{
|
||||
using var db = new AppDbContext();
|
||||
var entity = db.BubblePointRecords.Find(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.BubblePointPressure = entity.BubblePointPressure;
|
||||
Record.PressureUnit = entity.PressureUnit;
|
||||
Record.TestDate = entity.TestDate;
|
||||
Record.Tester = entity.Tester;
|
||||
|
||||
OnPropertyChanged(nameof(Record));
|
||||
OnPropertyChanged(nameof(MaxPoreSize)); // 触发界面更新
|
||||
}
|
||||
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
|
||||
public ICommand ExportCommand { get; }
|
||||
|
||||
private void OpenPressureCalibration()
|
||||
{
|
||||
// 使用简单的输入框获取新零点系数和量程系数
|
||||
string zeroStr = Microsoft.VisualBasic.Interaction.InputBox("请输入压力零点系数", "压力校准", "0");
|
||||
string spanStr = Microsoft.VisualBasic.Interaction.InputBox("请输入压力量程系数", "压力校准", "1");
|
||||
if (float.TryParse(zeroStr, out float zero) && float.TryParse(spanStr, out float span))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await WriteFloatAsync(_plcConfig.PressureCalibZero, zero);
|
||||
await WriteFloatAsync(_plcConfig.PressureCalibSpan, span);
|
||||
MessageBox.Show("压力校准系数已写入", "完成");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportToExcel()
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "Excel文件|*.xlsx",
|
||||
FileName = $"泡点法_工位{StationId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
||||
};
|
||||
if (saveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
// 转换为Entity后导出
|
||||
var entity = new BubblePointEntity { /* 从Record复制 */ };
|
||||
ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName);
|
||||
MessageBox.Show("导出成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel; // 用于 PropertyChanged
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<StationItem> Stations { get; } = new();
|
||||
|
||||
public class StationItem : ViewModelBase
|
||||
{
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _plcConfig;
|
||||
private bool _isPressing;
|
||||
private string _pressButtonText = "加压";
|
||||
private string _highLowPressure = "低压";
|
||||
private bool _enableStatus; // M21 状态
|
||||
|
||||
public string Name { get; set; }
|
||||
public BubblePointViewModel BubblePointVM { get; set; }
|
||||
public PoreDistributionViewModel PoreDistributionVM { get; set; }
|
||||
public int StationId { get; set; }
|
||||
|
||||
public string HighLowPressure
|
||||
{
|
||||
get => _highLowPressure;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _highLowPressure, value))
|
||||
{
|
||||
// 当选择变化时,写入 PLC 压力模式寄存器
|
||||
Task.Run(async () => await WritePressureModeAsync(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string PressButtonText
|
||||
{
|
||||
get => _pressButtonText;
|
||||
set => SetProperty(ref _pressButtonText, value);
|
||||
}
|
||||
|
||||
// 使能状态(只读)
|
||||
public bool EnableStatus
|
||||
{
|
||||
get => _enableStatus;
|
||||
private set => SetProperty(ref _enableStatus, value);
|
||||
}
|
||||
// 使能状态显示文本
|
||||
public string EnableStatusText => EnableStatus ? "运行中" : "未启动";
|
||||
|
||||
// 使能状态显示颜色(绿色表示运行中,灰色表示未启动)
|
||||
public string EnableStatusColor => EnableStatus ? "Green" : "Gray";
|
||||
|
||||
// 定时器,用于轮询 M21 状态
|
||||
private System.Windows.Threading.DispatcherTimer _timer;
|
||||
|
||||
public ICommand PressCommand { get; }
|
||||
//public ICommand BurstCommand { get; }
|
||||
public ICommand StartCommand { get; }
|
||||
public ICommand StopCommand { get; }
|
||||
public ICommand EnableCommand { get; } // 备用,但可以使用复选框直接绑定
|
||||
|
||||
public StationItem()
|
||||
{
|
||||
_plcService = App.PlcService;
|
||||
_plcConfig = App.PlcConfig;
|
||||
|
||||
PressCommand = new RelayCommand(async () => await TogglePressAsync());
|
||||
//BurstCommand = new RelayCommand(async () => await ReadBurstPressureAsync());
|
||||
StartCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StartCoil, true));
|
||||
StopCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StopCoil, true));
|
||||
// 启动定时器,每秒读取一次 M21 状态
|
||||
_timer = new System.Windows.Threading.DispatcherTimer();
|
||||
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||
_timer.Tick += async (s, e) => await ReadEnableStatusAsync();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private async Task TogglePressAsync()
|
||||
{
|
||||
_isPressing = !_isPressing;
|
||||
await WriteCoilAsync(_plcConfig.PressCoil, _isPressing);
|
||||
PressButtonText = _isPressing ? "停止加压" : "加压";
|
||||
}
|
||||
|
||||
|
||||
private async Task ReadEnableStatusAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool status = await _plcService.ReadCoilAsync(_plcConfig.EnableCoil); // 读取 M21
|
||||
EnableStatus = status;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 读取出错时保持原状态或显示错误
|
||||
System.Diagnostics.Debug.WriteLine($"读取使能状态失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteCoilAsync(ushort coil, bool value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _plcService.WriteCoilAsync(coil, value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"写线圈失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WritePressureModeAsync(string mode)
|
||||
{
|
||||
ushort val = mode == "高压" ? (ushort)1 : (ushort)0;
|
||||
try
|
||||
{
|
||||
await _plcService.WriteRegisterAsync(_plcConfig.PressureModeRegister, val);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"写压力模式失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
for (int i = 1; i <= 3; i++)
|
||||
{
|
||||
var station = new StationItem
|
||||
{
|
||||
Name = $"工位 {i}",
|
||||
BubblePointVM = new BubblePointViewModel { StationId = i },
|
||||
PoreDistributionVM = new PoreDistributionViewModel { StationId = i },
|
||||
StationId = i
|
||||
};
|
||||
Stations.Add(station);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
308
ViewModels/ParameterViewModel.cs
Normal file
308
ViewModels/ParameterViewModel.cs
Normal file
@@ -0,0 +1,308 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MembranePoreTester.Communication;
|
||||
using Modbus.Device;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _config;
|
||||
private DispatcherTimer _autoRefreshTimer;
|
||||
private bool _isUpdatingFromPlc = false; // 防止定时器更新触发写入
|
||||
|
||||
// 构造函数(依赖注入)
|
||||
public ParameterViewModel()
|
||||
{
|
||||
_plcService = App.PlcService;
|
||||
_config = App.PlcConfig;
|
||||
|
||||
// 启动定时器(每秒自动刷新)
|
||||
_autoRefreshTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
||||
_autoRefreshTimer.Start();
|
||||
}
|
||||
|
||||
// ========== 设备数据属性(全部使用源生成器) ==========
|
||||
[ObservableProperty]
|
||||
private double _upperLampData1;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData2;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData3;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData4;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData5;
|
||||
[ObservableProperty]
|
||||
private double _upperLampData6;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData1;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData2;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData3;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData4;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData5;
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData6;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _middleLamp1;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp2;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp3;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp4;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp5;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp6;
|
||||
[ObservableProperty]
|
||||
private double _middleLamp7;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _leftEyeAreaCoeff;
|
||||
[ObservableProperty]
|
||||
private double _rightEyeAreaCoeff;
|
||||
[ObservableProperty]
|
||||
private double _saveRateCorrectionCoeff;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _motorLimit;
|
||||
[ObservableProperty]
|
||||
private double _resetCompensation;
|
||||
|
||||
// 通信状态(用于界面显示)
|
||||
[ObservableProperty]
|
||||
private string _communicationStatus = "未连接";
|
||||
[ObservableProperty]
|
||||
private System.Windows.Media.SolidColorBrush _statusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
|
||||
|
||||
// ========== 属性变化时的自动写入(用户修改时立即写入PLC) ==========
|
||||
// 这里我们重写属性 setter,在值变化时写入PLC(排除定时器更新)
|
||||
//partial void OnUpperLampData1Changing(double value) => WriteParameterAsync(_config.UpperLampData1, value, false);
|
||||
//partial void OnUpperLampData2Changing(double value) => WriteParameterAsync(_config.UpperLampData2, value, false);
|
||||
// ... 其他灯条属性类似(为了简洁,仅展示几个,您需全部添加)
|
||||
// 您可以使用代码生成工具批量添加,或手动添加所有属性对应的 Changing 方法。
|
||||
// 注意:只有需要自动写入的才添加,只读属性不添加。
|
||||
|
||||
// 为节省篇幅,下面仅示例部分,实际使用时请按此模式为所有需要读写的属性添加
|
||||
partial void OnLowerLampData1Changing(double value) => WriteParameterAsync(_config.LowerLampData1, value, false);
|
||||
partial void OnMiddleLamp1Changing(double value) => WriteParameterAsync(_config.MiddleLamp1, value, false);
|
||||
partial void OnLeftEyeAreaCoeffChanging(double value) => WriteParameterAsync(_config.LeftEyeAreaCoeff, value, true);
|
||||
partial void OnRightEyeAreaCoeffChanging(double value) => WriteParameterAsync(_config.RightEyeAreaCoeff, value, true);
|
||||
partial void OnSaveRateCorrectionCoeffChanging(double value) => WriteParameterAsync(_config.SaveRateCorrectionCoeff, value, true);
|
||||
partial void OnMotorLimitChanging(double value) => WriteParameterAsync(_config.MotorLimit, value, true);
|
||||
partial void OnResetCompensationChanging(double value) => WriteParameterAsync(_config.ResetCompensation, value, true);
|
||||
|
||||
// 写入PLC的通用方法
|
||||
private async Task WriteParameterAsync(ushort address, double value, bool isFloat)
|
||||
{
|
||||
if (_isUpdatingFromPlc) return; // 定时器读取时跳过写入
|
||||
try
|
||||
{
|
||||
if (isFloat)
|
||||
await WriteFloatAsync(address, (float)value);
|
||||
else
|
||||
await WriteInt16Async(address, (ushort)value);
|
||||
System.Diagnostics.Debug.WriteLine($"写入成功: 地址={address}, 值={value}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Windows.MessageBox.Show($"写入参数失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
|
||||
//// ========== 手动保存/读取命令 ==========
|
||||
//[RelayCommand]
|
||||
//private async Task SaveAllParameters()
|
||||
//{
|
||||
// // 将所有当前值写入PLC
|
||||
// // 这里可以遍历所有属性,但为了简单,直接调用各个写入方法
|
||||
// await WriteParameterAsync(_config.UpperLampData1, UpperLampData1, false);
|
||||
// // ... 所有其他属性
|
||||
// CommunicationStatus = "参数已保存";
|
||||
// StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
|
||||
//}
|
||||
|
||||
//[RelayCommand]
|
||||
//private async Task LoadAllParameters()
|
||||
//{
|
||||
// // 从PLC读取所有参数并刷新界面
|
||||
// await RefreshAllFromPlc();
|
||||
// CommunicationStatus = "参数已读取";
|
||||
// StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
|
||||
//}
|
||||
|
||||
// 定时刷新(从PLC读取,但不触发写入)
|
||||
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
await RefreshAllFromPlc();
|
||||
}
|
||||
|
||||
private async Task RefreshAllFromPlc()
|
||||
{
|
||||
_isUpdatingFromPlc = true;
|
||||
try
|
||||
{
|
||||
// 读取并更新每个属性(16位整数和浮点数)
|
||||
UpperLampData1 = await ReadInt16Async(_config.UpperLampData1);
|
||||
UpperLampData2 = await ReadInt16Async(_config.UpperLampData2);
|
||||
UpperLampData3 = await ReadInt16Async(_config.UpperLampData3);
|
||||
UpperLampData4 = await ReadInt16Async(_config.UpperLampData4);
|
||||
UpperLampData5 = await ReadInt16Async(_config.UpperLampData5);
|
||||
UpperLampData6 = await ReadInt16Async(_config.UpperLampData6);
|
||||
|
||||
LowerLampData1 = await ReadInt16Async(_config.LowerLampData1);
|
||||
LowerLampData2 = await ReadInt16Async(_config.LowerLampData2);
|
||||
LowerLampData3 = await ReadInt16Async(_config.LowerLampData3);
|
||||
LowerLampData4 = await ReadInt16Async(_config.LowerLampData4);
|
||||
LowerLampData5 = await ReadInt16Async(_config.LowerLampData5);
|
||||
LowerLampData6 = await ReadInt16Async(_config.LowerLampData6);
|
||||
|
||||
MiddleLamp1 = await ReadInt16Async(_config.MiddleLamp1);
|
||||
MiddleLamp2 = await ReadInt16Async(_config.MiddleLamp2);
|
||||
MiddleLamp3 = await ReadInt16Async(_config.MiddleLamp3);
|
||||
MiddleLamp4 = await ReadInt16Async(_config.MiddleLamp4);
|
||||
MiddleLamp5 = await ReadInt16Async(_config.MiddleLamp5);
|
||||
MiddleLamp6 = await ReadInt16Async(_config.MiddleLamp6);
|
||||
MiddleLamp7 = await ReadInt16Async(_config.MiddleLamp7);
|
||||
|
||||
LeftEyeAreaCoeff = await ReadFloatAsync(_config.LeftEyeAreaCoeff);
|
||||
RightEyeAreaCoeff = await ReadFloatAsync(_config.RightEyeAreaCoeff);
|
||||
SaveRateCorrectionCoeff = await ReadFloatAsync(_config.SaveRateCorrectionCoeff);
|
||||
|
||||
MotorLimit = await ReadFloatAsync(_config.MotorLimit);
|
||||
ResetCompensation = await ReadFloatAsync(_config.ResetCompensation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"刷新参数失败: {ex.Message}");
|
||||
CommunicationStatus = "通信异常";
|
||||
StatusColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingFromPlc = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 设备控制命令(供右侧按钮使用)
|
||||
[RelayCommand]
|
||||
private async Task Reset()
|
||||
{
|
||||
// 实现硬件复位逻辑
|
||||
CommunicationStatus = "复位指令已发送";
|
||||
|
||||
await _plcService.WriteCoilAsync(91, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(91, false);
|
||||
Thread.Sleep(100);
|
||||
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenLeftEye()
|
||||
{
|
||||
CommunicationStatus = "左眼开启指令已发送";
|
||||
await _plcService.WriteCoilAsync(0, true);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenRightEye()
|
||||
{
|
||||
CommunicationStatus = "右眼开启指令已发送";
|
||||
await _plcService.WriteCoilAsync(1, true);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Reverse()
|
||||
{
|
||||
CommunicationStatus = "反转指令已发送";
|
||||
await _plcService.WriteCoilAsync(10, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(10, false);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Forward()
|
||||
{
|
||||
CommunicationStatus = "正转指令已发送";
|
||||
await _plcService.WriteCoilAsync(11, true);
|
||||
Thread.Sleep(100);
|
||||
await _plcService.WriteCoilAsync(11, false);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// 导航命令(可根据实际需求实现)
|
||||
[RelayCommand]
|
||||
private void NavigateHome()
|
||||
{
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateTest()
|
||||
{
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateDataRecord()
|
||||
{
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateRecordScreen()
|
||||
{
|
||||
// 切换页面逻辑
|
||||
}
|
||||
|
||||
// ========== PLC 读写基础方法 ==========
|
||||
private async Task<ushort> ReadInt16Async(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 1);
|
||||
return registers[0];
|
||||
}
|
||||
|
||||
private async Task WriteInt16Async(ushort address, ushort value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteSingleRegisterAsync(address, value);
|
||||
}
|
||||
|
||||
private async Task<float> ReadFloatAsync(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
|
||||
return ((ModbusTcpPlcService)_plcService).UshortToFloat(registers[1], registers[0]);
|
||||
}
|
||||
|
||||
private async Task WriteFloatAsync(ushort address, float value)
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteMultipleRegistersAsync(address, value);
|
||||
}
|
||||
|
||||
// 释放定时器(在窗口关闭时调用)
|
||||
public void Dispose()
|
||||
{
|
||||
_autoRefreshTimer?.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using MembranePoreTester.Helpers;
|
||||
using MembranePoreTester.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public class PoreDistributionViewModel : ViewModelBase, IStationViewModel
|
||||
{
|
||||
private PoreDistributionRecord _record = new();
|
||||
private TestLiquid _selectedLiquid;
|
||||
private bool _isCustomLiquid;
|
||||
private double _customSurfaceTension = 30.0;
|
||||
private double _lowerPore = 0.2;
|
||||
private double _upperPore = 0.8;
|
||||
private double _rangePercentage;
|
||||
|
||||
|
||||
|
||||
// 添加字段
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _plcConfig;
|
||||
private DataPoint _selectedDataPoint;
|
||||
|
||||
|
||||
// 添加属性
|
||||
public DataPoint SelectedDataPoint
|
||||
{
|
||||
get => _selectedDataPoint;
|
||||
set => SetProperty(ref _selectedDataPoint, value);
|
||||
}
|
||||
|
||||
public ICommand ReadPlcCommand { get; }
|
||||
|
||||
public PoreDistributionRecord Record
|
||||
{
|
||||
get => _record;
|
||||
set => SetProperty(ref _record, value);
|
||||
}
|
||||
|
||||
public List<TestLiquid> Liquids => TestLiquid.Predefined;
|
||||
public List<string> PressureUnits => new() { "Pa", "cmHg", "psi" };
|
||||
public List<string> 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; }
|
||||
|
||||
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.PressureUnit = PressureUnits[0]; // 默认 "Pa"
|
||||
|
||||
ReadPlcCommand = new RelayCommand(async () => await ReadPlcAsync());
|
||||
SaveCommand = new RelayCommand(SaveToDatabase);
|
||||
ExportCommand = new RelayCommand(ExportToExcel);
|
||||
OpenFlowCalibCommand = new RelayCommand(OpenFlowCalibration);
|
||||
|
||||
|
||||
Record.DataPoints.CollectionChanged += (s, e) => UpdatePlot();
|
||||
}
|
||||
|
||||
|
||||
private async Task ReadPlcAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 始终读取压力
|
||||
float rawPressure = await _plcService.ReadPressureAsync(StationId);
|
||||
double pressure = rawPressure * _plcConfig.PressureFactor;
|
||||
|
||||
if (SelectedDataPoint != null)
|
||||
{
|
||||
// 更新选中行
|
||||
SelectedDataPoint.Pressure = pressure;
|
||||
if (TestMode == "湿膜")
|
||||
{
|
||||
float rawWet = await _plcService.ReadWetFlowAsync();
|
||||
SelectedDataPoint.WetFlow = rawWet * _plcConfig.WetFlowFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
float rawDry = await _plcService.ReadDryFlowAsync();
|
||||
SelectedDataPoint.DryFlow = rawDry * _plcConfig.DryFlowFactor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 新增一行
|
||||
var newPoint = new DataPoint { Pressure = pressure };
|
||||
if (TestMode == "湿膜")
|
||||
{
|
||||
float rawWet = await _plcService.ReadWetFlowAsync();
|
||||
newPoint.WetFlow = rawWet * _plcConfig.WetFlowFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
float rawDry = await _plcService.ReadDryFlowAsync();
|
||||
newPoint.DryFlow = rawDry * _plcConfig.DryFlowFactor;
|
||||
}
|
||||
Record.DataPoints.Add(newPoint);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDataPoint()
|
||||
{
|
||||
Record.DataPoints.Add(new 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 添加私有字段和公共属性
|
||||
private double _averagePoreSize;
|
||||
public double AveragePoreSize
|
||||
{
|
||||
get => _averagePoreSize;
|
||||
set => SetProperty(ref _averagePoreSize, value);
|
||||
}
|
||||
|
||||
|
||||
// 修改 Calculate 方法:
|
||||
private void Calculate()
|
||||
{
|
||||
if (Record.DataPoints.Count < 2) return;
|
||||
|
||||
AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore(
|
||||
Record.DataPoints, Record.PressureUnit, Record.Liquid);
|
||||
|
||||
RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage(
|
||||
Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore);
|
||||
}
|
||||
|
||||
private void GenerateReport()
|
||||
{
|
||||
ReportGenerator.GeneratePoreDistributionReport(Record);
|
||||
}
|
||||
|
||||
private void OpenFlowCalibration()
|
||||
{
|
||||
string zeroStr = Microsoft.VisualBasic.Interaction.InputBox("请输入流量零点系数", "流量校准", "0");
|
||||
string spanStr = Microsoft.VisualBasic.Interaction.InputBox("请输入流量量程系数", "流量校准", "1");
|
||||
if (float.TryParse(zeroStr, out float zero) && float.TryParse(spanStr, out float span))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await WriteFloatAsync(_plcConfig.FlowCalibZero, zero);
|
||||
await WriteFloatAsync(_plcConfig.FlowCalibSpan, span);
|
||||
MessageBox.Show("流量校准系数已写入", "完成");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private int _stationId;
|
||||
public int StationId
|
||||
{
|
||||
get => _stationId;
|
||||
set => SetProperty(ref _stationId, value);
|
||||
}
|
||||
|
||||
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 DataPoint
|
||||
{
|
||||
Pressure = dp.Pressure,
|
||||
WetFlow = dp.WetFlow,
|
||||
DryFlow = dp.DryFlow
|
||||
});
|
||||
}
|
||||
|
||||
// 重新计算平均孔径和分布(触发计算命令)
|
||||
Calculate();
|
||||
}
|
||||
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
|
||||
|
||||
public ICommand OpenFlowCalibCommand { get; }
|
||||
public ICommand ExportCommand { get; }
|
||||
|
||||
|
||||
|
||||
private void ExportToExcel()
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "Excel文件|*.xlsx",
|
||||
FileName = $"泡点法_工位{StationId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
||||
};
|
||||
if (saveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
// 转换为Entity后导出
|
||||
var entity = new BubblePointEntity { /* 从Record复制 */ };
|
||||
ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName);
|
||||
MessageBox.Show("导出成功");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string _testMode = "湿膜";
|
||||
public string TestMode
|
||||
{
|
||||
get => _testMode;
|
||||
set => SetProperty(ref _testMode, value);
|
||||
}
|
||||
|
||||
private int _selectedFlowModeIndex; // 0=大流量,1=小流量
|
||||
public int SelectedFlowModeIndex
|
||||
{
|
||||
get => _selectedFlowModeIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedFlowModeIndex, value))
|
||||
{
|
||||
ushort reg = StationId switch
|
||||
{
|
||||
1 => _plcConfig.FlowModeRegister1,
|
||||
2 => _plcConfig.FlowModeRegister2,
|
||||
3 => _plcConfig.FlowModeRegister3,
|
||||
_ => 0
|
||||
};
|
||||
Task.Run(async () => await _plcService.WriteSingleRegisterAsync(reg, (ushort)value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private OxyPlot.PlotModel _plotModel;
|
||||
public OxyPlot.PlotModel PlotModel
|
||||
{
|
||||
get => _plotModel;
|
||||
set => SetProperty(ref _plotModel, value);
|
||||
}
|
||||
|
||||
|
||||
private void UpdatePlot()
|
||||
{
|
||||
var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList();
|
||||
if (sorted.Count == 0)
|
||||
{
|
||||
PlotModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var model = new OxyPlot.PlotModel { Title = "流量-压力曲线" };
|
||||
model.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Bottom, Title = "压力" });
|
||||
model.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Left, Title = "流量 (L/min)" });
|
||||
|
||||
var wetSeries = new OxyPlot.Series.LineSeries { Title = "湿膜流量", Color = OxyPlot.OxyColors.Blue, MarkerType = OxyPlot.MarkerType.Circle };
|
||||
var drySeries = new OxyPlot.Series.LineSeries { Title = "干膜流量", Color = OxyPlot.OxyColors.Red, MarkerType = OxyPlot.MarkerType.Circle };
|
||||
|
||||
foreach (var dp in sorted)
|
||||
{
|
||||
wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow));
|
||||
drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow));
|
||||
}
|
||||
|
||||
model.Series.Add(wetSeries);
|
||||
model.Series.Add(drySeries);
|
||||
|
||||
PlotModel = model;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using MembranePoreTester.Communication;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public class StationViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _plcConfig;
|
||||
private bool _isPressing;
|
||||
private string _pressButtonText = "加压";
|
||||
private string _highLowPressure = "低压";
|
||||
private bool _enableChecked;
|
||||
|
||||
public int StationId { get; set; }
|
||||
|
||||
public BubblePointViewModel BubblePointVM { get; } = new();
|
||||
public PoreDistributionViewModel PoreDistributionVM { get; } = new();
|
||||
|
||||
public string HighLowPressure
|
||||
{
|
||||
get => _highLowPressure;
|
||||
set => SetProperty(ref _highLowPressure, value);
|
||||
}
|
||||
|
||||
public string PressButtonText
|
||||
{
|
||||
get => _pressButtonText;
|
||||
set => SetProperty(ref _pressButtonText, value);
|
||||
}
|
||||
|
||||
public bool EnableChecked
|
||||
{
|
||||
get => _enableChecked;
|
||||
set => SetProperty(ref _enableChecked, value);
|
||||
}
|
||||
|
||||
public ICommand PressCommand { get; }
|
||||
public ICommand BurstCommand { get; }
|
||||
public ICommand StartCommand { get; }
|
||||
public ICommand StopCommand { get; }
|
||||
public ICommand EnableCommand { get; } // 用于使能复选框
|
||||
|
||||
public StationViewModel()
|
||||
{
|
||||
_plcService = App.PlcService;
|
||||
_plcConfig = App.PlcConfig;
|
||||
BubblePointVM.StationId = StationId;
|
||||
PoreDistributionVM.StationId = StationId;
|
||||
// 初始化按钮文字
|
||||
_pressButtonText = "加压"; // 直接设置字段,避免触发属性通知循环
|
||||
PressButtonText = "加压"; // 或者通过属性设置
|
||||
System.Diagnostics.Debug.WriteLine($"工位{StationId} PressButtonText初始值: {PressButtonText}");
|
||||
PressCommand = new RelayCommand(async () => await TogglePressAsync());
|
||||
BurstCommand = new RelayCommand(async () => await ReadBurstPressureAsync());
|
||||
StartCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StartCoil, true));
|
||||
StopCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StopCoil, true));
|
||||
// 使能复选框点击时写入对应线圈
|
||||
EnableCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.EnableCoil, EnableChecked));
|
||||
}
|
||||
|
||||
private async Task TogglePressAsync()
|
||||
{
|
||||
_isPressing = !_isPressing;
|
||||
await WriteCoilAsync(_plcConfig.PressCoil, _isPressing);
|
||||
PressButtonText = _isPressing ? "停止加压" : "加压";
|
||||
}
|
||||
|
||||
private async Task ReadBurstPressureAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
float pressure = await ((ModbusTcpPlcService)_plcService).ReadPressureAsync(StationId);
|
||||
BubblePointVM.UpdateBubblePointPressure(pressure * _plcConfig.PressureFactor);
|
||||
MessageBox.Show($"涨破压力: {pressure} {BubblePointVM.Record.PressureUnit}", "提示");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"读取压力失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteCoilAsync(ushort coil, bool value)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ((ModbusTcpPlcService)_plcService).WriteCoilAsync(coil, value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"写线圈失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
263
ViewModels/TestViewModel.cs
Normal file
263
ViewModels/TestViewModel.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MembranePoreTester
|
||||
{
|
||||
public partial class TestViewModel : ObservableObject
|
||||
{
|
||||
private readonly DispatcherTimer _angleUpdateTimer;
|
||||
private bool _isTestRunning;
|
||||
|
||||
public TestViewModel()
|
||||
{
|
||||
_angleUpdateTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(100)
|
||||
};
|
||||
_angleUpdateTimer.Tick += AngleUpdateTimer_Tick;
|
||||
|
||||
// 默认值
|
||||
ResolutionAngle = 0.1;
|
||||
RotationSpeed = 30;
|
||||
LeftEyeCoeff = 1.0;
|
||||
RightEyeCoeff = 1.0;
|
||||
RetentionCorrectionCoeff = 1.0;
|
||||
IsSampleMode = true;
|
||||
}
|
||||
|
||||
// 当前模式显示文本
|
||||
[ObservableProperty]
|
||||
private string _currentMode = "试样测试";
|
||||
|
||||
// 模式切换
|
||||
private bool _isBlankMode;
|
||||
public bool IsBlankMode
|
||||
{
|
||||
get => _isBlankMode;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isBlankMode, value) && value)
|
||||
{
|
||||
CurrentMode = "空白模式";
|
||||
IsSampleMode = false;
|
||||
OnModeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isSampleMode;
|
||||
public bool IsSampleMode
|
||||
{
|
||||
get => _isSampleMode;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isSampleMode, value) && value)
|
||||
{
|
||||
CurrentMode = "试样测试";
|
||||
IsBlankMode = false;
|
||||
OnModeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 参数设置
|
||||
[ObservableProperty]
|
||||
private double _resolutionAngle;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _rotationSpeed;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _currentAngle;
|
||||
|
||||
// 视野面积数据
|
||||
[ObservableProperty]
|
||||
private double _leftEyeArea;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _rightEyeArea;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _binocularArea;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _lowerField;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _blankArea;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _fieldRetentionRate;
|
||||
|
||||
// 灯条数据
|
||||
[ObservableProperty]
|
||||
private double _upperLampData;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _middleLampData;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _lowerLampData;
|
||||
|
||||
// 面积系数
|
||||
[ObservableProperty]
|
||||
private double _leftEyeCoeff;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _rightEyeCoeff;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _retentionCorrectionCoeff;
|
||||
|
||||
// 命令
|
||||
[RelayCommand]
|
||||
private void Reset()
|
||||
{
|
||||
CurrentAngle = 0;
|
||||
// 用户在此添加硬件复位代码
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenLeftEye()
|
||||
{
|
||||
// 用户实现左眼开逻辑
|
||||
UpdateLeftEyeArea();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenRightEye()
|
||||
{
|
||||
// 用户实现右眼开逻辑
|
||||
UpdateRightEyeArea();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Reverse()
|
||||
{
|
||||
// 反转方向
|
||||
RotationSpeed = -Math.Abs(RotationSpeed);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Forward()
|
||||
{
|
||||
// 正转方向
|
||||
RotationSpeed = Math.Abs(RotationSpeed);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void StartTest()
|
||||
{
|
||||
if (_isTestRunning) return;
|
||||
_isTestRunning = true;
|
||||
_angleUpdateTimer.Start();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void StopTest()
|
||||
{
|
||||
_isTestRunning = false;
|
||||
_angleUpdateTimer.Stop();
|
||||
}
|
||||
|
||||
// 导航命令(供用户跳转页面)
|
||||
[RelayCommand]
|
||||
private void NavigateHome()
|
||||
{
|
||||
// 用户实现:关闭当前窗口或切换主窗口内容
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateTest()
|
||||
{
|
||||
// 已在测试界面,可忽略
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateData()
|
||||
{
|
||||
// 跳转到数据记录界面
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateRecord()
|
||||
{
|
||||
// 跳转到记录画面
|
||||
}
|
||||
|
||||
// 内部辅助方法
|
||||
private void OnModeChanged()
|
||||
{
|
||||
if (IsBlankMode)
|
||||
{
|
||||
BlankArea = 0;
|
||||
FieldRetentionRate = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
CalculateFieldAreas();
|
||||
}
|
||||
}
|
||||
|
||||
private void AngleUpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (!_isTestRunning) return;
|
||||
|
||||
// 模拟角度变化
|
||||
CurrentAngle += RotationSpeed * 0.1;
|
||||
if (CurrentAngle >= 360) CurrentAngle -= 360;
|
||||
if (CurrentAngle < 0) CurrentAngle += 360;
|
||||
|
||||
CalculateFieldAreas();
|
||||
UpdateLampData();
|
||||
}
|
||||
|
||||
private void CalculateFieldAreas()
|
||||
{
|
||||
double angle = CurrentAngle;
|
||||
LeftEyeArea = Math.Round(50 + 30 * Math.Sin(angle * Math.PI / 180) * LeftEyeCoeff, 3);
|
||||
RightEyeArea = Math.Round(50 + 30 * Math.Cos(angle * Math.PI / 180) * RightEyeCoeff, 3);
|
||||
BinocularArea = Math.Round(LeftEyeArea + RightEyeArea - 20, 3);
|
||||
LowerField = Math.Round(30 + 20 * Math.Sin(angle * Math.PI / 180), 1);
|
||||
|
||||
if (IsBlankMode)
|
||||
{
|
||||
BlankArea = Math.Round(10 + 5 * Math.Sin(angle * Math.PI / 180), 3);
|
||||
FieldRetentionRate = BinocularArea > 0
|
||||
? Math.Round((BinocularArea - BlankArea) / BinocularArea * 100, 2)
|
||||
: 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldRetentionRate = Math.Round(BinocularArea / 100 * 100 * RetentionCorrectionCoeff, 2);
|
||||
if (FieldRetentionRate > 100) FieldRetentionRate = 100;
|
||||
if (FieldRetentionRate < 0) FieldRetentionRate = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLeftEyeArea()
|
||||
{
|
||||
LeftEyeArea = Math.Round(LeftEyeArea + 5, 3);
|
||||
if (LeftEyeArea > 100) LeftEyeArea = 100;
|
||||
CalculateFieldAreas();
|
||||
}
|
||||
|
||||
private void UpdateRightEyeArea()
|
||||
{
|
||||
RightEyeArea = Math.Round(RightEyeArea + 5, 3);
|
||||
if (RightEyeArea > 100) RightEyeArea = 100;
|
||||
CalculateFieldAreas();
|
||||
}
|
||||
|
||||
private void UpdateLampData()
|
||||
{
|
||||
Random rand = new Random();
|
||||
UpperLampData = Math.Round(rand.NextDouble() * 100, 2);
|
||||
MiddleLampData = Math.Round(rand.NextDouble() * 100, 2);
|
||||
LowerLampData = Math.Round(rand.NextDouble() * 100, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using MembranePoreTester.Communication;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MembranePoreTester.ViewModels
|
||||
{
|
||||
public class ViewModelBase : INotifyPropertyChanged
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<UserControl x:Class="MembranePoreTester.Views.BubblePointView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MembranePoreTester.ViewModels"
|
||||
xmlns:conv="clr-namespace:MembranePoreTester.Converters">
|
||||
<UserControl.Resources>
|
||||
<conv:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<StackPanel Grid.Row="0">
|
||||
<GroupBox Header="样品信息" Margin="0,0,0,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="膜类型:"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding MembraneTypes}"
|
||||
SelectedItem="{Binding Record.SampleType}" Margin="5"/>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="规格:"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="3" Text="{Binding Record.SampleSpec}" Margin="5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="室温(°C):"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Record.RoomTemperature}" Margin="5"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="浸润时间(h):"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="3" Text="{Binding Record.SoakingTime}" Margin="5"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="测试液体" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<RadioButton Content="预定义" IsChecked="{Binding IsCustomLiquid, Converter={StaticResource InverseBooleanConverter}}" Margin="5"/>
|
||||
<ComboBox ItemsSource="{Binding Liquids}" SelectedItem="{Binding SelectedLiquid}"
|
||||
DisplayMemberPath="Name" IsEnabled="{Binding IsCustomLiquid, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Margin="5,0,5,5"/>
|
||||
<RadioButton Content="自定义" IsChecked="{Binding IsCustomLiquid}" Margin="5"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="20,0,0,0" IsEnabled="{Binding IsCustomLiquid}">
|
||||
<Label Content="表面张力(mN/m):"/>
|
||||
<TextBox Text="{Binding CustomSurfaceTension}" Width="100" Margin="5"/>
|
||||
</StackPanel>
|
||||
<!--<Label Content="生产厂家:" Margin="5"/>
|
||||
<TextBox Text="{Binding Record.LiquidManufacturer}" Margin="5"/>-->
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="泡点压力" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding Record.BubblePointPressure}" Width="150" Margin="5"/>
|
||||
|
||||
<Button Content="涨破" Command="{Binding ReadPlcCommand}" Padding="10,5" Margin="5"/>
|
||||
<ComboBox ItemsSource="{Binding PressureUnits}" SelectedItem="{Binding Record.PressureUnit}" Width="80" Margin="5"/>
|
||||
<Button Content="计算最大孔径" Command="{Binding CalculateCommand}" Padding="10,5" Margin="5"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 结果显示 -->
|
||||
<GroupBox Grid.Row="1" Header="测试结果" VerticalAlignment="Center">
|
||||
<TextBlock FontSize="36" TextAlignment="Center"
|
||||
Text="{Binding MaxPoreSize, StringFormat={}{0:F3} μm}"/>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="压力校准" Command="{Binding OpenPressureCalibCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="保存到历史" Command="{Binding SaveCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="生成报告" Command="{Binding GenerateReportCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="导出Excel" Command="{Binding ExportCommand}" Padding="15,8" Margin="5"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
{
|
||||
public partial class BubblePointView : UserControl
|
||||
{
|
||||
public BubblePointView()
|
||||
{
|
||||
InitializeComponent();
|
||||
//DataContext = new ViewModels.BubblePointViewModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<Window x:Class="MembranePoreTester.Views.HistoryWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="历史记录" Height="600" Width="900"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 过滤条件 -->
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,10">
|
||||
<Label Content="工位:"/>
|
||||
<ComboBox x:Name="cmbStation" SelectedIndex="0" Width="80" Margin="5,0">
|
||||
<ComboBoxItem Content="全部"/>
|
||||
<ComboBoxItem Content="1"/>
|
||||
<ComboBoxItem Content="2"/>
|
||||
<ComboBoxItem Content="3"/>
|
||||
</ComboBox>
|
||||
<Label Content="测试类型:"/>
|
||||
<ComboBox x:Name="cmbType" SelectedIndex="0" Width="120" Margin="5,0">
|
||||
<ComboBoxItem Content="全部"/>
|
||||
<ComboBoxItem Content="泡点法"/>
|
||||
<ComboBoxItem Content="孔分布"/>
|
||||
</ComboBox>
|
||||
<Label Content="日期范围:"/>
|
||||
<DatePicker x:Name="dpStart" Width="120"/>
|
||||
<Label Content="-" Margin="2"/>
|
||||
<DatePicker x:Name="dpEnd" Width="120"/>
|
||||
<Button Content="查询" Click="Query_Click" Padding="15,5" Margin="10,0"/>
|
||||
<Button Content="导出选中" Click="ExportSelected_Click" Padding="15,5"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 历史记录列表 -->
|
||||
<DataGrid x:Name="dgHistory" Grid.Row="1" AutoGenerateColumns="False"
|
||||
IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="50"/>
|
||||
<DataGridTextColumn Header="工位" Binding="{Binding StationId}" Width="50"/>
|
||||
<DataGridTextColumn Header="类型" Binding="{Binding Type}" Width="80"/>
|
||||
<DataGridTextColumn Header="测试日期" Binding="{Binding TestDate}" Width="150"/>
|
||||
<DataGridTextColumn Header="样品类型" Binding="{Binding SampleType}" Width="100"/>
|
||||
<DataGridTextColumn Header="规格" Binding="{Binding SampleSpec}" Width="100"/>
|
||||
<DataGridTextColumn Header="测试者" Binding="{Binding Tester}" Width="100"/>
|
||||
<DataGridTextColumn Header="结果" Binding="{Binding Result}" Width="150"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<Button Content="加载到当前工位" Click="LoadToCurrentStation_Click" Padding="15,5" Margin="5"/>
|
||||
<Button Content="关闭" Click="Close_Click" Padding="15,5"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,156 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
{
|
||||
public partial class HistoryWindow : Window
|
||||
{
|
||||
public int? SelectedStation { get; set; } // 传入当前工位ID
|
||||
|
||||
public HistoryWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Query_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using var db = new AppDbContext();
|
||||
var query = new List<object>();
|
||||
|
||||
// 获取泡点法记录
|
||||
var bubblePoints = db.BubblePointRecords
|
||||
.Select(b => new
|
||||
{
|
||||
b.Id,
|
||||
b.StationId,
|
||||
Type = "泡点法",
|
||||
b.TestDate,
|
||||
b.SampleType,
|
||||
b.SampleSpec,
|
||||
b.Tester,
|
||||
Result = $"最大孔径: {b.MaxPoreSize:F3} μm"
|
||||
});
|
||||
query.AddRange(bubblePoints);
|
||||
|
||||
// 获取孔分布记录
|
||||
var poreDistributions = db.PoreDistributionRecords
|
||||
.Select(p => new
|
||||
{
|
||||
p.Id,
|
||||
p.StationId,
|
||||
Type = "孔分布",
|
||||
p.TestDate,
|
||||
p.SampleType,
|
||||
p.SampleSpec,
|
||||
p.Tester,
|
||||
Result = $"平均孔径: {p.AveragePoreSize:F3} μm"
|
||||
});
|
||||
query.AddRange(poreDistributions);
|
||||
|
||||
// 过滤工位
|
||||
if (cmbStation.SelectedIndex > 0)
|
||||
{
|
||||
int station = cmbStation.SelectedIndex;
|
||||
query = query.Where(x => ((dynamic)x).StationId == station).ToList();
|
||||
}
|
||||
|
||||
// 过滤类型
|
||||
if (cmbType.SelectedIndex == 1)
|
||||
query = query.Where(x => ((dynamic)x).Type == "泡点法").ToList();
|
||||
else if (cmbType.SelectedIndex == 2)
|
||||
query = query.Where(x => ((dynamic)x).Type == "孔分布").ToList();
|
||||
|
||||
// 过滤日期范围
|
||||
if (dpStart.SelectedDate != null)
|
||||
query = query.Where(x => ((dynamic)x).TestDate >= dpStart.SelectedDate).ToList();
|
||||
if (dpEnd.SelectedDate != null)
|
||||
query = query.Where(x => ((dynamic)x).TestDate <= dpEnd.SelectedDate.Value.AddDays(1)).ToList();
|
||||
|
||||
dgHistory.ItemsSource = query.OrderByDescending(x => ((dynamic)x).TestDate).ToList();
|
||||
}
|
||||
|
||||
private void ExportSelected_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (dgHistory.SelectedItem == null)
|
||||
{
|
||||
MessageBox.Show("请先选中一条记录");
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic selected = dgHistory.SelectedItem;
|
||||
int id = selected.Id;
|
||||
string type = selected.Type;
|
||||
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "Excel文件|*.xlsx",
|
||||
FileName = $"{type}_{id}.xlsx"
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
if (type == "泡点法")
|
||||
{
|
||||
using var db = new AppDbContext();
|
||||
var entity = db.BubblePointRecords.Find(id);
|
||||
if (entity != null)
|
||||
{
|
||||
ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using var db = new AppDbContext();
|
||||
var entity = db.PoreDistributionRecords
|
||||
.Include(p => p.DataPoints)
|
||||
.FirstOrDefault(p => p.Id == id);
|
||||
if (entity != null)
|
||||
{
|
||||
ExportHelper.ExportPoreDistribution(entity, saveFileDialog.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadToCurrentStation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (dgHistory.SelectedItem == null) return;
|
||||
if (SelectedStation == null)
|
||||
{
|
||||
MessageBox.Show("未指定当前工位");
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic selected = dgHistory.SelectedItem;
|
||||
int id = selected.Id;
|
||||
string type = selected.Type;
|
||||
|
||||
// 通过事件或回调将记录加载到当前工位
|
||||
// 这里需要与主窗口交互,简单起见,我们使用静态事件
|
||||
LoadRecordEvent?.Invoke(this, new LoadRecordEventArgs
|
||||
{
|
||||
RecordId = id,
|
||||
Type = type,
|
||||
TargetStation = SelectedStation.Value
|
||||
});
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
public static event EventHandler<LoadRecordEventArgs> LoadRecordEvent;
|
||||
}
|
||||
|
||||
public class LoadRecordEventArgs : EventArgs
|
||||
{
|
||||
public int RecordId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int TargetStation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<Window x:Class="MembranePoreTester.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MembranePoreTester.Views"
|
||||
xmlns:viewModels="clr-namespace:MembranePoreTester.ViewModels"
|
||||
Title="膜孔径测试系统 (GB/T 32361-2015)"
|
||||
Width="1024" Height="768"
|
||||
WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown">
|
||||
<Window.DataContext>
|
||||
<viewModels:MainViewModel />
|
||||
</Window.DataContext>
|
||||
<DockPanel>
|
||||
<!-- 全局历史记录按钮 -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
|
||||
<Button Content="历史记录" Click="OpenHistory_Click" Padding="10,5" Margin="5"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 工位选项卡 -->
|
||||
<TabControl x:Name="stationTabControl" ItemsSource="{Binding Stations}">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<DockPanel>
|
||||
<!-- 工位控制栏 -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
|
||||
<ComboBox SelectedItem="{Binding HighLowPressure}" Width="80" Margin="5">
|
||||
<ComboBoxItem IsSelected="True">低压</ComboBoxItem>
|
||||
<ComboBoxItem>高压</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<Button Content="启动" Command="{Binding StartCommand}" Width="80" Margin="5"/>
|
||||
<Button Content="停止" Command="{Binding StopCommand}" Width="80" Margin="5"/>
|
||||
|
||||
|
||||
<Button Content="{Binding PressButtonText}" Command="{Binding PressCommand}" Width="80" Margin="5"/>
|
||||
<!--<Button Content="涨破" Command="{Binding BurstCommand}" Width="80" Margin="5"/>-->
|
||||
|
||||
<!--<CheckBox Content="使能" IsChecked="{Binding EnableChecked}" Margin="5"/>-->
|
||||
<!-- 原来的复选框 -->
|
||||
<!-- <CheckBox Content="使能" IsChecked="{Binding EnableChecked}" Margin="5"/> -->
|
||||
|
||||
<!-- 替换为状态指示灯 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="5">
|
||||
<Ellipse Width="12" Height="12" Fill="{Binding EnableStatusColor}" Margin="2"/>
|
||||
<TextBlock Text="{Binding EnableStatusText}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 测试类型选项卡 -->
|
||||
<TabControl>
|
||||
<TabItem Header="泡点法测试最大孔径">
|
||||
<local:BubblePointView DataContext="{Binding BubblePointVM}"/>
|
||||
</TabItem>
|
||||
<TabItem Header="孔分布测试">
|
||||
<local:PoreDistributionView DataContext="{Binding PoreDistributionVM}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -1,57 +0,0 @@
|
||||
using MembranePoreTester.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
HistoryWindow.LoadRecordEvent += OnLoadRecord;
|
||||
}
|
||||
|
||||
private void OnLoadRecord(object sender, LoadRecordEventArgs e)
|
||||
{
|
||||
var mainVM = DataContext as MainViewModel;
|
||||
if (mainVM == null) return;
|
||||
|
||||
// 根据工位和类型找到对应的 ViewModel
|
||||
if (e.Type == "泡点法")
|
||||
{
|
||||
var targetVM = mainVM.Stations[e.TargetStation - 1].BubblePointVM;
|
||||
targetVM.LoadFromDatabase(e.RecordId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetVM = mainVM.Stations[e.TargetStation - 1].PoreDistributionVM;
|
||||
targetVM.LoadFromDatabase(e.RecordId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OpenHistory_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainVM = DataContext as MainViewModel;
|
||||
if (mainVM == null) return;
|
||||
|
||||
// 获取当前选中的工位索引(假设选项卡控件是 TabControl,名称为 stationTabControl)
|
||||
// 需要在 XAML 中为 TabControl 设置 x:Name="stationTabControl"
|
||||
int currentStation = stationTabControl.SelectedIndex + 1; // 假设索引从0开始
|
||||
var historyWin = new HistoryWindow { SelectedStation = currentStation };
|
||||
historyWin.ShowDialog();
|
||||
}
|
||||
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.P)
|
||||
{
|
||||
var win = new ParameterWindow();
|
||||
win.Owner = this;
|
||||
win.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1,280 @@
|
||||
<Window x:Class="MembranePoreTester.Views.ParameterWindow"
|
||||
<Window x:Class="MembranePoreTester.ParameterWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="运维参数设置" Height="600" Width="800"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="10">
|
||||
<GroupBox Header="加压控制">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Row="0" Grid.Column="0">加压上限(D300):</Label>
|
||||
<TextBox x:Name="txtPressureUpper" Grid.Row="0" Grid.Column="1"/>
|
||||
<Label Grid.Row="1" Grid.Column="0">加压速率(D280):</Label>
|
||||
<TextBox x:Name="txtPressureRate" Grid.Row="1" Grid.Column="1"/>
|
||||
<Label Grid.Row="2" Grid.Column="0">加压系数(D282):</Label>
|
||||
<TextBox x:Name="txtPressureCoeff" Grid.Row="2" Grid.Column="1"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
xmlns:local="clr-namespace:MembranePoreTester"
|
||||
Title="智能设备控制平台"
|
||||
Width="1024" MinHeight="768" WindowState="Maximized"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize"
|
||||
Background="#F5F7FA" FontFamily="Segoe UI">
|
||||
|
||||
<GroupBox Header="高压/低压系数">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<!-- 预留空白 -->
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Window.Resources>
|
||||
<!-- 统一按钮样式 -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#2196F3"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="12,6"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#1976D2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#0D47A1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" FontWeight="Bold">工位</Label>
|
||||
<Label Grid.Row="0" Grid.Column="1" FontWeight="Bold">高压系数</Label>
|
||||
<Label Grid.Row="0" Grid.Column="2" FontWeight="Bold">低压系数</Label>
|
||||
<!-- 统一文本框样式 -->
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D0D3D9"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="5,2"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0">工位1</Label>
|
||||
<TextBox x:Name="txtHPCoeff1" Grid.Row="1" Grid.Column="1" Width="80" HorizontalAlignment="Left"/>
|
||||
<TextBox x:Name="txtLPCoeff1" Grid.Row="1" Grid.Column="2" Width="80" HorizontalAlignment="Left"/>
|
||||
<!-- 统一标签样式 -->
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Foreground" Value="#2C3E50"/>
|
||||
<Setter Property="Margin" Value="5,5,5,5"/>
|
||||
</Style>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0">工位2</Label>
|
||||
<TextBox x:Name="txtHPCoeff2" Grid.Row="2" Grid.Column="1" Width="80" HorizontalAlignment="Left"/>
|
||||
<TextBox x:Name="txtLPCoeff2" Grid.Row="2" Grid.Column="2" Width="80" HorizontalAlignment="Left"/>
|
||||
<!-- 统一GroupBox样式 -->
|
||||
<Style TargetType="GroupBox">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#E9ECF0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Margin" Value="0,0,0,12"/>
|
||||
<Setter Property="Padding" Value="12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupBox">
|
||||
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="{TemplateBinding Header}" FontWeight="SemiBold" FontSize="14" Margin="0,0,0,12"/>
|
||||
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0">工位3</Label>
|
||||
<TextBox x:Name="txtHPCoeff3" Grid.Row="3" Grid.Column="1" Width="80" HorizontalAlignment="Left"/>
|
||||
<TextBox x:Name="txtLPCoeff3" Grid.Row="3" Grid.Column="2" Width="80" HorizontalAlignment="Left"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Header="流量系数">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Row="0" Grid.Column="0" FontWeight="Bold">工位</Label>
|
||||
<Label Grid.Row="0" Grid.Column="1" FontWeight="Bold">大流量系数</Label>
|
||||
<Label Grid.Row="0" Grid.Column="2" FontWeight="Bold">小流量系数</Label>
|
||||
<Label Grid.Row="1" Grid.Column="0">工位1</Label>
|
||||
<TextBox x:Name="txtLargeFlow1" Grid.Row="1" Grid.Column="1"/>
|
||||
<TextBox x:Name="txtSmallFlow1" Grid.Row="1" Grid.Column="2"/>
|
||||
<Label Grid.Row="2" Grid.Column="0">工位2</Label>
|
||||
<TextBox x:Name="txtLargeFlow2" Grid.Row="2" Grid.Column="1"/>
|
||||
<TextBox x:Name="txtSmallFlow2" Grid.Row="2" Grid.Column="2"/>
|
||||
<Label Grid.Row="3" Grid.Column="0">工位3</Label>
|
||||
<TextBox x:Name="txtLargeFlow3" Grid.Row="3" Grid.Column="1"/>
|
||||
<TextBox x:Name="txtSmallFlow3" Grid.Row="3" Grid.Column="2"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="校准参数">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Row="0" Grid.Column="0">压力零点校准:</Label>
|
||||
<TextBox x:Name="txtPressureZero" Grid.Row="0" Grid.Column="1"/>
|
||||
<Label Grid.Row="1" Grid.Column="0">压力量程校准:</Label>
|
||||
<TextBox x:Name="txtPressureSpan" Grid.Row="1" Grid.Column="1"/>
|
||||
<Label Grid.Row="2" Grid.Column="0">流量零点校准:</Label>
|
||||
<TextBox x:Name="txtFlowZero" Grid.Row="2" Grid.Column="1"/>
|
||||
<Label Grid.Row="3" Grid.Column="0">流量量程校准:</Label>
|
||||
<TextBox x:Name="txtFlowSpan" Grid.Row="3" Grid.Column="1"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,0">
|
||||
<!--<Button Content="读取参数" Click="ReadParameters_Click" Width="100" Margin="5"/>-->
|
||||
<Button Content="写入参数" Click="WriteParameters_Click" Width="100" Margin="5"/>
|
||||
<Button Content="关闭" Click="Close_Click" Width="100" Margin="5"/>
|
||||
<!-- 标题栏 -->
|
||||
<Border Grid.Row="0" Background="#2196F3" CornerRadius="6" Padding="12" Margin="0,0,0,12">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock Text="智能设备控制平台" FontSize="24" FontWeight="Bold" Foreground="White"/>
|
||||
<TextBlock Text="v1.0" FontSize="14" Foreground="#E3F2FD" VerticalAlignment="Bottom" Margin="10,0,0,5"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- 主要内容区 -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2.5*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧参数区 -->
|
||||
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="0,0,12,0">
|
||||
<!-- 灯条数据组 -->
|
||||
<GroupBox Header="📊 灯条数据">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 第1行 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="上灯条数据1"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData1" Grid.Row="0" Grid.Column="1" Text="{Binding UpperLampData1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="上灯条数据4"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData4" Grid.Row="0" Grid.Column="3" Text="{Binding UpperLampData4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第2行 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="下灯条数据1"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData1" Grid.Column="1" Text="{Binding LowerLampData1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="下灯条数据4"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData4" Grid.Column="3" Text="{Binding LowerLampData4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第3行 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="上灯条数据2"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData2" Grid.Row="2" Grid.Column="1" Text="{Binding UpperLampData2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="上灯条数据5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData5" Grid.Row="2" Grid.Column="3" Text="{Binding UpperLampData5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第4行 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="下灯条数据2"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="3" Name="txtLowerLampData2" Grid.Column="1" Text="{Binding LowerLampData2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="3" Grid.Column="2" Content="下灯条数据5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData5" Grid.Row="3" Grid.Column="3" Text="{Binding LowerLampData5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第5行 -->
|
||||
<Label Grid.Row="4" Grid.Column="0" Content="上灯条数据3"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData3" Grid.Row="4" Grid.Column="1" Text="{Binding UpperLampData3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="4" Grid.Column="2" Content="上灯条数据6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtUpperLampData6" Grid.Row="4" Grid.Column="3" Text="{Binding UpperLampData6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第6行 -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="下灯条数据3"/>
|
||||
<TextBox IsReadOnly="True" Grid.Row="5" Name="txtLowerLampData3" Grid.Column="1" Text="{Binding LowerLampData3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="5" Grid.Column="2" Content="下灯条数据6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtLowerLampData6" Grid.Row="5" Grid.Column="3" Text="{Binding LowerLampData6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 面积系数与保存率矫正系数 -->
|
||||
<GroupBox Header="📐 面积系数">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="左眼面积系数"/>
|
||||
<TextBox x:Name="txtLeftEyeAreaCoeff" Grid.Row="0" Grid.Column="1" Text="{Binding LeftEyeAreaCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="右眼面积系数"/>
|
||||
<TextBox x:Name="txtRightEyeAreaCoeff" Grid.Row="1" Grid.Column="1" Text="{Binding RightEyeAreaCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="保存率矫正系数"/>
|
||||
<TextBox x:Name="txtSaveRateCorrectionCoeff" Grid.Row="2" Grid.Column="1" Text="{Binding SaveRateCorrectionCoeff, UpdateSourceTrigger=LostFocus}" Width="120"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 中灯数据组 -->
|
||||
<GroupBox Header="💡 中灯数据">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 第1行 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="中灯1"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp1" Grid.Row="0" Grid.Column="1" Text="{Binding MiddleLamp1, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="中灯5"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp5" Grid.Row="0" Grid.Column="3" Text="{Binding MiddleLamp5, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第2行 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="中灯2"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp2" Grid.Row="1" Grid.Column="1" Text="{Binding MiddleLamp2, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="中灯6"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp6" Grid.Row="1" Grid.Column="3" Text="{Binding MiddleLamp6, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第3行 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="中灯3"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp3" Grid.Row="2" Grid.Column="1" Text="{Binding MiddleLamp3, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="中灯7"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp7" Grid.Row="2" Grid.Column="3" Text="{Binding MiddleLamp7, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
|
||||
<!-- 第4行 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="中灯4"/>
|
||||
<TextBox IsReadOnly="True" Name="txtMiddleLamp4" Grid.Row="3" Grid.Column="1" Text="{Binding MiddleLamp4, UpdateSourceTrigger=LostFocus}" Width="80"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 右侧控制区 -->
|
||||
<StackPanel Grid.Column="1" Margin="12,0,0,0">
|
||||
<!-- 电机状态 -->
|
||||
<GroupBox Header="⚙️ 电机状态">
|
||||
<StackPanel Margin="5">
|
||||
<Label Content="电机限位" FontWeight="SemiBold"/>
|
||||
<TextBox x:Name="txtMotorLimit" Text="{Binding MotorLimit, UpdateSourceTrigger=LostFocus}" Height="28" Margin="0,0,0,12"/>
|
||||
<Label Content="复位补偿" FontWeight="SemiBold"/>
|
||||
<TextBox x:Name="txtResetCompensation" Text="{Binding ResetCompensation, UpdateSourceTrigger=LostFocus}" Height="28" Margin="0,0,0,12"/>
|
||||
<!--<Button Content="💾 保存参数" Command="{Binding SaveParametersCommand}" Margin="0,5" Background="#4CAF50"/>
|
||||
<Button Content="🔄 读取参数" Command="{Binding LoadParametersCommand}" Margin="0,5" Background="#FF9800"/>-->
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 控制按钮组 -->
|
||||
<GroupBox Header="🎮 设备控制">
|
||||
<WrapPanel Margin="5">
|
||||
<Button Content="🔄 复位" Command="{Binding ResetCommand}" Width="80" Background="#607D8B"/>
|
||||
<Button Content="👁️ 左眼开" Command="{Binding OpenLeftEyeCommand}" Width="80" Background="#4CAF50"/>
|
||||
<Button Content="↩️ 反转" Command="{Binding ReverseCommand}" Width="80" Background="#FF9800"/>
|
||||
<Button Content="👁️ 右眼开" Command="{Binding OpenRightEyeCommand}" Width="80" Background="#4CAF50"/>
|
||||
<Button Content="↪️ 正转" Command="{Binding ForwardCommand}" Width="80" Background="#FF9800"/>
|
||||
</WrapPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 通信状态 -->
|
||||
<!--<GroupBox Header="📡 通信状态">
|
||||
<Border Background="#F8F9FA" CornerRadius="4" Padding="10" Margin="5">
|
||||
<StackPanel>
|
||||
<Ellipse Width="12" Height="12" Fill="{Binding StatusColor}" Margin="0,0,0,5" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="{Binding CommunicationStatus}" FontWeight="Bold" Foreground="{Binding StatusColor}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</GroupBox>-->
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<Border Grid.Row="2" Background="White" CornerRadius="6" Padding="10" Margin="0,12,0,0" BorderBrush="#E9ECF0" BorderThickness="1">
|
||||
<WrapPanel HorizontalAlignment="Center">
|
||||
<Button Content="🏠 主页" Command="{Binding NavigateHomeCommand}" Width="100" Background="#607D8B"/>
|
||||
<Button Content="🧪 测试界面" Command="{Binding NavigateTestCommand}" Width="100" Background="#2196F3"/>
|
||||
<Button Content="📊 数据记录" Command="{Binding NavigateDataRecordCommand}" Width="100" Background="#4CAF50"/>
|
||||
<Button Content="📸 记录画面" Command="{Binding NavigateRecordScreenCommand}" Width="100" Background="#FF9800"/>
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,186 +1,18 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using MembranePoreTester.ViewModels;
|
||||
using System;
|
||||
using MembranePoreTester.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
namespace MembranePoreTester
|
||||
{
|
||||
public partial class ParameterWindow : Window
|
||||
{
|
||||
private readonly IPlcService _plcService;
|
||||
private readonly PlcConfiguration _config;
|
||||
private DispatcherTimer _autoRefreshTimer;
|
||||
private bool _isEditing = false; // 用户是否正在编辑任何文本框
|
||||
private ParameterViewModel _viewModel;
|
||||
|
||||
public ParameterWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_plcService = App.PlcService;
|
||||
_config = App.PlcConfig;
|
||||
|
||||
// 为所有文本框注册焦点事件(在XAML中设置事件,或在此处统一查找)
|
||||
RegisterTextBoxEvents();
|
||||
|
||||
Loaded += async (s, e) =>
|
||||
{
|
||||
// 启动自动刷新定时器(每秒1次)
|
||||
_autoRefreshTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
||||
_autoRefreshTimer.Start();
|
||||
|
||||
// 首次加载时读取一次
|
||||
await ReadParametersAsync();
|
||||
};
|
||||
|
||||
Closed += (s, e) => _autoRefreshTimer?.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为所有文本框注册焦点事件,用于检测编辑状态
|
||||
/// </summary>
|
||||
private void RegisterTextBoxEvents()
|
||||
{
|
||||
// 查找当前窗口中所有文本框(可根据实际布局调整)
|
||||
var textBoxes = FindVisualChildren<System.Windows.Controls.TextBox>(this);
|
||||
foreach (var tb in textBoxes)
|
||||
{
|
||||
tb.GotFocus += (s, e) => _isEditing = true;
|
||||
tb.LostFocus += (s, e) => _isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时器事件:如果用户未在编辑,则刷新参数
|
||||
/// </summary>
|
||||
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (!_isEditing)
|
||||
{
|
||||
await ReadParametersAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadParametersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 加压控制
|
||||
txtPressureUpper.Text = (await ReadFloatAsync(_config.PressureUpperLimit)).ToString("F3");
|
||||
txtPressureRate.Text = (await ReadFloatAsync(_config.PressureRate)).ToString("F3");
|
||||
txtPressureCoeff.Text = (await ReadFloatAsync(_config.PressureCoeff)).ToString("F3");
|
||||
|
||||
// 高压/低压系数
|
||||
txtHPCoeff1.Text = (await ReadFloatAsync(_config.HPCoeff1)).ToString("F3");
|
||||
txtLPCoeff1.Text = (await ReadFloatAsync(_config.LPCoeff1)).ToString("F3");
|
||||
txtHPCoeff2.Text = (await ReadFloatAsync(_config.HPCoeff2)).ToString("F3");
|
||||
txtLPCoeff2.Text = (await ReadFloatAsync(_config.LPCoeff2)).ToString("F3");
|
||||
txtHPCoeff3.Text = (await ReadFloatAsync(_config.HPCoeff3)).ToString("F3");
|
||||
txtLPCoeff3.Text = (await ReadFloatAsync(_config.LPCoeff3)).ToString("F3");
|
||||
|
||||
// 流量系数
|
||||
txtLargeFlow1.Text = (await ReadFloatAsync(_config.LargeFlowCoeff1)).ToString("F3");
|
||||
txtSmallFlow1.Text = (await ReadFloatAsync(_config.SmallFlowCoeff1)).ToString("F3");
|
||||
txtLargeFlow2.Text = (await ReadFloatAsync(_config.LargeFlowCoeff2)).ToString("F3");
|
||||
txtSmallFlow2.Text = (await ReadFloatAsync(_config.SmallFlowCoeff2)).ToString("F3");
|
||||
txtLargeFlow3.Text = (await ReadFloatAsync(_config.LargeFlowCoeff3)).ToString("F3");
|
||||
txtSmallFlow3.Text = (await ReadFloatAsync(_config.SmallFlowCoeff3)).ToString("F3");
|
||||
|
||||
// 校准参数
|
||||
txtPressureZero.Text = (await ReadFloatAsync(_config.PressureCalibZero)).ToString("F3");
|
||||
txtPressureSpan.Text = (await ReadFloatAsync(_config.PressureCalibSpan)).ToString("F3");
|
||||
txtFlowZero.Text = (await ReadFloatAsync(_config.FlowCalibZero)).ToString("F3");
|
||||
txtFlowSpan.Text = (await ReadFloatAsync(_config.FlowCalibSpan)).ToString("F3");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 静默处理,避免频繁弹窗干扰(可记录日志)
|
||||
System.Diagnostics.Debug.WriteLine($"自动读取参数失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void WriteParameters_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 加压控制
|
||||
await WriteFloatAsync(_config.PressureUpperLimit, ParseFloat(txtPressureUpper.Text));
|
||||
await WriteFloatAsync(_config.PressureRate, ParseFloat(txtPressureRate.Text));
|
||||
await WriteFloatAsync(_config.PressureCoeff, ParseFloat(txtPressureCoeff.Text));
|
||||
|
||||
// 高压/低压系数
|
||||
await WriteFloatAsync(_config.HPCoeff1, ParseFloat(txtHPCoeff1.Text));
|
||||
await WriteFloatAsync(_config.LPCoeff1, ParseFloat(txtLPCoeff1.Text));
|
||||
await WriteFloatAsync(_config.HPCoeff2, ParseFloat(txtHPCoeff2.Text));
|
||||
await WriteFloatAsync(_config.LPCoeff2, ParseFloat(txtLPCoeff2.Text));
|
||||
await WriteFloatAsync(_config.HPCoeff3, ParseFloat(txtHPCoeff3.Text));
|
||||
await WriteFloatAsync(_config.LPCoeff3, ParseFloat(txtLPCoeff3.Text));
|
||||
|
||||
// 流量系数
|
||||
await WriteFloatAsync(_config.LargeFlowCoeff1, ParseFloat(txtLargeFlow1.Text));
|
||||
await WriteFloatAsync(_config.SmallFlowCoeff1, ParseFloat(txtSmallFlow1.Text));
|
||||
await WriteFloatAsync(_config.LargeFlowCoeff2, ParseFloat(txtLargeFlow2.Text));
|
||||
await WriteFloatAsync(_config.SmallFlowCoeff2, ParseFloat(txtSmallFlow2.Text));
|
||||
await WriteFloatAsync(_config.LargeFlowCoeff3, ParseFloat(txtLargeFlow3.Text));
|
||||
await WriteFloatAsync(_config.SmallFlowCoeff3, ParseFloat(txtSmallFlow3.Text));
|
||||
|
||||
// 校准参数
|
||||
await WriteFloatAsync(_config.PressureCalibZero, ParseFloat(txtPressureZero.Text));
|
||||
await WriteFloatAsync(_config.PressureCalibSpan, ParseFloat(txtPressureSpan.Text));
|
||||
await WriteFloatAsync(_config.FlowCalibZero, ParseFloat(txtFlowZero.Text));
|
||||
await WriteFloatAsync(_config.FlowCalibSpan, ParseFloat(txtFlowSpan.Text));
|
||||
|
||||
MessageBox.Show("参数写入成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"写入参数失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private float ParseFloat(string text) => float.TryParse(text, out var val) ? val : 0;
|
||||
|
||||
private async Task<float> ReadFloatAsync(ushort address)
|
||||
{
|
||||
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
|
||||
byte[] bytes = new byte[4];
|
||||
bytes[0] = (byte)(registers[0] >> 8);
|
||||
bytes[1] = (byte)(registers[0] & 0xFF);
|
||||
bytes[2] = (byte)(registers[1] >> 8);
|
||||
bytes[3] = (byte)(registers[1] & 0xFF);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
private async Task WriteFloatAsync(ushort address, float value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
|
||||
ushort high = (ushort)((bytes[0] << 8) | bytes[1]);
|
||||
ushort low = (ushort)((bytes[2] << 8) | bytes[3]);
|
||||
await ((ModbusTcpPlcService)_plcService).WriteRegisterAsync(address, high);
|
||||
await ((ModbusTcpPlcService)_plcService).WriteRegisterAsync((ushort)(address + 1), low);
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
/// <summary>
|
||||
/// 辅助方法:查找视觉树中的所有指定类型子元素
|
||||
/// </summary>
|
||||
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
|
||||
{
|
||||
if (depObj == null) yield break;
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(depObj, i);
|
||||
if (child is T t) yield return t;
|
||||
foreach (var childOfChild in FindVisualChildren<T>(child)) yield return childOfChild;
|
||||
}
|
||||
_viewModel = new ParameterViewModel();
|
||||
DataContext = _viewModel;
|
||||
Closed += (s, e) => _viewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
<UserControl x:Class="MembranePoreTester.Views.PoreDistributionView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
xmlns:conv="clr-namespace:MembranePoreTester.Converters">
|
||||
<UserControl.Resources>
|
||||
<conv:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0" Header="样品信息" Margin="0,0,0,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- 新增第4行 -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 第0行 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="膜类型:"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding MembraneTypes}"
|
||||
SelectedItem="{Binding Record.SampleType}" Margin="5"/>
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="规格:"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="3" Text="{Binding Record.SampleSpec}" Margin="5"/>
|
||||
|
||||
<!-- 第1行 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="室温(°C):"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Record.RoomTemperature}" Margin="5"/>
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="浸润时间(h):"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="3" Text="{Binding Record.SoakingTime}" Margin="5"/>
|
||||
|
||||
<!-- 第2行 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="测试液体:"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Liquids}"
|
||||
SelectedItem="{Binding SelectedLiquid}" DisplayMemberPath="Name" Margin="5"/>
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="生产厂家:"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding Record.LiquidManufacturer}" Margin="5"/>
|
||||
|
||||
<!-- 第3行:压力单位 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="压力单位:"/>
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding PressureUnits}"
|
||||
SelectedItem="{Binding Record.PressureUnit}" Margin="5"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 数据表格和简单图表(此处用ListView代替图表) -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧数据输入表格 -->
|
||||
<GroupBox Grid.Column="0" Header="压力-流量数据" Margin="0,0,5,0">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,5">
|
||||
<Button Content="添加行" Command="{Binding AddDataPointCommand}" Padding="10,5" Margin="5"/>
|
||||
<Button Content="删除行" Command="{Binding RemoveDataPointCommand}" Padding="10,5" Margin="5"/>
|
||||
|
||||
|
||||
<!-- 新增:模式选择 -->
|
||||
<ComboBox SelectedItem="{Binding TestMode}" Width="80" Margin="5">
|
||||
<ComboBoxItem Content="湿膜" IsSelected="True"/>
|
||||
<ComboBoxItem Content="干膜"/>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding SelectedFlowModeIndex}" Width="100" Margin="5">
|
||||
<ComboBoxItem IsSelected="True" Content="大流量"/>
|
||||
<ComboBoxItem Content="小流量"/>
|
||||
</ComboBox>
|
||||
<Button Content="读取PLC" Command="{Binding ReadPlcCommand}" Padding="10,5" Margin="5"/>
|
||||
</StackPanel>
|
||||
<DataGrid ItemsSource="{Binding Record.DataPoints}" AutoGenerateColumns="False" CanUserAddRows="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="压力" Binding="{Binding Pressure, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
|
||||
<DataGridTextColumn Header="湿膜流量(L/min)" Binding="{Binding WetFlow, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
|
||||
<DataGridTextColumn Header="干膜流量(L/min)" Binding="{Binding DryFlow, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center"/>
|
||||
|
||||
<!-- 右侧简易数据展示(可用图表库,此处仅用ListBox预览) -->
|
||||
<GroupBox Grid.Column="2" Header="数据预览" Margin="5,0,0,0">
|
||||
<!--<ListBox ItemsSource="{Binding Record.DataPoints}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Pressure, StringFormat=P:{0:F2}}"/>
|
||||
<TextBlock Text=" "/>
|
||||
<TextBlock Text="{Binding WetFlow, StringFormat=W:{0:F3}}"/>
|
||||
<TextBlock Text=" "/>
|
||||
<TextBlock Text="{Binding DryFlow, StringFormat=D:{0:F3}}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>-->
|
||||
<GroupBox Grid.Column="2" Header="流量-压力曲线" Margin="5,0,0,0">
|
||||
<oxy:PlotView Model="{Binding PlotModel}" Height="300" Margin="5"/>
|
||||
</GroupBox>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<!-- 计算结果区域 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="0,10,0,0">
|
||||
<GroupBox Header="平均孔径" Margin="5">
|
||||
<TextBlock Text="{Binding AveragePoreSize, StringFormat={}{0:F3} μm}" FontSize="16"/>
|
||||
</GroupBox>
|
||||
<GroupBox Header="孔分布区间计算" Margin="5">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding LowerPore}" Width="60" Margin="5"/>
|
||||
<TextBlock Text="~" VerticalAlignment="Center"/>
|
||||
<TextBox Text="{Binding UpperPore}" Width="60" Margin="5"/>
|
||||
<TextBlock Text="μm" VerticalAlignment="Center"/>
|
||||
<Button Content="计算" Command="{Binding CalculateCommand}" Margin="10,0" Padding="10,5"/>
|
||||
<TextBlock Text="{Binding RangePercentage, StringFormat={}{0:F1}%}" FontSize="16" VerticalAlignment="Center" Margin="10,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<Button Content="流量校准" Command="{Binding OpenFlowCalibCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="生成报告" Command="{Binding GenerateReportCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="保存到历史" Command="{Binding SaveCommand}" Padding="15,8" Margin="5"/>
|
||||
<Button Content="导出Excel" Command="{Binding ExportCommand}" Padding="15,8" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
{
|
||||
public partial class PoreDistributionView : UserControl
|
||||
{
|
||||
public PoreDistributionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
//DataContext = new ViewModels.PoreDistributionViewModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
245
Views/TestView.xaml
Normal file
245
Views/TestView.xaml
Normal file
@@ -0,0 +1,245 @@
|
||||
<Window x:Class="MembranePoreTester.TestView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="测试界面"
|
||||
Width="1024" MinHeight="768" WindowState="Maximized"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="#F5F7FA" FontFamily="Segoe UI" KeyDown="Window_KeyDown">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 统一按钮样式(与 MainWindow 一致) -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#2196F3"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="12,6"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#1976D2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#0D47A1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- 统一文本框样式 -->
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D0D3D9"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="5,2"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<!-- 只读文本框样式 -->
|
||||
<Style x:Key="ReadOnlyTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
|
||||
<Setter Property="IsReadOnly" Value="True"/>
|
||||
<Setter Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="Foreground" Value="#2196F3"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
|
||||
<!-- 统一标签样式 -->
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Foreground" Value="#2C3E50"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
</Style>
|
||||
|
||||
<!-- 统一GroupBox样式 -->
|
||||
<Style TargetType="GroupBox">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#E9ECF0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Margin" Value="0,0,0,12"/>
|
||||
<Setter Property="Padding" Value="12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupBox">
|
||||
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="{TemplateBinding Header}" FontWeight="SemiBold" FontSize="14" Margin="0,0,0,12"/>
|
||||
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 统一 RadioButton 样式 -->
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏(蓝底卡片) -->
|
||||
<Border Grid.Row="0" Background="#2196F3" CornerRadius="6" Padding="12" Margin="0,0,0,12">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="测试界面" FontSize="20" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock Text="当前模式:" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<Border Background="White" CornerRadius="20" Padding="12,4">
|
||||
<TextBlock Text="{Binding CurrentMode}" Foreground="#2196F3" FontSize="14" FontWeight="Bold"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" >
|
||||
<RadioButton Content="空白模式" IsChecked="{Binding IsBlankMode}" GroupName="TestMode" Foreground="White" FontSize="13"/>
|
||||
<RadioButton Content="试样测试" IsChecked="{Binding IsSampleMode}" GroupName="TestMode" Foreground="White" FontSize="13" Margin="10,5,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 参数设置区域 -->
|
||||
<GroupBox Grid.Row="1" Header="⚙️ 参数设置" Margin="0,0,0,12">
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label Content="分辨角度:" Margin="0,0,5,0"/>
|
||||
<TextBox Text="{Binding ResolutionAngle, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<Label Content="°" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label Content="转动速度:" Margin="0,0,5,0"/>
|
||||
<TextBox Text="{Binding RotationSpeed, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
|
||||
<Label Content="°/s" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label Content="当前角度:" Margin="0,0,5,0"/>
|
||||
<TextBox Text="{Binding CurrentAngle, UpdateSourceTrigger=PropertyChanged}" Width="80" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="°" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 视野面积数据区域 -->
|
||||
<GroupBox Grid.Row="2" Header="👁️ 视野面积数据" Margin="0,0,0,12">
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="左目视野面积:" Width="110"/>
|
||||
<TextBox Text="{Binding LeftEyeArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="cm²" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="右目视野面积:" Width="110"/>
|
||||
<TextBox Text="{Binding RightEyeArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="cm²" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="双目视野面积:" Width="110"/>
|
||||
<TextBox Text="{Binding BinocularArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="cm²" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="下方视野:" Width="110"/>
|
||||
<TextBox Text="{Binding LowerField}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="°" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="空白视野面积:" Width="110"/>
|
||||
<TextBox Text="{Binding BlankArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="cm²" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" Margin="5">
|
||||
<Label Content="视野保存率:" Width="110"/>
|
||||
<TextBox Text="{Binding FieldRetentionRate}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
|
||||
<Label Content="%" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 控制按钮区域 -->
|
||||
<GroupBox Grid.Row="3" Header="🎮 设备控制" Margin="0,0,0,12">
|
||||
<WrapPanel HorizontalAlignment="Center" Margin="5">
|
||||
<Button Content="🔄 复位" Command="{Binding ResetCommand}" Background="#607D8B"/>
|
||||
<Button Content="👁️ 左眼开" Command="{Binding OpenLeftEyeCommand}" Background="#4CAF50"/>
|
||||
<Button Content="↩️ 反转" Command="{Binding ReverseCommand}" Background="#FF9800"/>
|
||||
<Button Content="👁️ 右眼开" Command="{Binding OpenRightEyeCommand}" Background="#4CAF50"/>
|
||||
<Button Content="↪️ 正转" Command="{Binding ForwardCommand}" Background="#FF9800"/>
|
||||
<Button Content="▶️ 测试" Command="{Binding StartTestCommand}" Background="#2196F3"/>
|
||||
<Button Content="⏹️ 停止" Command="{Binding StopTestCommand}" Background="#F44336"/>
|
||||
</WrapPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<Border Grid.Row="4" Background="White" CornerRadius="6" Padding="10" Margin="0,12,0,0" BorderBrush="#E9ECF0" BorderThickness="1">
|
||||
<WrapPanel HorizontalAlignment="Center">
|
||||
<Button Content="🏠 主页" Command="{Binding NavigateHomeCommand}" Width="100" Background="#607D8B"/>
|
||||
<Button Content="🧪 测试界面" Command="{Binding NavigateTestCommand}" Width="100" Background="#2196F3"/>
|
||||
<Button Content="📊 数据记录" Command="{Binding NavigateDataRecordCommand}" Width="100" Background="#4CAF50"/>
|
||||
<Button Content="📸 记录画面" Command="{Binding NavigateRecordScreenCommand}" Width="100" Background="#FF9800"/>
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Window>
|
||||
29
Views/TestView.xaml.cs
Normal file
29
Views/TestView.xaml.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using MembranePoreTester.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MembranePoreTester
|
||||
{
|
||||
/// <summary>
|
||||
/// TestView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class TestView : Window
|
||||
{
|
||||
public TestView()
|
||||
{
|
||||
InitializeComponent();
|
||||
// 如果需要在打开时自动开始模拟,可以在此设置 DataContext
|
||||
DataContext = new TestViewModel();
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.P)
|
||||
{
|
||||
var win = new ParameterWindow();
|
||||
win.Owner = this;
|
||||
win.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,55 +3,6 @@
|
||||
"IpAddress": "192.168.1.10",
|
||||
"Port": 502,
|
||||
"SlaveId": 1,
|
||||
"PressureRegisterStation1": 6,
|
||||
"PressureRegisterStation2": 8,
|
||||
"PressureRegisterStation3": 10,
|
||||
"PressureModeRegister": 200, // 高压/低压选择寄存器(0=低压,1=高压)
|
||||
"PressCoil": 100, // 加压线圈(M100)
|
||||
"StartCoil": 20, // 启动线圈(M20)
|
||||
"EnableCoil": 21, // 使能线圈(M21)
|
||||
"StopCoil": 7, // 停止线圈(M7)
|
||||
"PressureFactor": 1.0,
|
||||
"WetFlowRegister": 2,
|
||||
"DryFlowRegister": 4,
|
||||
"WetFlowFactor": 1.0,
|
||||
"DryFlowFactor": 1.0,
|
||||
|
||||
|
||||
|
||||
|
||||
"PressureUpperLimit": 300,
|
||||
"PressureRate": 280,
|
||||
"PressureCoeff": 282,
|
||||
"HPCoeff1": 3120,
|
||||
"LPCoeff1": 3122,
|
||||
"HPCoeff2": 3124,
|
||||
"LPCoeff2": 3126,
|
||||
"HPCoeff3": 3128,
|
||||
"LPCoeff3": 3130,
|
||||
"LargeFlowCoeff1": 3048,
|
||||
"SmallFlowCoeff1": 380,
|
||||
"LargeFlowCoeff2": 1218,
|
||||
"SmallFlowCoeff2": 1318,
|
||||
"LargeFlowCoeff3": 1418,
|
||||
"SmallFlowCoeff3": 1468,
|
||||
"FlowModeRegister1": 5,
|
||||
"FlowModeRegister2": 6,
|
||||
"FlowModeRegister3": 7,
|
||||
"PressureCalibZero": 3200,
|
||||
"PressureCalibSpan": 3202,
|
||||
"FlowCalibZero": 3204,
|
||||
"FlowCalibSpan": 3206
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -27,4 +27,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Report\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user