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 { private System.Windows.Forms.Timer dataTimer; private DataTable sampleDataTable; public Form3() { InitializeComponent(); InitializeDataTable(); InitializeTimer(); InitializeDataGridView(); InitializeEventHandlers(); } /// /// 初始化 DataGridView 列 - 实现复杂多级表头 /// private void InitializeDataGridView() { // 禁用自动生成列 dataGridView1.AutoGenerateColumns = false; // 先解除数据绑定 dataGridView1.DataSource = null; // 清除现有列 dataGridView1.Columns.Clear(); // 先移除可能存在的事件处理器,避免重复绑定 dataGridView1.CellFormatting -= DataGridView1_CellFormatting; dataGridView1.CellValueChanged -= DataGridView1_CellValueChanged; dataGridView1.CellBeginEdit -= DataGridView1_CellBeginEdit; // 白色背景样式(只读)- 不设置BackColor,让隔行变色生效 DataGridViewCellStyle readonlyStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.MiddleCenter, SelectionBackColor = Color.LightGray }; // 白色背景样式(可编辑)- 不设置BackColor,让隔行变色生效 DataGridViewCellStyle editableStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.MiddleCenter, SelectionBackColor = Color.LightBlue }; // 序号列 DataGridViewTextBoxColumn seqCol = new DataGridViewTextBoxColumn { Name = "序号", HeaderText = "序号", DataPropertyName = "序号", Width = 120, ReadOnly = true, DefaultCellStyle = (DataGridViewCellStyle)readonlyStyle.Clone() }; dataGridView1.Columns.Add(seqCol); // 为每个试样添加3列(试样次数:1、2、3) for (int i = 1; i <= 5; i++) { // 列1:试样次数1 DataGridViewTextBoxColumn col1 = new DataGridViewTextBoxColumn { Name = $"试样{i}_1", HeaderText = $"试样{i}\n1", DataPropertyName = $"试样{i}_1", Width = 100, ReadOnly = false, DefaultCellStyle = (DataGridViewCellStyle)editableStyle.Clone() }; dataGridView1.Columns.Add(col1); // 列2:试样次数2 DataGridViewTextBoxColumn col2 = new DataGridViewTextBoxColumn { Name = $"试样{i}_2", HeaderText = $"试样{i}\n2", DataPropertyName = $"试样{i}_2", Width = 100, ReadOnly = false, DefaultCellStyle = (DataGridViewCellStyle)editableStyle.Clone() }; dataGridView1.Columns.Add(col2); // 列3:试样次数3 DataGridViewTextBoxColumn col3 = new DataGridViewTextBoxColumn { Name = $"试样{i}_3", HeaderText = $"试样{i}\n3", DataPropertyName = $"试样{i}_3", Width = 100, ReadOnly = false, DefaultCellStyle = (DataGridViewCellStyle)editableStyle.Clone() }; dataGridView1.Columns.Add(col3); } // 绑定数据源 dataGridView1.DataSource = sampleDataTable; // 调试:输出列信息 System.Diagnostics.Debug.WriteLine("=== DataGridView 列信息 ==="); System.Diagnostics.Debug.WriteLine($"DataTable 列数: {sampleDataTable.Columns.Count}"); System.Diagnostics.Debug.WriteLine($"DataGridView 列数: {dataGridView1.Columns.Count}"); for (int i = 0; i < dataGridView1.Columns.Count; i++) { var col = dataGridView1.Columns[i]; System.Diagnostics.Debug.WriteLine($"列{i}: Name={col.Name}, DataPropertyName={col.DataPropertyName}"); } // 添加单元格格式化事件 dataGridView1.CellFormatting += DataGridView1_CellFormatting; // 添加单元格编辑事件(用于手动输入后重新计算) dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; // 添加单元格编辑前事件(动态控制可编辑性) dataGridView1.CellBeginEdit += DataGridView1_CellBeginEdit; } /// /// 单元格编辑前事件 - 动态控制哪些单元格可以编辑 /// 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 == "吸芯高度(mm)") { return; // 允许编辑 } // 其他行 - 取消编辑 e.Cancel = true; } } /// /// 单元格格式化事件 - 处理数值显示格式 /// private void DataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (e.RowIndex >= 0 && e.Value != null && e.Value != DBNull.Value) { // 格式化数值列(保留2位小数) string columnName = dataGridView1.Columns[e.ColumnIndex].Name; if (columnName.Contains("试样") && columnName != "序号") { if (double.TryParse(e.Value.ToString(), out double value)) { // 如果值为0,根据行和列决定是否显示 if (value == 0) { string rowName = dataGridView1.Rows[e.RowIndex].Cells["序号"].Value?.ToString() ?? ""; // 根据示意图,某些单元格应该显示为空白 if (ShouldBeEmpty(rowName, columnName)) { e.Value = ""; e.FormattingApplied = true; return; } } e.Value = value.ToString("F2"); e.FormattingApplied = true; } } } } /// /// 判断单元格是否应该为空白 /// private bool ShouldBeEmpty(string rowName, string columnName) { // 吸水时间(s) - 只有试样次数1有数据 if (rowName == "吸水时间(s)") { return columnName.EndsWith("_2") || columnName.EndsWith("_3"); } // 吸芯高度(mm) - 所有列都有数据(全部可编辑) if (rowName == "吸芯高度(mm)") { return false; // 不应该为空 } // 芯吸速率(mm/min) - 只有试样次数3有数据 if (rowName == "芯吸速率(mm/min)") { return columnName.EndsWith("_1") || columnName.EndsWith("_2"); } // 平均芯吸速率(mm/min) - 只有试样次数3有数据 if (rowName == "平均芯吸速率(mm/min)") { return columnName.EndsWith("_1") || columnName.EndsWith("_2"); } // 标准偏差 - 只有试样次数3有数据 if (rowName == "标准偏差") { return columnName.EndsWith("_1") || columnName.EndsWith("_2"); } return false; } /// /// 单元格值改变事件 - 手动输入后重新计算 /// 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 == "吸芯高度(mm)") { RecalculateRow(e.RowIndex); } } } /// /// 初始化事件处理器 /// private void InitializeEventHandlers() { // 连接设备按钮 button1.Click += Button1_Click; // 打印按钮 button2.Click += Button2_Click; // 导出按钮 button3.Click += Button3_Click; // 返回按钮 button4.Click += Button4_Click; // 生成模拟数据按钮 button5.Click += Button5_Click; // 更新日期时间标签 System.Windows.Forms.Timer clockTimer = new System.Windows.Forms.Timer(); clockTimer.Interval = 1000; clockTimer.Tick += (s, e) => label2.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); clockTimer.Start(); } /// /// 连接设备按钮点击事件 /// private void Button1_Click(object sender, EventArgs e) { if (dataTimer.Enabled) { StopDataCollection(); button1.Text = "🔗 连接设备"; button1.BackColor = Color.FromArgb(46, 204, 113); MessageBox.Show("已停止数据采集", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { StartDataCollection(); button1.Text = "⏸️ 停止采集"; button1.BackColor = Color.FromArgb(231, 76, 60); MessageBox.Show("开始数据采集", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// /// 打印按钮点击事件 /// private void Button2_Click(object sender, EventArgs e) { MessageBox.Show("打印功能开发中...", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } /// /// 导出按钮点击事件 /// 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 <= 5; i++) { sampleDataTable.Columns.Add($"试样{i}_1", typeof(double)); // 系统读数 sampleDataTable.Columns.Add($"试样{i}_2", typeof(double)); // 手动输入 sampleDataTable.Columns.Add($"试样{i}_3", typeof(double)); // 系统计算 } // 初始化6行数据 AddDataRow("吸水时间(s)"); AddDataRow("吸芯高度(mm)"); AddDataRow("芯吸速率(mm/min)"); AddDataRow("平均芯吸速率(mm/min)"); AddDataRow("标准偏差"); } /// /// 添加数据行 /// private void AddDataRow(string rowName) { DataRow row = sampleDataTable.NewRow(); row["序号"] = rowName; // 初始化所有数值列为0 for (int i = 1; i <= 5; 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) { // 读取吸水时间(s)- 从寄存器读取 DataRow timeRow = sampleDataTable.Rows[0]; // 第一行:吸水时间 for (int i = 1; i <= 5; i++) { double registerValue = ReadRegisterData(i - 1); timeRow[$"试样{i}_1"] = registerValue; } // 触发界面更新 UpdateDisplay(); } /// /// 模拟从寄存器读取数据 /// 实际应用中替换为真实的 Modbus 或其他协议读取 /// private double ReadRegisterData(int registerAddress) { // 模拟数据:30-34 之间的随机值 Random random = new Random(Guid.NewGuid().GetHashCode()); return 30 + random.NextDouble() * 4; } /// /// 生成模拟测试数据 /// public void GenerateMockData() { Random random = new Random(); // 第1行:吸水时间(s)- 试样次数1(系统读数) DataRow timeRow = sampleDataTable.Rows[0]; for (int i = 1; i <= 5; i++) { double timeValue = Math.Round(30 + random.NextDouble() * 4, 2); timeRow[$"试样{i}_1"] = timeValue; System.Diagnostics.Debug.WriteLine($"吸水时间 - 试样{i}_1 = {timeValue}"); } // 第2行:吸芯高度(mm)- 所有列都可编辑(手动输入) DataRow heightRow = sampleDataTable.Rows[1]; for (int i = 1; i <= 5; i++) { double height1 = Math.Round(50 + random.NextDouble() * 20, 2); double height2 = Math.Round(50 + random.NextDouble() * 20, 2); double height3 = Math.Round(50 + random.NextDouble() * 20, 2); heightRow[$"试样{i}_1"] = height1; heightRow[$"试样{i}_2"] = height2; heightRow[$"试样{i}_3"] = height3; System.Diagnostics.Debug.WriteLine($"吸芯高度 - 试样{i}_1 = {height1}"); System.Diagnostics.Debug.WriteLine($"吸芯高度 - 试样{i}_2 = {height2}"); System.Diagnostics.Debug.WriteLine($"吸芯高度 - 试样{i}_3 = {height3}"); } // 计算所有行 CalculateAllRows(); // 更新显示 UpdateDisplay(); // 调试信息:验证数据 System.Diagnostics.Debug.WriteLine("=== 模拟数据生成完成 ==="); DataRow rateRow = sampleDataTable.Rows[2]; for (int i = 1; i <= 5; i++) { System.Diagnostics.Debug.WriteLine($"芯吸速率 - 试样{i}_3 = {rateRow[$"试样{i}_3"]}"); } MessageBox.Show("已生成模拟数据\n请查看输出窗口的调试信息", "模拟数据生成", MessageBoxButtons.OK, MessageBoxIcon.Information); } /// /// 更新界面显示 /// private void UpdateDisplay() { // 刷新 DataGridView if (dataGridView1.DataSource != null) { ((DataTable)dataGridView1.DataSource).AcceptChanges(); dataGridView1.Refresh(); } } /// /// 计算所有行的数据 /// private void CalculateAllRows() { // 第3行:芯吸速率(mm/min)= 吸芯高度 / (吸水时间 / 60) CalculateWickingRate(); // 第4行:平均芯吸速率(mm/min) CalculateAverageWickingRate(); // 第5行:标准偏差 CalculateStandardDeviation(); } /// /// 重新计算指定行 /// private void RecalculateRow(int rowIndex) { CalculateAllRows(); UpdateDisplay(); } /// /// 计算芯吸速率(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 <= 5; i++) { // 获取吸水时间(试样次数1:系统读数) double time = ConvertToDouble(timeRow[$"试样{i}_1"]); // 获取吸芯高度(优先使用对应列的数据) // 试样次数1 → 使用吸芯高度_1 // 试样次数2 → 使用吸芯高度_2 // 试样次数3 → 使用吸芯高度_3 double height1 = ConvertToDouble(heightRow[$"试样{i}_1"]); double height2 = ConvertToDouble(heightRow[$"试样{i}_2"]); double height3 = ConvertToDouble(heightRow[$"试样{i}_3"]); // 计算芯吸速率(使用对应的吸芯高度) double rate = 0; if (time > 0) { // 使用平均吸芯高度或选择非零值 double avgHeight = 0; int count = 0; if (height1 > 0) { avgHeight += height1; count++; } if (height2 > 0) { avgHeight += height2; count++; } if (height3 > 0) { avgHeight += height3; count++; } if (count > 0) { avgHeight /= count; rate = avgHeight / (time / 60.0); } } // 存储到试样次数3(系统计算) rateRow[$"试样{i}_3"] = Math.Round(rate, 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 <= 5; 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 <= 5; 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 <= 5; 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 <= 5; 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; // 根据文件扩展名选择格式 if (filePath.EndsWith(".xlsx")) { workbook = new XSSFWorkbook(); // Excel 2007+ } else { workbook = new HSSFWorkbook(); // Excel 97-2003 } ISheet sheet = workbook.CreateSheet("液体吸收测试报表"); // 创建样式 ICellStyle headerStyle = workbook.CreateCellStyle(); headerStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index; headerStyle.FillPattern = FillPattern.SolidForeground; headerStyle.BorderBottom = NPOIBorderStyle.Thin; headerStyle.BorderTop = NPOIBorderStyle.Thin; headerStyle.BorderLeft = NPOIBorderStyle.Thin; headerStyle.BorderRight = NPOIBorderStyle.Thin; headerStyle.Alignment = NPOIHorizontalAlignment.Center; headerStyle.VerticalAlignment = VerticalAlignment.Center; IFont headerFont = workbook.CreateFont(); headerFont.IsBold = true; headerStyle.SetFont(headerFont); // 白色背景样式(偶数行) ICellStyle whiteStyle = workbook.CreateCellStyle(); whiteStyle.BorderBottom = NPOIBorderStyle.Thin; whiteStyle.BorderTop = NPOIBorderStyle.Thin; whiteStyle.BorderLeft = NPOIBorderStyle.Thin; whiteStyle.BorderRight = NPOIBorderStyle.Thin; whiteStyle.Alignment = NPOIHorizontalAlignment.Center; whiteStyle.VerticalAlignment = VerticalAlignment.Center; // 黄色背景样式(奇数行) ICellStyle yellowStyle = workbook.CreateCellStyle(); yellowStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.LightYellow.Index; yellowStyle.FillPattern = FillPattern.SolidForeground; yellowStyle.BorderBottom = NPOIBorderStyle.Thin; yellowStyle.BorderTop = NPOIBorderStyle.Thin; yellowStyle.BorderLeft = NPOIBorderStyle.Thin; yellowStyle.BorderRight = NPOIBorderStyle.Thin; yellowStyle.Alignment = NPOIHorizontalAlignment.Center; yellowStyle.VerticalAlignment = VerticalAlignment.Center; // 创建第一行表头(试样1-5) IRow headerRow1 = sheet.CreateRow(0); headerRow1.Height = 400; // 设置行高(单位:1/20点) 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 <= 5; 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 <= 5; 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; } // 填充数据 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) ? whiteStyle : yellowStyle; // 序号列 ICell cellSeq = row.CreateCell(0); cellSeq.SetCellValue(dataRow["序号"].ToString()); cellSeq.CellStyle = rowStyle; colIndex = 1; for (int i = 1; i <= 5; i++) { // 试样次数1 ICell cell1 = row.CreateCell(colIndex++); double val1 = ConvertToDouble(dataRow[$"试样{i}_1"]); if (val1 != 0) { cell1.SetCellValue(val1); } cell1.CellStyle = rowStyle; // 试样次数2 ICell cell2 = row.CreateCell(colIndex++); double val2 = ConvertToDouble(dataRow[$"试样{i}_2"]); if (val2 != 0) { cell2.SetCellValue(val2); } cell2.CellStyle = rowStyle; // 试样次数3 ICell cell3 = row.CreateCell(colIndex++); double val3 = ConvertToDouble(dataRow[$"试样{i}_3"]); if (val3 != 0) { cell3.SetCellValue(val3); } cell3.CellStyle = rowStyle; } rowIndex++; dataRowIndex++; } // 手动设置列宽(单位:1/256字符宽度) sheet.SetColumnWidth(0, 20 * 256); // 序号列:20个字符宽度 for (int i = 1; i <= 15; i++) { sheet.SetColumnWidth(i, 12 * 256); // 数据列:12个字符宽度 } // 保存文件 using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { workbook.Write(fs); } MessageBox.Show($"数据已成功导出到:{filePath}", "导出成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 生成报表(带格式的 Excel) /// public void GenerateReport(string filePath) { // 使用 ExportToExcel 方法 ExportToExcel(filePath); } /// /// 清空数据 /// public void ClearData() { // 重新初始化数据表 sampleDataTable.Clear(); InitializeDataTable(); UpdateDisplay(); } } }