diff --git a/ViewModels/D7896ViewModel.cs b/ViewModels/D7896ViewModel.cs
index 9900778..fb5451c 100644
--- a/ViewModels/D7896ViewModel.cs
+++ b/ViewModels/D7896ViewModel.cs
@@ -439,7 +439,7 @@ public partial class D7896ViewModel : ObservableObject
// 测量间隔(即使舍弃也等待,让样品恢复)
if (validCount < requiredCount && !_stopRequested && attemptCount < maxAttempts)
{
- try { await Task.Delay(_config.TestParameters.IntervalSeconds * 1000, _testCts.Token); } catch (OperationCanceledException) { break; }
+ try { await Task.Delay(_config.TestParameters.IntervalSeconds * 100, _testCts.Token); } catch (OperationCanceledException) { break; }
}
}
@@ -667,34 +667,6 @@ public partial class D7896ViewModel : ObservableObject
return timeArray.Length - 1;
}
- private void ExportMeasurementCsv(string path, double[] time, double[] ustd, double[] upt, double[] deltaT, int fitStart, int fitEnd)
- {
- using var sw = new StreamWriter(path, false, Encoding.UTF8);
- sw.WriteLine("index,time,U_std,U_pt,deltaT,fitWindow");
- int n = Math.Min(Math.Min(time.Length, ustd.Length), deltaT.Length);
- for (int i = 0; i < n; i++)
- {
- bool inFit = (i >= fitStart && i <= fitEnd);
- sw.WriteLine($"{i},{time[i]:F6},{ustd[i]:F6},{upt[i]:F6},{(double.IsNaN(deltaT[i]) ? 0 : deltaT[i]):F6},{(inFit ? 1 : 0)}");
- }
- }
-
- private void ExportCandidateWindowsCsv(string path, double[] time, double[] deltaT, int startIdx, int endIdx)
- {
- using var sw = new StreamWriter(path, false, Encoding.UTF8);
- sw.WriteLine("s,e,t_start,t_end,meanDelta");
- int n = time.Length;
- int minPts = 10;
- for (int s = startIdx; s <= endIdx - minPts; s++)
- {
- for (int e = s + minPts - 1; e <= endIdx; e++)
- {
- double mean = deltaT.Skip(s).Take(e - s + 1).Where(x => !double.IsNaN(x)).DefaultIfEmpty(0).Average();
- sw.WriteLine($"{s},{e},{time[s]:F6},{time[e]:F6},{mean:F6}");
- }
- }
- }
-
/////
///// 最小二乘法拟合斜率 (X轴为横坐标,Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT
/////
@@ -809,40 +781,6 @@ public partial class D7896ViewModel : ObservableObject
TemperatureCurveModel = null;
}
- //[RelayCommand]
- //private async Task GenerateReportAsync()
- //{
- // if (Measurements.Count == 0)
- // {
- // MessageBox.Show("没有测试数据", "提示");
- // return;
- // }
- // try
- // {
- // var extraParams = new Dictionary
- // {
- // ["SampleVolume"] = SampleVolume,
- // ["BubbleRemoved"] = BubbleRemoved,
- // ["UsePressure"] = UsePressure,
- // ["PressureValue"] = PressureValue,
- // ["IsCleanConfirmed"] = IsCleanConfirmed,
- // ["CleanerName"] = CleanerName,
- // ["AmbientTemperature"] = AmbientTemperature,
- // ["AmbientCalibrated"] = AmbientCalibrated,
- // ["PlatinumCompatible"] = PlatinumCompatible,
- // ["LiquidReactivityNote"] = LiquidReactivityNote,
- // ["InitialResistance"] = PlatinumResistance
- // };
- // string reportPath = await _reportService.GenerateReportAsync(SampleId, TestTemperature, Measurements.ToList(),
- // AverageThermalConductivity, AverageThermalDiffusivity, AverageVolumetricHeatCapacity,
- // _config.TestParameters, extraParams);
- // MessageBox.Show($"报告已生成: {reportPath}", "成功");
- // }
- // catch (Exception ex)
- // {
- // MessageBox.Show($"生成报告失败: {ex.Message}", "错误");
- // }
- //}
[RelayCommand]
private async Task GenerateReportAsync()
{
@@ -866,8 +804,23 @@ public partial class D7896ViewModel : ObservableObject
try
{
- // 生成 PDF
- await Task.Run(() => GeneratePdfReport(pdfPath));
+ // 先在 UI 线程导出曲线图为字节数组
+ byte[] chartImageBytes = null;
+ await Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ if (TemperatureCurveModel != null && TemperatureCurveModel.Series.Count > 0)
+ {
+ using (var stream = new MemoryStream())
+ {
+ var exporter = new PngExporter { Width = 600, Height = 400 };
+ exporter.Export(TemperatureCurveModel, stream);
+ chartImageBytes = stream.ToArray();
+ }
+ }
+ });
+
+ // 然后在后台线程生成 PDF(避免阻塞 UI)
+ await Task.Run(() => GeneratePdfReport(pdfPath, chartImageBytes));
MessageBox.Show($"报表已生成: {pdfPath}", "成功");
}
catch (Exception ex)
@@ -879,103 +832,134 @@ public partial class D7896ViewModel : ObservableObject
- private void GeneratePdfReport(string filePath)
+ private void GeneratePdfReport(string filePath, byte[] chartImageBytes)
{
- // 1. 创建文档
using (var document = new PdfDocument())
{
- // 2. 添加页面
+ // 创建第一个页面
var page = document.AddPage();
page.Width = XUnit.FromMillimeter(210);
page.Height = XUnit.FromMillimeter(297);
+ var gfx = XGraphics.FromPdfPage(page);
- // 3. 开始绘制
- using (var gfx = XGraphics.FromPdfPage(page))
+ var titleFont = new XFont("SimHei", 16, XFontStyle.Bold);
+ var headerFont = new XFont("SimHei", 12, XFontStyle.Bold);
+ var normalFont = new XFont("SimHei", 10, XFontStyle.Regular);
+ double yPosition = 30;
+
+ // 标题
+ gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告", titleFont, XBrushes.Black,
+ new XRect(0, yPosition, page.Width, 30), XStringFormats.TopCenter);
+ yPosition += 40;
+
+ // 基础信息(只显示一次)
+ gfx.DrawString($"样品名称: {SampleId}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"测试温度: {TestTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"测试时间: {TestDateTime}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"样品体积: {SampleVolume:F1} mL", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"样品密度: {SampleDensity:F0} kg/m^3", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"环境温度: {AmbientTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"铂丝电阻温度系数: {AlphaPt:F4} /°C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"铂丝长度: {_config.TestParameters.PlatinumWireLength:F3} m", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
+ gfx.DrawString($"铂丝直径: {_config.TestParameters.PlatinumWireDiameter * 1000:F2} mm", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 35;
+
+ // 曲线图(如果有)
+ if (chartImageBytes != null)
{
- // 创建字体
- var titleFont = new XFont("Verdana", 16, XFontStyle.Bold);
- var headerFont = new XFont("Verdana", 12, XFontStyle.Bold);
- var normalFont = new XFont("Verdana", 10, XFontStyle.Regular);
- double yPosition = 30;
-
- // ---------- 4. 添加标题 ----------
- gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告", titleFont, XBrushes.Black,
- new XRect(0, yPosition, page.Width, 30), XStringFormats.TopCenter);
- yPosition += 40;
-
- // ---------- 5. 添加基础信息 ----------
- gfx.DrawString($"样品名称: {SampleId}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
- gfx.DrawString($"测试温度: {TestTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
- // ... 所有需要显示的基础信息
-
- yPosition += 10;
-
- // ---------- 6. 插入温升曲线图 ----------
- if (TemperatureCurveModel != null)
+ using (var imgStream = new MemoryStream(chartImageBytes))
{
- using (var stream = new MemoryStream())
- {
- var exporter = new PngExporter { Width = 600, Height = 400 };
- exporter.Export(TemperatureCurveModel, stream);
- stream.Position = 0;
- var imgStream = new MemoryStream(stream.ToArray(), 0, (int)stream.Length, false, true);
- var image = XImage.FromStream(() => imgStream);
- gfx.DrawImage(image, 40, yPosition, 500, 330);
- yPosition += 350;
- }
+ var image = XImage.FromStream(() => new MemoryStream(imgStream.ToArray()));
+ gfx.DrawImage(image, 40, yPosition, 500, 330);
+ yPosition += 350;
}
+ }
- // ---------- 7. 创建表格 ----------
- gfx.DrawString("测量结果明细", headerFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
+ // 表格标题
+ gfx.DrawString("测量结果明细", headerFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
- // 表头
- string[] headers = { "序号", "热导率λ(W/(m·K))", "热扩散率α(10⁻⁷ m²/s)", "体积热容VHC(kJ/(m³·K))", "比热容Cp(J/(kg·K))" };
- double[] colWidths = { 50, 100, 100, 120, 100 };
- double startX = 40;
- double currentRowY = yPosition;
+ // 表头(已替换特殊符号)
+ string[] headers = { "序号", "热导率λ(W/(m.K))", "热扩散率α(10^-7 m^2/s)", "体积热容VHC(kJ/(m^3.K))", "比热容Cp(J/(kg.K))" };
+ double[] colWidths = { 50, 115, 125, 125, 125 };
+ double startX = 40;
- // 绘制表头
+ // 辅助函数:绘制表头(用于新页)
+ void DrawHeader(XGraphics g, double y)
+ {
for (int i = 0; i < headers.Length; i++)
{
double cellX = startX + colWidths.Take(i).Sum();
- gfx.DrawRectangle(XPens.Black, cellX, currentRowY, colWidths[i], 20);
- var textRect = new XRect(cellX, currentRowY, colWidths[i], 20);
- gfx.DrawString(headers[i], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
+ g.DrawRectangle(XPens.Black, cellX, y, colWidths[i], 20);
+ var textRect = new XRect(cellX, y, colWidths[i], 20);
+ g.DrawString(headers[i], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
}
- currentRowY += 20;
-
- // 填充数据行 (假设 Measurements 是列表)
- for (int i = 0; i < Measurements.Count; i++)
- {
- var m = Measurements[i];
- string[] rowData = {
- m.Index.ToString(),
- m.ThermalConductivity.ToString("F6"),
- (m.ThermalDiffusivity * 1e7).ToString("F3"),
- m.VolumetricHeatCapacity.ToString("F2"),
- m.SpecificHeatCapacity.ToString("F2")
- };
-
- for (int j = 0; j < rowData.Length; j++)
- {
- double cellX = startX + colWidths.Take(j).Sum();
- gfx.DrawRectangle(XPens.Black, cellX, currentRowY, colWidths[j], 20);
- var textRect = new XRect(cellX, currentRowY, colWidths[j], 20);
- gfx.DrawString(rowData[j], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
- }
- currentRowY += 20;
- }
- yPosition = currentRowY + 10;
-
- // ---------- 8. 添加平均值 ----------
- gfx.DrawString($"平均热导率: {AverageThermalConductivity:F6} W/(m·K)", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 20;
- // ... 其他平均值
-
- // ---------- 9. 添加生成时间和页脚 ----------
- gfx.DrawString($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", normalFont, XBrushes.Black, new XPoint(40, page.Height - 30));
}
- // 10. 保存文档
+ // 绘制第一页的表头
+ DrawHeader(gfx, yPosition);
+ double currentRowY = yPosition + 20; // 表头高度20
+ const double rowHeight = 20;
+ const double bottomMargin = 50; // 页面底部留白
+
+ // 遍历所有测量数据
+ foreach (var m in Measurements)
+ {
+ // 检查是否需要换页
+ if (currentRowY + rowHeight > page.Height - bottomMargin)
+ {
+ gfx.Dispose();
+ page = document.AddPage();
+ page.Width = XUnit.FromMillimeter(210);
+ page.Height = XUnit.FromMillimeter(297);
+ gfx = XGraphics.FromPdfPage(page);
+ gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告(续)", headerFont, XBrushes.Black,
+ new XRect(0, 30, page.Width, 30), XStringFormats.TopCenter);
+ currentRowY = 80;
+ DrawHeader(gfx, currentRowY - 20);
+ }
+
+ string[] rowData = {
+ m.Index.ToString(),
+ m.ThermalConductivity.ToString("F6"),
+ (m.ThermalDiffusivity * 1e7).ToString("F3"),
+ m.VolumetricHeatCapacity.ToString("F2"),
+ m.SpecificHeatCapacity.ToString("F2")
+ };
+ for (int j = 0; j < rowData.Length; j++)
+ {
+ double cellX = startX + colWidths.Take(j).Sum();
+ gfx.DrawRectangle(XPens.Black, cellX, currentRowY, colWidths[j], rowHeight);
+ var textRect = new XRect(cellX, currentRowY, colWidths[j], rowHeight);
+ gfx.DrawString(rowData[j], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
+ }
+ currentRowY += rowHeight;
+ }
+
+ // 添加空行,避免平均值与表格重叠
+ currentRowY += 60;
+
+ // 检查是否需要换页(平均值部分)
+ if (currentRowY + 80 > page.Height - bottomMargin)
+ {
+ gfx.Dispose();
+ page = document.AddPage();
+ page.Width = XUnit.FromMillimeter(210);
+ page.Height = XUnit.FromMillimeter(297);
+ gfx = XGraphics.FromPdfPage(page);
+ currentRowY = 50;
+ }
+
+ // 平均值(单位已替换)
+ gfx.DrawString($"平均热导率: {AverageThermalConductivity:F6} W/(m.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
+ gfx.DrawString($"平均热扩散率: {AverageThermalDiffusivity:E6} m^2/s", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
+ gfx.DrawString($"平均体积热容: {AverageVolumetricHeatCapacity:F2} kJ/(m^3.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
+ // 比热容转换:VHC 单位 kJ/(m^3.K) 先转为 J/(m^3.K) 再除以密度得到 J/(kg.K)
+ double avgCp_J_per_kgK = (AverageVolumetricHeatCapacity * 1000) / SampleDensity;
+ gfx.DrawString($"平均比热容: {avgCp_J_per_kgK:F0} J/(kg.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 40;
+
+ // 页脚
+ gfx.DrawString($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", normalFont, XBrushes.Black, new XPoint(40, page.Height - 30));
+ gfx.Dispose();
+
document.Save(filePath);
}
}