Files
NonWovenFabric/WindowsFormsApp6/Form3.cs
GukSang.Jin f9f66b7a94 更新
2026-01-05 17:24:49 +08:00

1274 lines
47 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // 用于 .xlsx
using NPOI.HSSF.UserModel; // 用于 .xls
using NPOI.SS.Util;
// 使用别名解决命名空间冲突
using NPOIBorderStyle = NPOI.SS.UserModel.BorderStyle;
using NPOIHorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;
namespace WindowsFormsApp6
{
public partial class Form3 : Form
{
#region
private const int TIMER_INTERVAL = 1000;
private const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
private const string ROW_WICKING_TIME = "吸水时间(s)";
private const string ROW_WICKING_HEIGHT = "吸芯高度(mm)";
private const string ROW_WICKING_RATE = "芯吸速率(mm/min)";
private const string ROW_AVG_WICKING_RATE = "平均芯吸速率(mm/min)";
private const string ROW_STD_DEVIATION = "标准偏差";
#endregion
#region
private System.Windows.Forms.Timer dataTimer;
private System.Windows.Forms.Timer clockTimer;
private DataTable sampleDataTable;
private int currentSampleCount = 5; // 当前试样数量固定为5最多5组共15次测试
private readonly Random random = new Random();
private bool isVerticalLayout = true; // true=纵向当前false=横向
private const int MAX_SAMPLE_COUNT = 5; // 最大试样数量限制为5
#endregion
public Form3()
{
InitializeComponent();
InitializeDataTable();
InitializeTimer();
InitializeDataGridView();
InitializeEventHandlers();
}
/// <summary>
/// 设置试样数量1-5
/// Form3限定为最多5组试样每组3次测试共15次测试数据
/// </summary>
public void SetSampleCount(int count)
{
if (count < 1 || count > MAX_SAMPLE_COUNT)
{
ShowMessage($"试样数量必须在1-{MAX_SAMPLE_COUNT}之间\n" +
$"Form3限定为最多{MAX_SAMPLE_COUNT}组试样(共{MAX_SAMPLE_COUNT * 3}次测试)",
"参数错误", MessageBoxIcon.Warning);
return;
}
currentSampleCount = count;
// 重新初始化数据表和界面
sampleDataTable.Clear();
InitializeDataTable();
InitializeDataGridView();
UpdateDisplay();
}
/// <summary>
/// 初始化 DataGridView 列 - 实现2级表头纵向或普通表头横向
/// </summary>
private void InitializeDataGridView()
{
dataGridView1.SuspendLayout();
try
{
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = null;
dataGridView1.Columns.Clear();
// 移除可能存在的事件处理器
dataGridView1.CellFormatting -= DataGridView1_CellFormatting;
dataGridView1.CellValueChanged -= DataGridView1_CellValueChanged;
dataGridView1.CellBeginEdit -= DataGridView1_CellBeginEdit;
// 创建共享样式(不设置背景色,让 AlternatingRowsDefaultCellStyle 生效)
DataGridViewCellStyle centerStyle = new DataGridViewCellStyle
{
Alignment = DataGridViewContentAlignment.MiddleCenter
};
if (isVerticalLayout)
{
// 纵向布局2级表头
InitializeVerticalLayout(centerStyle);
}
else
{
// 横向布局:普通表头
InitializeHorizontalLayout(centerStyle);
}
// 绑定数据源
dataGridView1.DataSource = sampleDataTable;
dataGridView1.CellFormatting += DataGridView1_CellFormatting;
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.CellBeginEdit += DataGridView1_CellBeginEdit;
}
finally
{
dataGridView1.ResumeLayout();
}
}
/// <summary>
/// 初始化纵向布局(当前默认布局)
/// </summary>
private void InitializeVerticalLayout(DataGridViewCellStyle centerStyle)
{
// 序号列
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = "序号",
HeaderText = "序号",
DataPropertyName = "序号",
Width = 180,
ReadOnly = true,
DefaultCellStyle = centerStyle
});
// 为每个试样添加3列2级表头试样N + 子列1/2/3
for (int i = 1; i <= currentSampleCount; i++)
{
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = $"试样{i}_1",
HeaderText = $"试样{i}\n1",
DataPropertyName = $"试样{i}_1",
Width = 100,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = $"试样{i}_2",
HeaderText = $"试样{i}\n2",
DataPropertyName = $"试样{i}_2",
Width = 100,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = $"试样{i}_3",
HeaderText = $"试样{i}\n3",
DataPropertyName = $"试样{i}_3",
Width = 100,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
}
}
/// <summary>
/// 初始化横向布局
/// </summary>
private void InitializeHorizontalLayout(DataGridViewCellStyle centerStyle)
{
// 序号列
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = "序号",
HeaderText = "序号",
DataPropertyName = "序号",
Width = 120,
ReadOnly = true,
DefaultCellStyle = centerStyle
});
// 数据项列
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = ROW_WICKING_TIME,
HeaderText = ROW_WICKING_TIME,
DataPropertyName = ROW_WICKING_TIME,
Width = 120,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = ROW_WICKING_HEIGHT,
HeaderText = ROW_WICKING_HEIGHT,
DataPropertyName = ROW_WICKING_HEIGHT,
Width = 120,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = ROW_WICKING_RATE,
HeaderText = ROW_WICKING_RATE,
DataPropertyName = ROW_WICKING_RATE,
Width = 140,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = ROW_AVG_WICKING_RATE,
HeaderText = ROW_AVG_WICKING_RATE,
DataPropertyName = ROW_AVG_WICKING_RATE,
Width = 160,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
{
Name = ROW_STD_DEVIATION,
HeaderText = ROW_STD_DEVIATION,
DataPropertyName = ROW_STD_DEVIATION,
Width = 120,
ReadOnly = true,
DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone()
});
}
/// <summary>
/// 单元格编辑前事件 - 动态控制哪些单元格可以编辑
/// </summary>
private void DataGridView1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
// 所有单元格都不可编辑吸芯高度从PLC自动读取
e.Cancel = true;
}
/// <summary>
/// 单元格格式化事件 - 处理数值显示格式
/// </summary>
private void DataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex == 0) return; // 跳过序号列
// 格式化数值显示为2位小数
if (e.Value != null && e.Value != DBNull.Value)
{
if (double.TryParse(e.Value.ToString(), out double numValue))
{
// 如果值为0或接近0显示为空白
if (Math.Abs(numValue) < 0.001)
{
e.Value = "";
e.FormattingApplied = true;
return;
}
// 显示2位小数
e.Value = numValue.ToString("F2");
e.FormattingApplied = true;
}
}
else
{
// null 或 DBNull 显示为空白
e.Value = "";
e.FormattingApplied = true;
}
}
/// <summary>
/// 单元格值改变事件 - 手动输入后重新计算
/// 注意:由于所有单元格都不可编辑,此事件不会被触发
/// </summary>
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// 所有单元格都不可编辑,此事件不会被触发
// 保留此方法以防将来需要启用编辑功能
}
/// <summary>
/// 初始化事件处理器
/// </summary>
private void InitializeEventHandlers()
{
button1.Click += Button1_Click;
button2.Click += Button2_Click;
button3.Click += Button3_Click;
button4.Click += Button4_Click;
button5.Click += Button5_Click;
clockTimer = new System.Windows.Forms.Timer
{
Interval = TIMER_INTERVAL
};
clockTimer.Tick += (s, e) => label2.Text = DateTime.Now.ToString(DATE_TIME_FORMAT);
clockTimer.Start();
// 不再在 Form3 内部创建切换按钮,改为在 MainForm 中创建
// CreateToggleButton();
}
/// <summary>
/// 创建切换按钮
/// </summary>
private void CreateToggleButton()
{
Button toggleButton = new Button
{
Name = "buttonToggle",
Text = "🔄 横向",
Width = 100,
Height = 30,
BackColor = Color.FromArgb(52, 152, 219),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat,
Font = new Font("微软雅黑", 9F, FontStyle.Bold),
Cursor = Cursors.Hand
};
toggleButton.FlatAppearance.BorderSize = 0;
toggleButton.Click += (s, e) =>
{
ToggleTableLayout();
toggleButton.Text = isVerticalLayout ? "🔄 横向" : "🔄 纵向";
};
// 将按钮添加到 panel2
if (this.Controls.Find("tableLayoutPanel1", true).FirstOrDefault() is TableLayoutPanel tlp1)
{
if (tlp1.Controls.Find("tableLayoutPanel2", true).FirstOrDefault() is TableLayoutPanel tlp2)
{
if (tlp2.Controls.Find("panel2", true).FirstOrDefault() is Panel panel2)
{
toggleButton.Location = new Point(panel2.Width - 120, 5);
toggleButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
panel2.Controls.Add(toggleButton);
}
}
}
}
/// <summary>
/// 切换表格方向(纵向/横向)
/// </summary>
public void ToggleTableLayout()
{
isVerticalLayout = !isVerticalLayout;
// 保存当前数据
var currentData = SaveCurrentData();
// 重新初始化表格
InitializeDataTable();
InitializeDataGridView();
// 恢复数据
RestoreData(currentData);
// 更新显示
RefreshDataGridView();
ShowMessage($"已切换到{(isVerticalLayout ? "" : "")}布局", "布局切换", MessageBoxIcon.Information);
}
/// <summary>
/// 获取当前布局方向
/// </summary>
public bool IsVerticalLayout()
{
return isVerticalLayout;
}
/// <summary>
/// 保存当前数据
/// </summary>
private Dictionary<string, Dictionary<string, double>> SaveCurrentData()
{
var data = new Dictionary<string, Dictionary<string, double>>();
if (sampleDataTable == null || sampleDataTable.Rows.Count == 0)
return data;
foreach (DataRow row in sampleDataTable.Rows)
{
string rowName = row["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
var rowData = new Dictionary<string, double>();
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
string colName = $"试样{i}_{j}";
if (row.Table.Columns.Contains(colName))
{
rowData[colName] = ConvertToDouble(row[colName]);
}
}
}
data[rowName] = rowData;
}
return data;
}
/// <summary>
/// 恢复数据
/// </summary>
private void RestoreData(Dictionary<string, Dictionary<string, double>> data)
{
if (data == null || data.Count == 0 || sampleDataTable == null)
return;
foreach (DataRow row in sampleDataTable.Rows)
{
string rowName = row["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName) || !data.ContainsKey(rowName))
continue;
var rowData = data[rowName];
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
string colName = $"试样{i}_{j}";
if (row.Table.Columns.Contains(colName) && rowData.ContainsKey(colName))
{
row[colName] = rowData[colName];
}
}
}
}
}
/// <summary>
/// 连接设备按钮点击事件
/// </summary>
private void Button1_Click(object sender, EventArgs e)
{
if (dataTimer.Enabled)
{
StopDataCollection();
UpdateButtonState(button1, "🔗 连接设备", Color.FromArgb(46, 204, 113));
ShowMessage("已停止数据采集");
}
else
{
StartDataCollection();
UpdateButtonState(button1, "⏸️ 停止采集", Color.FromArgb(231, 76, 60));
ShowMessage("开始数据采集");
}
}
/// <summary>
/// 打印按钮点击事件
/// </summary>
private void Button2_Click(object sender, EventArgs e)
{
ShowMessage("打印功能开发中...");
}
/// <summary>
/// 更新按钮状态
/// </summary>
private void UpdateButtonState(Button button, string text, Color backColor)
{
button.Text = text;
button.BackColor = backColor;
}
/// <summary>
/// 显示提示消息
/// </summary>
private void ShowMessage(string message, string title = "提示", MessageBoxIcon icon = MessageBoxIcon.Information)
{
MessageBox.Show(message, title, MessageBoxButtons.OK, icon);
}
/// <summary>
/// 导出按钮点击事件
/// </summary>
private void Button3_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Excel 文件 (*.xlsx)|*.xlsx|Excel 97-2003 (*.xls)|*.xls",
FileName = $"液体吸收测试报告_{DateTime.Now:yyyyMMdd_HHmmss}",
Title = "导出数据"
};
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
GenerateReport(saveFileDialog.FileName);
}
}
/// <summary>
/// 返回按钮点击事件
/// </summary>
private void Button4_Click(object sender, EventArgs e)
{
this.Close();
}
/// <summary>
/// 生成模拟数据按钮点击事件
/// </summary>
private void Button5_Click(object sender, EventArgs e)
{
GenerateMockData();
}
/// <summary>
/// 初始化数据表结构 - 包含所有必要的列
/// </summary>
private void InitializeDataTable()
{
sampleDataTable = new DataTable();
if (isVerticalLayout)
{
// 纵向布局:序号列 + 试样列
sampleDataTable.Columns.Add("序号", typeof(string));
// 为每个试样添加3列试样次数1、2、3
for (int i = 1; i <= currentSampleCount; i++)
{
sampleDataTable.Columns.Add($"试样{i}_1", typeof(double));
sampleDataTable.Columns.Add($"试样{i}_2", typeof(double));
sampleDataTable.Columns.Add($"试样{i}_3", typeof(double));
}
// 初始化5行数据
AddDataRow(ROW_WICKING_TIME);
AddDataRow(ROW_WICKING_HEIGHT);
AddDataRow(ROW_WICKING_RATE);
AddDataRow(ROW_AVG_WICKING_RATE);
AddDataRow(ROW_STD_DEVIATION);
}
else
{
// 横向布局:序号列 + 数据项列
sampleDataTable.Columns.Add("序号", typeof(string));
sampleDataTable.Columns.Add(ROW_WICKING_TIME, typeof(double));
sampleDataTable.Columns.Add(ROW_WICKING_HEIGHT, typeof(double));
sampleDataTable.Columns.Add(ROW_WICKING_RATE, typeof(double));
sampleDataTable.Columns.Add(ROW_AVG_WICKING_RATE, typeof(double));
sampleDataTable.Columns.Add(ROW_STD_DEVIATION, typeof(double));
// 为每个试样的每次测试添加一行
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
DataRow row = sampleDataTable.NewRow();
row["序号"] = $"试样{i}_{j}";
row[ROW_WICKING_TIME] = 0.0;
row[ROW_WICKING_HEIGHT] = 0.0;
row[ROW_WICKING_RATE] = 0.0;
row[ROW_AVG_WICKING_RATE] = 0.0;
row[ROW_STD_DEVIATION] = 0.0;
sampleDataTable.Rows.Add(row);
}
}
}
}
/// <summary>
/// 添加数据行
/// </summary>
private void AddDataRow(string rowName)
{
DataRow row = sampleDataTable.NewRow();
row["序号"] = rowName;
// 初始化所有数值列为0
for (int i = 1; i <= currentSampleCount; i++)
{
row[$"试样{i}_1"] = 0.0;
row[$"试样{i}_2"] = 0.0;
row[$"试样{i}_3"] = 0.0;
}
sampleDataTable.Rows.Add(row);
}
/// <summary>
/// 初始化定时器用于模拟寄存器数据读取
/// </summary>
private void InitializeTimer()
{
dataTimer = new System.Windows.Forms.Timer();
dataTimer.Interval = 1000; // 每秒读取一次
dataTimer.Tick += DataTimer_Tick;
}
/// <summary>
/// 定时器事件 - 模拟从寄存器读取数据
/// </summary>
private void DataTimer_Tick(object sender, EventArgs e)
{
if (isVerticalLayout)
{
// 纵向布局第1行读取吸水时间
DataRow timeRow = sampleDataTable.Rows[0];
for (int i = 1; i <= currentSampleCount; i++)
{
double time1 = ReadRegisterData((i - 1) * 3);
double time2 = ReadRegisterData((i - 1) * 3 + 1);
double time3 = ReadRegisterData((i - 1) * 3 + 2);
timeRow[$"试样{i}_1"] = time1;
timeRow[$"试样{i}_2"] = time2;
timeRow[$"试样{i}_3"] = time3;
}
}
else
{
// 横向布局:每行读取吸水时间
int rowIndex = 0;
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
if (rowIndex < sampleDataTable.Rows.Count)
{
double time = ReadRegisterData((i - 1) * 3 + (j - 1));
sampleDataTable.Rows[rowIndex][ROW_WICKING_TIME] = time;
rowIndex++;
}
}
}
}
// 计算所有相关数据
CalculateAllRows();
UpdateDisplay();
}
/// <summary>
/// 模拟从寄存器读取数据
/// </summary>
private double ReadRegisterData(int registerAddress)
{
// 吸水时间寄存器30-34秒
return 30 + random.NextDouble() * 4;
}
/// <summary>
/// 生成模拟测试数据
/// </summary>
public void GenerateMockData()
{
// 清空所有行的数据(保持表结构)
foreach (DataRow row in sampleDataTable.Rows)
{
for (int colIndex = 1; colIndex < sampleDataTable.Columns.Count; colIndex++)
{
row[colIndex] = 0.0;
}
}
if (isVerticalLayout)
{
// 纵向布局第1行生成吸水时间
DataRow timeRow = sampleDataTable.Rows[0];
for (int i = 1; i <= currentSampleCount; i++)
{
double baseTime = 31 + random.NextDouble() * 2;
double time1 = Math.Round(baseTime + (random.NextDouble() - 0.5) * 2, 2);
double time2 = Math.Round(baseTime + (random.NextDouble() - 0.5) * 2, 2);
double time3 = Math.Round(baseTime + (random.NextDouble() - 0.5) * 2, 2);
timeRow[$"试样{i}_1"] = time1;
timeRow[$"试样{i}_2"] = time2;
timeRow[$"试样{i}_3"] = time3;
}
}
else
{
// 横向布局:每行生成吸水时间
int rowIndex = 0;
for (int i = 1; i <= currentSampleCount; i++)
{
double baseTime = 31 + random.NextDouble() * 2;
for (int j = 1; j <= 3; j++)
{
if (rowIndex < sampleDataTable.Rows.Count)
{
double time = Math.Round(baseTime + (random.NextDouble() - 0.5) * 2, 2);
sampleDataTable.Rows[rowIndex][ROW_WICKING_TIME] = time;
rowIndex++;
}
}
}
}
// 计算所有相关数据
CalculateAllRows();
// 更新显示
RefreshDataGridView();
ShowMessage($"已生成 {currentSampleCount} 个试样的吸水时间数据\n" +
$"- 吸水时间30-34秒系统读数每个试样3次\n" +
$"- 吸芯高度请手动输入每个试样3次测试\n" +
$"- 芯吸速率:输入吸芯高度后自动计算",
"模拟数据生成");
}
/// <summary>
/// 更新界面显示
/// </summary>
private void UpdateDisplay()
{
RefreshDataGridView();
}
/// <summary>
/// 刷新DataGridView
/// </summary>
private void RefreshDataGridView()
{
if (dataGridView1.DataSource is DataTable dt)
{
dt.AcceptChanges();
dataGridView1.Refresh();
}
}
/// <summary>
/// 计算所有行的数据
/// </summary>
private void CalculateAllRows()
{
// 第3行芯吸速率mm/min= 吸芯高度 / (吸水时间 / 60)
CalculateWickingRate();
// 第4行平均芯吸速率mm/min
CalculateAverageWickingRate();
// 第5行标准偏差
CalculateStandardDeviation();
}
/// <summary>
/// 计算芯吸速率mm/min
/// 公式:芯吸速率 = 吸芯高度mm / (吸水时间s / 60)
/// </summary>
private void CalculateWickingRate()
{
if (isVerticalLayout)
{
// 纵向布局
DataRow timeRow = sampleDataTable.Rows[0];
DataRow heightRow = sampleDataTable.Rows[1];
DataRow rateRow = sampleDataTable.Rows[2];
for (int i = 1; i <= currentSampleCount; i++)
{
double time1 = ConvertToDouble(timeRow[$"试样{i}_1"]);
double time2 = ConvertToDouble(timeRow[$"试样{i}_2"]);
double time3 = ConvertToDouble(timeRow[$"试样{i}_3"]);
double height1 = ConvertToDouble(heightRow[$"试样{i}_1"]);
double height2 = ConvertToDouble(heightRow[$"试样{i}_2"]);
double height3 = ConvertToDouble(heightRow[$"试样{i}_3"]);
double rate1 = (time1 > 0 && height1 > 0) ? height1 / (time1 / 60.0) : 0;
double rate2 = (time2 > 0 && height2 > 0) ? height2 / (time2 / 60.0) : 0;
double rate3 = (time3 > 0 && height3 > 0) ? height3 / (time3 / 60.0) : 0;
rateRow[$"试样{i}_1"] = Math.Round(rate1, 2);
rateRow[$"试样{i}_2"] = Math.Round(rate2, 2);
rateRow[$"试样{i}_3"] = Math.Round(rate3, 2);
}
}
else
{
// 横向布局
foreach (DataRow row in sampleDataTable.Rows)
{
double time = ConvertToDouble(row[ROW_WICKING_TIME]);
double height = ConvertToDouble(row[ROW_WICKING_HEIGHT]);
double rate = (time > 0 && height > 0) ? height / (time / 60.0) : 0;
row[ROW_WICKING_RATE] = Math.Round(rate, 2);
}
}
}
/// <summary>
/// 安全地将对象转换为double处理DBNull和null情况
/// </summary>
private double ConvertToDouble(object value)
{
if (value == null || value == DBNull.Value)
{
return 0.0;
}
if (double.TryParse(value.ToString(), out double result))
{
return result;
}
return 0.0;
}
/// <summary>
/// 计算平均芯吸速率mm/min
/// 计算方式每个试样的3次测试计算一个平均值合并显示在该试样的一列中
/// 纵向布局显示在该试样的第1列试样N_1
/// 横向布局显示在该试样的第3次测试行
/// </summary>
private void CalculateAverageWickingRate()
{
if (isVerticalLayout)
{
// 纵向布局
DataRow rateRow = sampleDataTable.Rows[2];
DataRow avgRow = sampleDataTable.Rows[3];
// 清空平均值行的所有数据
for (int i = 1; i <= currentSampleCount; i++)
{
avgRow[$"试样{i}_1"] = 0.0;
avgRow[$"试样{i}_2"] = 0.0;
avgRow[$"试样{i}_3"] = 0.0;
}
// 计算每个试样的平均值并填充到该试样的第1列合并显示效果
for (int i = 1; i <= currentSampleCount; i++)
{
double rate1 = ConvertToDouble(rateRow[$"试样{i}_1"]);
double rate2 = ConvertToDouble(rateRow[$"试样{i}_2"]);
double rate3 = ConvertToDouble(rateRow[$"试样{i}_3"]);
List<double> groupRates = new List<double>();
if (rate1 > 0) groupRates.Add(rate1);
if (rate2 > 0) groupRates.Add(rate2);
if (rate3 > 0) groupRates.Add(rate3);
// 计算该试样的平均值
double groupAvg = groupRates.Count > 0 ? groupRates.Average() : 0;
// 只填充到该试样的第1列实现合并显示效果
avgRow[$"试样{i}_1"] = Math.Round(groupAvg, 2);
// 第2、3列保持为0显示为空白
}
}
else
{
// 横向布局
// 清空所有行的平均值列
foreach (DataRow row in sampleDataTable.Rows)
{
row[ROW_AVG_WICKING_RATE] = 0.0;
}
// 计算每个试样的平均值
for (int i = 1; i <= currentSampleCount; i++)
{
List<double> groupRates = new List<double>();
for (int j = 1; j <= 3; j++)
{
int rowIndex = (i - 1) * 3 + (j - 1);
if (rowIndex < sampleDataTable.Rows.Count)
{
double rate = ConvertToDouble(sampleDataTable.Rows[rowIndex][ROW_WICKING_RATE]);
if (rate > 0) groupRates.Add(rate);
}
}
// 计算该试样的平均值
double groupAvg = groupRates.Count > 0 ? groupRates.Average() : 0;
// 填充到该试样的第3次测试行合并显示效果
int targetRowIndex = (i - 1) * 3 + 2; // 第3次测试的行索引
if (targetRowIndex < sampleDataTable.Rows.Count)
{
sampleDataTable.Rows[targetRowIndex][ROW_AVG_WICKING_RATE] = Math.Round(groupAvg, 2);
}
}
}
}
/// <summary>
/// 计算标准偏差
/// 计算方式:基于每个试样的平均芯吸速率计算标准偏差(组间标准偏差)
/// 结果只有一个标准偏差值合并显示在第1列
///
/// 说明:
/// - 每5个试样为一组计算该组的标准偏差
/// - 如果试样数量不足5个则计算现有试样的标准偏差
/// - 标准偏差基于每个试样的平均芯吸速率计算
/// </summary>
private void CalculateStandardDeviation()
{
if (isVerticalLayout)
{
// 纵向布局
DataRow rateRow = sampleDataTable.Rows[2];
DataRow stdRow = sampleDataTable.Rows[4];
// 清空标准偏差行的所有数据
for (int i = 1; i <= currentSampleCount; i++)
{
stdRow[$"试样{i}_1"] = 0.0;
stdRow[$"试样{i}_2"] = 0.0;
stdRow[$"试样{i}_3"] = 0.0;
}
// 计算每组5个试样的标准偏差
int groupCount = (int)Math.Ceiling((double)currentSampleCount / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
int endSample = Math.Min(startSample + 4, currentSampleCount);
List<double> groupAverages = new List<double>(); // 存储该组每个试样的平均值
// 计算该组每个试样的平均值
for (int i = startSample; i <= endSample; i++)
{
double rate1 = ConvertToDouble(rateRow[$"试样{i}_1"]);
double rate2 = ConvertToDouble(rateRow[$"试样{i}_2"]);
double rate3 = ConvertToDouble(rateRow[$"试样{i}_3"]);
List<double> sampleRates = new List<double>();
if (rate1 > 0) sampleRates.Add(rate1);
if (rate2 > 0) sampleRates.Add(rate2);
if (rate3 > 0) sampleRates.Add(rate3);
// 如果该试样有有效数据,计算该试样的平均值
if (sampleRates.Count > 0)
{
double sampleAvg = sampleRates.Average();
groupAverages.Add(sampleAvg);
}
}
// 基于该组试样的平均值计算标准偏差
double stdDev = 0;
if (groupAverages.Count > 1)
{
double overallAverage = groupAverages.Average();
double sumOfSquares = groupAverages.Sum(avg => Math.Pow(avg - overallAverage, 2));
stdDev = Math.Sqrt(sumOfSquares / (groupAverages.Count - 1));
}
// 只在该组第1个试样的第1列显示标准偏差合并显示效果
stdRow[$"试样{startSample}_1"] = Math.Round(stdDev, 2);
}
}
else
{
// 横向布局
// 清空所有行的标准偏差列
foreach (DataRow row in sampleDataTable.Rows)
{
row[ROW_STD_DEVIATION] = 0.0;
}
// 计算每组5个试样的标准偏差
int groupCount = (int)Math.Ceiling((double)currentSampleCount / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
int endSample = Math.Min(startSample + 4, currentSampleCount);
List<double> groupAverages = new List<double>(); // 存储该组每个试样的平均值
// 计算该组每个试样的平均值
for (int i = startSample; i <= endSample; i++)
{
List<double> sampleRates = new List<double>();
for (int j = 1; j <= 3; j++)
{
int rowIndex = (i - 1) * 3 + (j - 1);
if (rowIndex < sampleDataTable.Rows.Count)
{
double rate = ConvertToDouble(sampleDataTable.Rows[rowIndex][ROW_WICKING_RATE]);
if (rate > 0) sampleRates.Add(rate);
}
}
// 如果该试样有有效数据,计算该试样的平均值
if (sampleRates.Count > 0)
{
double sampleAvg = sampleRates.Average();
groupAverages.Add(sampleAvg);
}
}
// 基于该组试样的平均值计算标准偏差
double stdDev = 0;
if (groupAverages.Count > 1)
{
double overallAverage = groupAverages.Average();
double sumOfSquares = groupAverages.Sum(avg => Math.Pow(avg - overallAverage, 2));
stdDev = Math.Sqrt(sumOfSquares / (groupAverages.Count - 1));
}
// 只在该组第1个试样的第1次测试行显示标准偏差合并显示效果
int targetRowIndex = (startSample - 1) * 3; // 第1次测试的行索引
if (targetRowIndex < sampleDataTable.Rows.Count)
{
sampleDataTable.Rows[targetRowIndex][ROW_STD_DEVIATION] = Math.Round(stdDev, 2);
}
}
}
}
/// <summary>
/// 启动数据采集
/// </summary>
public void StartDataCollection()
{
dataTimer.Start();
}
/// <summary>
/// 停止数据采集
/// </summary>
public void StopDataCollection()
{
dataTimer.Stop();
}
/// <summary>
/// 导出数据到 Excel (.xlsx)
/// </summary>
public void ExportToExcel(string filePath)
{
try
{
IWorkbook workbook = filePath.EndsWith(".xlsx")
? (IWorkbook)new XSSFWorkbook()
: new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet("液体芯吸速率测试报表");
// 创建样式
var styles = CreateReportStyles(workbook);
// 创建表头2级
CreateReportHeader(sheet, styles.headerStyle);
// 填充数据
FillReportData(sheet, styles.dataStyle, styles.yellowStyle);
// 设置列宽
SetReportColumnWidths(sheet);
// 保存文件
SaveWorkbook(workbook, filePath);
ShowMessage($"数据已成功导出到:{filePath}", "导出成功");
}
catch (Exception ex)
{
ShowMessage($"导出失败:{ex.Message}", "错误", MessageBoxIcon.Error);
}
}
/// <summary>
/// 创建报表样式
/// </summary>
private (ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle)
CreateReportStyles(IWorkbook workbook)
{
// 表头样式
ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
SetBorders(headerStyle);
headerStyle.Alignment = NPOIHorizontalAlignment.Center;
headerStyle.VerticalAlignment = VerticalAlignment.Center;
IFont headerFont = workbook.CreateFont();
headerFont.IsBold = true;
headerStyle.SetFont(headerFont);
// 白色背景样式
ICellStyle dataStyle = workbook.CreateCellStyle();
SetBorders(dataStyle);
dataStyle.Alignment = NPOIHorizontalAlignment.Center;
dataStyle.VerticalAlignment = VerticalAlignment.Center;
// 黄色背景样式
ICellStyle yellowStyle = workbook.CreateCellStyle();
yellowStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.LightYellow.Index;
yellowStyle.FillPattern = FillPattern.SolidForeground;
SetBorders(yellowStyle);
yellowStyle.Alignment = NPOIHorizontalAlignment.Center;
yellowStyle.VerticalAlignment = VerticalAlignment.Center;
return (headerStyle, dataStyle, yellowStyle);
}
/// <summary>
/// 设置单元格边框
/// </summary>
private void SetBorders(ICellStyle style)
{
style.BorderBottom = NPOIBorderStyle.Thin;
style.BorderTop = NPOIBorderStyle.Thin;
style.BorderLeft = NPOIBorderStyle.Thin;
style.BorderRight = NPOIBorderStyle.Thin;
}
/// <summary>
/// 创建报表表头2级
/// </summary>
private void CreateReportHeader(ISheet sheet, ICellStyle headerStyle)
{
// 创建第一行表头试样1-N
IRow headerRow1 = sheet.CreateRow(0);
headerRow1.Height = 400;
ICell cell0 = headerRow1.CreateCell(0);
cell0.SetCellValue("序号");
cell0.CellStyle = headerStyle;
sheet.AddMergedRegion(new CellRangeAddress(0, 1, 0, 0)); // 合并序号列
int colIndex = 1;
for (int i = 1; i <= currentSampleCount; i++)
{
ICell cell = headerRow1.CreateCell(colIndex);
cell.SetCellValue($"试样{i}");
cell.CellStyle = headerStyle;
sheet.AddMergedRegion(new CellRangeAddress(0, 0, colIndex, colIndex + 2));
colIndex += 3;
}
// 创建第二行表头试样次数1、2、3
IRow headerRow2 = sheet.CreateRow(1);
headerRow2.Height = 400;
colIndex = 1;
for (int i = 1; i <= currentSampleCount; i++)
{
ICell cell1 = headerRow2.CreateCell(colIndex++);
cell1.SetCellValue("1");
cell1.CellStyle = headerStyle;
ICell cell2 = headerRow2.CreateCell(colIndex++);
cell2.SetCellValue("2");
cell2.CellStyle = headerStyle;
ICell cell3 = headerRow2.CreateCell(colIndex++);
cell3.SetCellValue("3");
cell3.CellStyle = headerStyle;
}
}
/// <summary>
/// 填充报表数据
/// </summary>
private void FillReportData(ISheet sheet, ICellStyle dataStyle, ICellStyle yellowStyle)
{
int rowIndex = 2;
int dataRowIndex = 0;
foreach (DataRow dataRow in sampleDataTable.Rows)
{
IRow row = sheet.CreateRow(rowIndex);
row.Height = 380;
// 根据行索引选择样式(隔行变色)
ICellStyle rowStyle = (dataRowIndex % 2 == 0) ? dataStyle : yellowStyle;
// 序号列
ICell cellSeq = row.CreateCell(0);
cellSeq.SetCellValue(dataRow["序号"].ToString());
cellSeq.CellStyle = rowStyle;
int colIndex = 1;
for (int i = 1; i <= currentSampleCount; i++)
{
// 试样次数1
ICell cell1 = row.CreateCell(colIndex++);
double val1 = ConvertToDouble(dataRow[$"试样{i}_1"]);
if (Math.Abs(val1) >= 0.001) // 只有非0值才显示
{
cell1.SetCellValue(val1);
}
cell1.CellStyle = rowStyle;
// 试样次数2
ICell cell2 = row.CreateCell(colIndex++);
double val2 = ConvertToDouble(dataRow[$"试样{i}_2"]);
if (Math.Abs(val2) >= 0.001) // 只有非0值才显示
{
cell2.SetCellValue(val2);
}
cell2.CellStyle = rowStyle;
// 试样次数3
ICell cell3 = row.CreateCell(colIndex++);
double val3 = ConvertToDouble(dataRow[$"试样{i}_3"]);
if (Math.Abs(val3) >= 0.001) // 只有非0值才显示
{
cell3.SetCellValue(val3);
}
cell3.CellStyle = rowStyle;
}
rowIndex++;
dataRowIndex++;
}
}
/// <summary>
/// 设置报表列宽
/// </summary>
private void SetReportColumnWidths(ISheet sheet)
{
sheet.SetColumnWidth(0, 20 * 256); // 序号列
int totalColumns = currentSampleCount * 3;
for (int i = 1; i <= totalColumns; i++)
{
sheet.SetColumnWidth(i, 12 * 256); // 数据列
}
}
/// <summary>
/// 保存工作簿
/// </summary>
private void SaveWorkbook(IWorkbook workbook, string filePath)
{
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(fs);
}
}
/// <summary>
/// 生成报表(带格式的 Excel
/// </summary>
public void GenerateReport(string filePath)
{
// 使用 ExportToExcel 方法
ExportToExcel(filePath);
}
/// <summary>
/// 清空数据
/// </summary>
public void ClearData()
{
sampleDataTable.Clear();
InitializeDataTable();
RefreshDataGridView();
}
}
}