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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Views/BubblePointView.xaml.cs b/Views/BubblePointView.xaml.cs
new file mode 100644
index 0000000..f93c382
--- /dev/null
+++ b/Views/BubblePointView.xaml.cs
@@ -0,0 +1,13 @@
+using System.Windows.Controls;
+
+namespace MembranePoreTester.Views
+{
+ public partial class BubblePointView : UserControl
+ {
+ public BubblePointView()
+ {
+ InitializeComponent();
+ DataContext = new ViewModels.BubblePointViewModel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml
new file mode 100644
index 0000000..2ddb03b
--- /dev/null
+++ b/Views/MainWindow.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000..49bfae0
--- /dev/null
+++ b/Views/MainWindow.xaml.cs
@@ -0,0 +1,12 @@
+using System.Windows;
+
+namespace MembranePoreTester.Views
+{
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Views/PoreDistributionView.xaml b/Views/PoreDistributionView.xaml
new file mode 100644
index 0000000..10879c7
--- /dev/null
+++ b/Views/PoreDistributionView.xaml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Views/PoreDistributionView.xaml.cs b/Views/PoreDistributionView.xaml.cs
new file mode 100644
index 0000000..49eb7d9
--- /dev/null
+++ b/Views/PoreDistributionView.xaml.cs
@@ -0,0 +1,13 @@
+using System.Windows.Controls;
+
+namespace MembranePoreTester.Views
+{
+ public partial class PoreDistributionView : UserControl
+ {
+ public PoreDistributionView()
+ {
+ InitializeComponent();
+ DataContext = new ViewModels.PoreDistributionViewModel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/泡压法膜孔径分析仪.csproj b/泡压法膜孔径分析仪.csproj
index 6e6420e..b39e670 100644
--- a/泡压法膜孔径分析仪.csproj
+++ b/泡压法膜孔径分析仪.csproj
@@ -8,4 +8,8 @@
true
+
+
+
+