diff --git a/App.xaml b/App.xaml index 68332bd..e1da920 100644 --- a/App.xaml +++ b/App.xaml @@ -1,9 +1,7 @@ - + StartupUri="Views/MainWindow.xaml"> - - + \ No newline at end of file diff --git a/App.xaml.cs b/App.xaml.cs index 3b31132..3357765 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,14 +1,8 @@ -using System.Configuration; -using System.Data; -using System.Windows; +using System.Windows; -namespace 泡压法膜孔径分析仪 +namespace MembranePoreTester { - /// - /// Interaction logic for App.xaml - /// public partial class App : Application { } - -} +} \ No newline at end of file diff --git a/Converters/InverseBooleanConverter.cs b/Converters/InverseBooleanConverter.cs new file mode 100644 index 0000000..9d8be90 --- /dev/null +++ b/Converters/InverseBooleanConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace MembranePoreTester.Converters +{ + [ValueConversion(typeof(bool), typeof(bool))] + public class InverseBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool b) + return !b; + return true; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool b) + return !b; + return true; + } + } +} \ No newline at end of file diff --git a/Helpers/Interpolation.cs b/Helpers/Interpolation.cs new file mode 100644 index 0000000..a3d667f --- /dev/null +++ b/Helpers/Interpolation.cs @@ -0,0 +1,25 @@ +using System; + +namespace MembranePoreTester.Helpers +{ + public static class Interpolation + { + // 线性插值 + public static double Linear(double[] xs, double[] ys, double x) + { + if (xs.Length == 0 || ys.Length == 0 || xs.Length != ys.Length) + throw new ArgumentException("数组长度不匹配或为空"); + + if (x <= xs[0]) return ys[0]; + if (x >= xs[^1]) return ys[^1]; + + int i = Array.BinarySearch(xs, x); + if (i >= 0) return ys[i]; + + i = ~i; // 大于 x 的第一个索引 + double x0 = xs[i - 1], x1 = xs[i]; + double y0 = ys[i - 1], y1 = ys[i]; + return y0 + (y1 - y0) * (x - x0) / (x1 - x0); + } + } +} \ No newline at end of file diff --git a/Helpers/PoreCalculator.cs b/Helpers/PoreCalculator.cs new file mode 100644 index 0000000..b1d6c46 --- /dev/null +++ b/Helpers/PoreCalculator.cs @@ -0,0 +1,31 @@ +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 + }; + } + } +} \ No newline at end of file diff --git a/Helpers/PoreDistributionAnalysis.cs b/Helpers/PoreDistributionAnalysis.cs new file mode 100644 index 0000000..c8d962d --- /dev/null +++ b/Helpers/PoreDistributionAnalysis.cs @@ -0,0 +1,69 @@ +using MembranePoreTester.Models; +using System.Collections.Generic; +using System.Linq; + +namespace MembranePoreTester.Helpers +{ + public static class PoreDistributionAnalysis + { + public static double CalculateAveragePore( + IEnumerable 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 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; + } + } +} \ No newline at end of file diff --git a/Helpers/ReportGenerator.cs b/Helpers/ReportGenerator.cs new file mode 100644 index 0000000..f52176c --- /dev/null +++ b/Helpers/ReportGenerator.cs @@ -0,0 +1,227 @@ +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); + } + } + } +} \ No newline at end of file diff --git a/MainWindow.xaml b/MainWindow.xaml deleted file mode 100644 index dcf23e1..0000000 --- a/MainWindow.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs deleted file mode 100644 index 4d7c06a..0000000 --- a/MainWindow.xaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace 泡压法膜孔径分析仪 -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : Window - { - public MainWindow() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/Models/BubblePointRecord.cs b/Models/BubblePointRecord.cs new file mode 100644 index 0000000..a73258f --- /dev/null +++ b/Models/BubblePointRecord.cs @@ -0,0 +1,30 @@ +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 + }; + } + } +} \ No newline at end of file diff --git a/Models/DataPoint.cs b/Models/DataPoint.cs new file mode 100644 index 0000000..4bb355c --- /dev/null +++ b/Models/DataPoint.cs @@ -0,0 +1,9 @@ +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) + } +} \ No newline at end of file diff --git a/Models/PoreDistributionRecord.cs b/Models/PoreDistributionRecord.cs new file mode 100644 index 0000000..8646c80 --- /dev/null +++ b/Models/PoreDistributionRecord.cs @@ -0,0 +1,81 @@ +using System.Collections.ObjectModel; + +namespace MembranePoreTester.Models +{ + /// + /// 孔分布测试的记录模型,存储一次孔分布测试的所有输入数据和计算结果。 + /// 对应 GB/T 32361-2015 标准中的平均流量法测试。 + /// + public class PoreDistributionRecord + { + /// + /// 膜类型:平板膜 或 中空纤维膜。 + /// + public string SampleType { get; set; } + + /// + /// 样品规格,如直径、厚度或型号等。 + /// + public string SampleSpec { get; set; } + + /// + /// 测试时的室温,单位:摄氏度(°C)。 + /// + public double RoomTemperature { get; set; } + + /// + /// 样品在测试液体中的浸润时间,单位:小时(h)。 + /// + public double SoakingTime { get; set; } + + /// + /// 测试使用的液体对象,包含液体名称、表面张力等信息。 + /// + public TestLiquid Liquid { get; set; } + + /// + /// 测试液体的生产厂家,用于溯源。 + /// + public string LiquidManufacturer { get; set; } + + /// + /// 压力单位:Pa、cmHg 或 psi。所有压力相关输入均以此单位为准。 + /// + public string PressureUnit { get; set; } + + /// + /// 压力-流量数据点集合,每个点包含压力、湿膜流量、干膜流量。 + /// 使用 ObservableCollection 以便在界面添加/删除时自动更新。 + /// + public ObservableCollection DataPoints { get; set; } = new(); + + /// + /// 测试日期,默认为当前日期时间。 + /// + public DateTime TestDate { get; set; } = DateTime.Now; + + /// + /// 测试人员姓名。 + /// + public string Tester { get; set; } + + // ---------- 计算结果(由计算逻辑填充) ---------- + + /// + /// 泡点压力(由用户单独记录或从数据点推断),用于最大孔径计算。 + /// + public double BubblePointPressure { get; set; } + + /// + /// 计算出的平均孔径,单位:微米(μm)。 + /// 对应标准中平均流量法的平均孔径结果。 + /// + public double AveragePoreSize { get; set; } + + /// + /// 孔分布计算结果列表,每个元素表示一个孔径区间的流量百分比。 + /// 对应标准中孔分布的多个区间结果。 + /// + public List PoreDistributions { get; set; } = new(); + } +} \ No newline at end of file diff --git a/Models/PoreDistributionResult.cs b/Models/PoreDistributionResult.cs new file mode 100644 index 0000000..119f504 --- /dev/null +++ b/Models/PoreDistributionResult.cs @@ -0,0 +1,9 @@ +namespace MembranePoreTester.Models +{ + public class PoreDistributionResult + { + public double LowerPore { get; set; } // 孔径下限(μm) + public double UpperPore { get; set; } // 孔径上限(μm) + public double Percentage { get; set; } // 流量百分比 + } +} \ No newline at end of file diff --git a/Models/TestLiquid.cs b/Models/TestLiquid.cs new file mode 100644 index 0000000..dcc9a90 --- /dev/null +++ b/Models/TestLiquid.cs @@ -0,0 +1,21 @@ +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 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 } + }; + } +} \ No newline at end of file diff --git a/Report/Report.cs b/Report/Report.cs new file mode 100644 index 0000000..b75f571 --- /dev/null +++ b/Report/Report.cs @@ -0,0 +1,59 @@ +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); + } + } +} diff --git a/ViewModels/BubblePointViewModel.cs b/ViewModels/BubblePointViewModel.cs new file mode 100644 index 0000000..9452e41 --- /dev/null +++ b/ViewModels/BubblePointViewModel.cs @@ -0,0 +1,99 @@ +using MembranePoreTester.Helpers; +using MembranePoreTester.Models; +using System.Collections.Generic; +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; + + public BubblePointRecord Record + { + get => _record; + set => SetProperty(ref _record, value); + } + + public List Liquids => TestLiquid.Predefined; + public List PressureUnits => new() { "Pa", "cmHg", "psi" }; + public List MembraneTypes => new() { "平板膜", "中空纤维膜" }; + + public TestLiquid SelectedLiquid + { + get => _selectedLiquid; + set + { + if (SetProperty(ref _selectedLiquid, value)) + { + Record.Liquid = value; + 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 BubblePointViewModel() + { + CalculateCommand = new RelayCommand(Calculate); + GenerateReportCommand = new RelayCommand(GenerateReport); + SelectedLiquid = Liquids[0]; + } + + + public double MaxPoreSize => Record.MaxPoreSize; + + private void Calculate() + { + // 触发最大孔径重新计算(通过绑定已自动刷新) + OnPropertyChanged(nameof(Record.MaxPoreSize)); + } + + private void GenerateReport() + { + ReportGenerator.GenerateBubblePointReport(Record); + } + } +} \ No newline at end of file diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs new file mode 100644 index 0000000..9584089 --- /dev/null +++ b/ViewModels/PoreDistributionViewModel.cs @@ -0,0 +1,148 @@ +using MembranePoreTester.Helpers; +using MembranePoreTester.Models; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows.Input; + +namespace MembranePoreTester.ViewModels +{ + public class PoreDistributionViewModel : ViewModelBase + { + 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; + + public PoreDistributionRecord Record + { + get => _record; + set => SetProperty(ref _record, value); + } + + public List Liquids => TestLiquid.Predefined; + public List PressureUnits => new() { "Pa", "cmHg", "psi" }; + public List MembraneTypes => new() { "平板膜", "中空纤维膜" }; + + public TestLiquid SelectedLiquid + { + get => _selectedLiquid; + set + { + if (SetProperty(ref _selectedLiquid, value)) + { + Record.Liquid = value; + } + } + } + + public bool IsCustomLiquid + { + get => _isCustomLiquid; + set + { + if (SetProperty(ref _isCustomLiquid, value)) + { + UpdateCustomLiquid(); + } + } + } + + public double CustomSurfaceTension + { + get => _customSurfaceTension; + set + { + if (SetProperty(ref _customSurfaceTension, value)) + { + UpdateCustomLiquid(); + } + } + } + + public double LowerPore + { + get => _lowerPore; + set => SetProperty(ref _lowerPore, value); + } + + public double UpperPore + { + get => _upperPore; + set => SetProperty(ref _upperPore, value); + } + + public double RangePercentage + { + get => _rangePercentage; + set => SetProperty(ref _rangePercentage, value); + } + + public ICommand AddDataPointCommand { get; } + public ICommand RemoveDataPointCommand { get; } + public ICommand CalculateCommand { get; } + public ICommand GenerateReportCommand { get; } + + public PoreDistributionViewModel() + { + 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" + } + + 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); + } + } +} \ No newline at end of file diff --git a/ViewModels/RelayCommand.cs b/ViewModels/RelayCommand.cs new file mode 100644 index 0000000..0958f97 --- /dev/null +++ b/ViewModels/RelayCommand.cs @@ -0,0 +1,47 @@ +using System; +using System.Windows.Input; + +namespace MembranePoreTester.ViewModels +{ + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); + public void Execute(object parameter) => _execute(); + } + + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); + public void Execute(object parameter) => _execute((T)parameter); + } +} \ No newline at end of file diff --git a/ViewModels/ViewModelBase.cs b/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..0785cd1 --- /dev/null +++ b/ViewModels/ViewModelBase.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MembranePoreTester.ViewModels +{ + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } + } +} \ No newline at end of file diff --git a/Views/BubblePointView.xaml b/Views/BubblePointView.xaml new file mode 100644 index 0000000..0af715c --- /dev/null +++ b/Views/BubblePointView.xaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +