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; // 当前试样数量,可动态调整 private readonly Random random = new Random(); #endregion public Form3() { InitializeComponent(); InitializeDataTable(); InitializeTimer(); InitializeDataGridView(); InitializeEventHandlers(); } /// /// 设置试样数量(1-20) /// public void SetSampleCount(int count) { if (count < 1 || count > 20) { ShowMessage("试样数量必须在1-20之间", "参数错误", MessageBoxIcon.Warning); return; } currentSampleCount = count; // 重新初始化数据表和界面 sampleDataTable.Clear(); InitializeDataTable(); InitializeDataGridView(); UpdateDisplay(); } /// /// 初始化 DataGridView 列 - 实现2级表头 /// 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 }; // 序号列 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 = false, DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone() }); dataGridView1.Columns.Add(new DataGridViewTextBoxColumn { Name = $"试样{i}_2", HeaderText = $"试样{i}\n2", DataPropertyName = $"试样{i}_2", Width = 100, ReadOnly = false, DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone() }); dataGridView1.Columns.Add(new DataGridViewTextBoxColumn { Name = $"试样{i}_3", HeaderText = $"试样{i}\n3", DataPropertyName = $"试样{i}_3", Width = 100, ReadOnly = false, DefaultCellStyle = (DataGridViewCellStyle)centerStyle.Clone() }); } // 绑定数据源 dataGridView1.DataSource = sampleDataTable; dataGridView1.CellFormatting += DataGridView1_CellFormatting; dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; dataGridView1.CellBeginEdit += DataGridView1_CellBeginEdit; } finally { dataGridView1.ResumeLayout(); } } /// /// 单元格编辑前事件 - 动态控制哪些单元格可以编辑 /// private void DataGridView1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e) { if (e.RowIndex >= 0 && e.ColumnIndex > 0) { string rowName = dataGridView1.Rows[e.RowIndex].Cells["序号"].Value?.ToString() ?? ""; // 吸芯高度(mm)行 - 所有列都可以编辑 if (rowName == ROW_WICKING_HEIGHT) { return; // 允许编辑 } // 其他行 - 取消编辑 e.Cancel = true; } } /// /// 单元格格式化事件 - 处理数值显示格式 /// 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; } } /// /// 单元格值改变事件 - 手动输入后重新计算 /// private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex >= 0 && e.ColumnIndex >= 0) { string rowName = dataGridView1.Rows[e.RowIndex].Cells["序号"].Value?.ToString() ?? ""; // 如果是吸芯高度行,任何列的修改都触发重新计算 if (rowName == ROW_WICKING_HEIGHT) { CalculateAllRows(); RefreshDataGridView(); } } } /// /// 初始化事件处理器 /// 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(); } /// /// 连接设备按钮点击事件 /// 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("开始数据采集"); } } /// /// 打印按钮点击事件 /// private void Button2_Click(object sender, EventArgs e) { ShowMessage("打印功能开发中..."); } /// /// 更新按钮状态 /// private void UpdateButtonState(Button button, string text, Color backColor) { button.Text = text; button.BackColor = backColor; } /// /// 显示提示消息 /// private void ShowMessage(string message, string title = "提示", MessageBoxIcon icon = MessageBoxIcon.Information) { MessageBox.Show(message, title, MessageBoxButtons.OK, icon); } /// /// 导出按钮点击事件 /// 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); } } /// /// 返回按钮点击事件 /// private void Button4_Click(object sender, EventArgs e) { this.Close(); } /// /// 生成模拟数据按钮点击事件 /// private void Button5_Click(object sender, EventArgs e) { GenerateMockData(); } /// /// 初始化数据表结构 - 包含所有必要的列 /// private void InitializeDataTable() { sampleDataTable = new DataTable(); // 序号列 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); } /// /// 添加数据行 /// 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); } /// /// 初始化定时器用于模拟寄存器数据读取 /// private void InitializeTimer() { dataTimer = new System.Windows.Forms.Timer(); dataTimer.Interval = 1000; // 每秒读取一次 dataTimer.Tick += DataTimer_Tick; } /// /// 定时器事件 - 模拟从寄存器读取数据 /// private void DataTimer_Tick(object sender, EventArgs e) { // 第1行:读取吸水时间(s) - 从寄存器读取(每个试样读取3次) DataRow timeRow = sampleDataTable.Rows[0]; for (int i = 1; i <= currentSampleCount; i++) { double time1 = ReadRegisterData((i - 1) * 3); // 第1次测试 double time2 = ReadRegisterData((i - 1) * 3 + 1); // 第2次测试 double time3 = ReadRegisterData((i - 1) * 3 + 2); // 第3次测试 timeRow[$"试样{i}_1"] = time1; timeRow[$"试样{i}_2"] = time2; timeRow[$"试样{i}_3"] = time3; } UpdateDisplay(); } /// /// 模拟从寄存器读取数据 /// private double ReadRegisterData(int registerAddress) { // 吸水时间寄存器:30-34秒 return 30 + random.NextDouble() * 4; } /// /// 生成模拟测试数据 /// public void GenerateMockData() { // 清空所有行的数据(保持表结构) foreach (DataRow row in sampleDataTable.Rows) { for (int i = 1; i <= currentSampleCount; i++) { row[$"试样{i}_1"] = 0.0; row[$"试样{i}_2"] = 0.0; row[$"试样{i}_3"] = 0.0; } } // 第1行:吸水时间(s)- 系统读数(每个试样3次) DataRow timeRow = sampleDataTable.Rows[0]; for (int i = 1; i <= currentSampleCount; i++) { // 生成3次测试的吸水时间数据(30-34秒,略有差异) double baseTime = 31 + random.NextDouble() * 2; // 基准时间 31-33秒 double time1 = Math.Round(baseTime + (random.NextDouble() - 0.5) * 2, 2); // ±1秒 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; } // 第2行:吸芯高度(mm)- 手动输入,不自动生成 // 保持为0,等待用户手动输入 // 第3-5行:芯吸速率、平均值、标准偏差 - 保持为0 // 等待用户输入吸芯高度后,会自动计算 // 更新显示 RefreshDataGridView(); ShowMessage($"已生成 {currentSampleCount} 个试样的吸水时间数据\n" + $"- 吸水时间:30-34秒(系统读数,每个试样3次)\n" + $"- 吸芯高度:请手动输入(每个试样3次测试)\n" + $"- 芯吸速率:输入吸芯高度后自动计算", "模拟数据生成"); } /// /// 更新界面显示 /// private void UpdateDisplay() { RefreshDataGridView(); } /// /// 刷新DataGridView /// private void RefreshDataGridView() { if (dataGridView1.DataSource is DataTable dt) { dt.AcceptChanges(); dataGridView1.Refresh(); } } /// /// 计算所有行的数据 /// private void CalculateAllRows() { // 第3行:芯吸速率(mm/min)= 吸芯高度 / (吸水时间 / 60) CalculateWickingRate(); // 第4行:平均芯吸速率(mm/min) CalculateAverageWickingRate(); // 第5行:标准偏差 CalculateStandardDeviation(); } /// /// 计算芯吸速率(mm/min) /// 公式:芯吸速率 = 吸芯高度(mm) / (吸水时间(s) / 60) /// private void CalculateWickingRate() { DataRow timeRow = sampleDataTable.Rows[0]; // 吸水时间 DataRow heightRow = sampleDataTable.Rows[1]; // 吸芯高度 DataRow rateRow = sampleDataTable.Rows[2]; // 芯吸速率 for (int i = 1; i <= currentSampleCount; i++) { // 获取3次吸水时间测试数据 double time1 = ConvertToDouble(timeRow[$"试样{i}_1"]); double time2 = ConvertToDouble(timeRow[$"试样{i}_2"]); double time3 = ConvertToDouble(timeRow[$"试样{i}_3"]); // 获取3次吸芯高度测试数据 double height1 = ConvertToDouble(heightRow[$"试样{i}_1"]); double height2 = ConvertToDouble(heightRow[$"试样{i}_2"]); double height3 = ConvertToDouble(heightRow[$"试样{i}_3"]); // 计算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); } } /// /// 安全地将对象转换为double,处理DBNull和null情况 /// 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; } /// /// 计算平均芯吸速率(mm/min) /// private void CalculateAverageWickingRate() { DataRow rateRow = sampleDataTable.Rows[2]; // 芯吸速率 DataRow avgRow = sampleDataTable.Rows[3]; // 平均芯吸速率 List rates = new List(); for (int i = 1; i <= currentSampleCount; i++) { double rate = ConvertToDouble(rateRow[$"试样{i}_3"]); if (rate > 0) { rates.Add(rate); } } double average = rates.Count > 0 ? rates.Average() : 0; // 所有试样显示相同的平均值(存储在试样次数3) for (int i = 1; i <= currentSampleCount; i++) { avgRow[$"试样{i}_3"] = Math.Round(average, 2); } } /// /// 计算标准偏差 /// private void CalculateStandardDeviation() { DataRow rateRow = sampleDataTable.Rows[2]; // 芯吸速率 DataRow stdRow = sampleDataTable.Rows[4]; // 标准偏差 List rates = new List(); for (int i = 1; i <= currentSampleCount; i++) { double rate = ConvertToDouble(rateRow[$"试样{i}_3"]); if (rate > 0) { rates.Add(rate); } } double stdDev = 0; if (rates.Count > 1) { double average = rates.Average(); double sumOfSquares = rates.Sum(r => Math.Pow(r - average, 2)); stdDev = Math.Sqrt(sumOfSquares / (rates.Count - 1)); } // 所有试样显示相同的标准偏差(存储在试样次数3) for (int i = 1; i <= currentSampleCount; i++) { stdRow[$"试样{i}_3"] = Math.Round(stdDev, 2); } } /// /// 启动数据采集 /// public void StartDataCollection() { dataTimer.Start(); } /// /// 停止数据采集 /// public void StopDataCollection() { dataTimer.Stop(); } /// /// 导出数据到 Excel (.xlsx) /// 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); } } /// /// 创建报表样式 /// 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); } /// /// 设置单元格边框 /// private void SetBorders(ICellStyle style) { style.BorderBottom = NPOIBorderStyle.Thin; style.BorderTop = NPOIBorderStyle.Thin; style.BorderLeft = NPOIBorderStyle.Thin; style.BorderRight = NPOIBorderStyle.Thin; } /// /// 创建报表表头(2级) /// 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; } } /// /// 填充报表数据 /// 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++; } } /// /// 设置报表列宽 /// 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); // 数据列 } } /// /// 保存工作簿 /// private void SaveWorkbook(IWorkbook workbook, string filePath) { using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { workbook.Write(fs); } } /// /// 生成报表(带格式的 Excel) /// public void GenerateReport(string filePath) { // 使用 ExportToExcel 方法 ExportToExcel(filePath); } /// /// 清空数据 /// public void ClearData() { sampleDataTable.Clear(); InitializeDataTable(); RefreshDataGridView(); } } }