Files
WindowsFormsApp6/WindowsFormsApp6/MainForm.cs
GukSang.Jin ed0fe12366 更新
2026-01-03 18:55:39 +08:00

1378 lines
57 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.Data;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.IO.Ports;
using System.Linq;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;
using NPOIBorderStyle = NPOI.SS.UserModel.BorderStyle;
using NPOIHorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;
using Modbus.Device;
namespace WindowsFormsApp6
{
public partial class MainForm : Form
{
private Form1 form1Instance;
private Form2 form2Instance;
private Form3 form3Instance;
private System.Windows.Forms.Timer timeUpdateTimer;
// 串口和Modbus相关
private SerialPort _serialPort;
private IModbusMaster _modbusMaster;
private System.Windows.Forms.Timer _readTimer;
public MainForm()
{
InitializeComponent();
InitializeTabControl();
InitializeEmbeddedForms();
InitializeTimeUpdate();
InitializeSerialPortAndModbus();
CenterInfoControls();
}
private void InitializeTimeUpdate()
{
// 初始化时间显示
textBox7.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
textBox7.ReadOnly = true; // 设置为只读
// 创建计时器,每秒更新一次
timeUpdateTimer = new System.Windows.Forms.Timer();
timeUpdateTimer.Interval = 1000;
timeUpdateTimer.Tick += (s, e) => textBox7.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
timeUpdateTimer.Start();
}
/// <summary>
/// 初始化串口和Modbus通信
/// </summary>
private void InitializeSerialPortAndModbus()
{
// 初始化读取定时器(信号量触发模式)
_readTimer = new System.Windows.Forms.Timer();
_readTimer.Interval = 100; // 100ms检查一次信号量
_readTimer.Tick += ReadTimer_Tick;
// 配置串口参数
_serialPort = new SerialPort
{
PortName = "COM2",
BaudRate = 19200,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 500,
WriteTimeout = 500,
RtsEnable = false,
DtrEnable = false
};
try
{
// 打开串口
if (!_serialPort.IsOpen)
{
_serialPort.Open();
// 创建Modbus RTU主站
_modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
_modbusMaster.Transport.WaitToRetryMilliseconds = 200;
_modbusMaster.Transport.Retries = 1;
_modbusMaster.Transport.ReadTimeout = 500;
// 启动定时读取
_readTimer.Start();
ShowInfoMsg("数据采集器初始化成功");
}
}
catch (Exception ex)
{
ShowErrorMsg($"串口/Modbus初始化失败{ex.Message}");
}
}
/// <summary>
/// 定时读取Modbus数据信号量触发模式
/// </summary>
private void ReadTimer_Tick(object sender, EventArgs e)
{
if (_modbusMaster == null || _serialPort == null || !_serialPort.IsOpen)
return;
try
{
byte slaveId = 1; // PLC从站地址
// 读取三个信号量M103, M252, M310
// Modbus地址M103 = 103, M252 = 252, M310 = 310
// 使用ReadCoils读取线圈状态M区
bool signalM103 = ReadCoil(slaveId, 103); // Form1信号量
bool signalM252 = ReadCoil(slaveId, 252); // Form2信号量
bool signalM310 = ReadCoil(slaveId, 310); // Form3信号量
// 根据信号量触发相应的数据读取
if (signalM103)
{
ReadForm1Data(slaveId);
// 读取完成后清除信号量
WriteCoil(slaveId, 103, false);
}
if (signalM252)
{
ReadForm2Data(slaveId);
// 读取完成后清除信号量
WriteCoil(slaveId, 252, false);
}
if (signalM310)
{
ReadForm3Data(slaveId);
// 读取完成后清除信号量
WriteCoil(slaveId, 310, false);
}
}
catch (Exception ex)
{
// 读取失败时不弹窗,避免频繁打扰用户
System.Diagnostics.Debug.WriteLine($"Modbus读取失败{ex.Message}");
}
}
/// <summary>
/// 读取单个线圈状态
/// </summary>
private bool ReadCoil(byte slaveId, ushort address)
{
try
{
bool[] coils = _modbusMaster.ReadCoils(slaveId, address, 1);
return coils[0];
}
catch
{
return false;
}
}
/// <summary>
/// 写入单个线圈状态
/// </summary>
private void WriteCoil(byte slaveId, ushort address, bool value)
{
try
{
_modbusMaster.WriteSingleCoil(slaveId, address, value);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"写入线圈失败:{ex.Message}");
}
}
/// <summary>
/// 读取Form1数据液体吸收时间
/// PLC地址D200 - 时间(s)
/// 信号量M103
/// </summary>
private void ReadForm1Data(byte slaveId)
{
try
{
// 读取D200寄存器时间数据
// 假设有5个试样每个试样1个时间值
ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 5);
// 使用反射获取Form1的私有字段
var sampleDataTableField = form1Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (sampleDataTableField != null)
{
DataTable dataTable = sampleDataTableField.GetValue(form1Instance) as DataTable;
if (dataTable != null)
{
// 清除旧的平均值行
var avgRows = dataTable.Select("序号 = '平均时间(s)'");
foreach (var row in avgRows)
{
dataTable.Rows.Remove(row);
}
// 创建新数据行
DataRow dataRow = dataTable.NewRow();
dataRow["序号"] = "时间(s)";
// 填充时间数据(将寄存器值转换为实际时间,假设寄存器值为整数秒*10
for (int i = 0; i < Math.Min(registers.Length, 5); i++)
{
double timeValue = registers[i] / 10.0; // 转换为秒
dataRow[$"试样{i + 1}"] = timeValue;
}
dataTable.Rows.Add(dataRow);
// 计算并添加平均值行
double totalAvg = 0;
int count = 0;
for (int i = 0; i < Math.Min(registers.Length, 5); i++)
{
totalAvg += registers[i] / 10.0;
count++;
}
if (count > 0)
{
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "平均时间(s)";
avgRow["试样1"] = totalAvg / count;
for (int i = 2; i <= 5; i++)
{
avgRow[$"试样{i}"] = DBNull.Value;
}
dataTable.Rows.Add(avgRow);
}
// 刷新显示
dataTable.AcceptChanges();
this.Invoke(new Action(() =>
{
// 触发Form1的界面刷新
var refreshMethod = form1Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form1Instance, null);
}));
}
}
System.Diagnostics.Debug.WriteLine($"Form1数据读取成功{registers.Length}个寄存器");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form1数据失败{ex.Message}");
}
}
/// <summary>
/// 读取Form2数据液体吸收量
/// PLC地址D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间
/// 信号量M252
/// </summary>
private void ReadForm2Data(byte slaveId)
{
try
{
// 读取初始重量D420开始5个试样
ushort[] initialWeights = _modbusMaster.ReadHoldingRegisters(slaveId, 420, 5);
// 读取浸润后重量D422开始5个试样
ushort[] afterWeights = _modbusMaster.ReadHoldingRegisters(slaveId, 422, 5);
// 读取浸润时间D402
ushort[] soakTime = _modbusMaster.ReadHoldingRegisters(slaveId, 402, 1);
// 读取悬挂时间D406
ushort[] hangTime = _modbusMaster.ReadHoldingRegisters(slaveId, 406, 1);
// 使用反射获取Form2的私有字段
var sampleDataTableField = form2Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (sampleDataTableField != null)
{
DataTable dataTable = sampleDataTableField.GetValue(form2Instance) as DataTable;
if (dataTable != null)
{
dataTable.Clear();
int count = Math.Min(initialWeights.Length, 5);
// 转换数据(假设寄存器值为实际值*100
double[] initialWeightValues = new double[count];
double[] afterWeightValues = new double[count];
for (int i = 0; i < count; i++)
{
initialWeightValues[i] = initialWeights[i] / 100.0;
afterWeightValues[i] = afterWeights[i] / 100.0;
}
// 1. 初始重量行
DataRow initialRow = dataTable.NewRow();
initialRow["序号"] = "初始重量(g)";
for (int i = 0; i < count; i++)
{
initialRow[$"试样{i + 1}"] = initialWeightValues[i];
}
dataTable.Rows.Add(initialRow);
// 2. 浸润后重量行
DataRow afterRow = dataTable.NewRow();
afterRow["序号"] = "浸润后重量(g)";
for (int i = 0; i < count; i++)
{
afterRow[$"试样{i + 1}"] = afterWeightValues[i];
}
dataTable.Rows.Add(afterRow);
// 3. 液体吸收量行 (%)
DataRow absorptionRow = dataTable.NewRow();
absorptionRow["序号"] = "液体吸收量(%)";
double[] absorptions = new double[count];
for (int i = 0; i < count; i++)
{
absorptions[i] = ((afterWeightValues[i] - initialWeightValues[i]) / initialWeightValues[i]) * 100;
absorptionRow[$"试样{i + 1}"] = absorptions[i];
}
dataTable.Rows.Add(absorptionRow);
// 4. 浸润时间行
DataRow soakTimeRow = dataTable.NewRow();
soakTimeRow["序号"] = "浸润时间";
for (int i = 1; i <= count; i++)
{
soakTimeRow[$"试样{i}"] = $"{soakTime[0]}s";
}
dataTable.Rows.Add(soakTimeRow);
// 5. 悬挂时间行
DataRow hangTimeRow = dataTable.NewRow();
hangTimeRow["序号"] = "悬挂时间";
for (int i = 1; i <= count; i++)
{
hangTimeRow[$"试样{i}"] = $"{hangTime[0]}s";
}
dataTable.Rows.Add(hangTimeRow);
// 6. 运行速度行(固定值或从其他寄存器读取)
DataRow runSpeedRow = dataTable.NewRow();
runSpeedRow["序号"] = "运行速度";
for (int i = 1; i <= count; i++)
{
runSpeedRow[$"试样{i}"] = "100mm/min";
}
dataTable.Rows.Add(runSpeedRow);
// 7. 液体吸收量平均值行
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "液体吸收量平均值(%)";
double avgAbsorption = absorptions.Average();
avgRow["试样1"] = avgAbsorption;
for (int i = 2; i <= count; i++)
{
avgRow[$"试样{i}"] = DBNull.Value;
}
dataTable.Rows.Add(avgRow);
// 8. 液体吸收量最大值行
DataRow maxRow = dataTable.NewRow();
maxRow["序号"] = "液体吸收量最大值(%)";
double maxAbsorption = absorptions.Max();
maxRow["试样1"] = maxAbsorption;
for (int i = 2; i <= count; i++)
{
maxRow[$"试样{i}"] = DBNull.Value;
}
dataTable.Rows.Add(maxRow);
// 9. 标准偏差行
DataRow stdDevRow = dataTable.NewRow();
stdDevRow["序号"] = "标准偏差";
double stdDev = CalculateStandardDeviation(absorptions);
stdDevRow["试样1"] = stdDev;
for (int i = 2; i <= count; i++)
{
stdDevRow[$"试样{i}"] = DBNull.Value;
}
dataTable.Rows.Add(stdDevRow);
// 刷新显示
dataTable.AcceptChanges();
this.Invoke(new Action(() =>
{
var refreshMethod = form2Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form2Instance, null);
}));
}
}
System.Diagnostics.Debug.WriteLine("Form2数据读取成功");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form2数据失败{ex.Message}");
}
}
/// <summary>
/// 读取Form3数据液体芯吸速率
/// PLC地址D456 - 吸水时间(s)
/// 信号量M310
/// </summary>
private void ReadForm3Data(byte slaveId)
{
try
{
// 读取吸水时间D456开始5个试样 * 3次测试 = 15个寄存器
ushort[] wickingTimes = _modbusMaster.ReadHoldingRegisters(slaveId, 456, 15);
// 使用反射获取Form3的私有字段
var sampleDataTableField = form3Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (sampleDataTableField != null)
{
DataTable dataTable = sampleDataTableField.GetValue(form3Instance) as DataTable;
if (dataTable != null && dataTable.Rows.Count > 0)
{
// 获取第一行(吸水时间行)
DataRow timeRow = dataTable.Rows[0];
// 填充吸水时间数据(假设寄存器值为实际时间*10
int registerIndex = 0;
for (int i = 1; i <= 5; i++)
{
if (registerIndex < wickingTimes.Length)
{
timeRow[$"试样{i}_1"] = wickingTimes[registerIndex++] / 10.0;
}
if (registerIndex < wickingTimes.Length)
{
timeRow[$"试样{i}_2"] = wickingTimes[registerIndex++] / 10.0;
}
if (registerIndex < wickingTimes.Length)
{
timeRow[$"试样{i}_3"] = wickingTimes[registerIndex++] / 10.0;
}
}
// 调用Form3的计算方法
this.Invoke(new Action(() =>
{
var calculateMethod = form3Instance.GetType()
.GetMethod("CalculateAllRows", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
calculateMethod?.Invoke(form3Instance, null);
var refreshMethod = form3Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form3Instance, null);
}));
}
}
System.Diagnostics.Debug.WriteLine("Form3数据读取成功");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form3数据失败{ex.Message}");
}
}
/// <summary>
/// 计算标准偏差
/// </summary>
private double CalculateStandardDeviation(double[] values)
{
if (values == null || values.Length <= 1) return 0;
double avg = values.Average();
double sumOfSquares = values.Sum(val => Math.Pow(val - avg, 2));
return Math.Sqrt(sumOfSquares / (values.Length - 1));
}
/// <summary>
/// 显示错误消息
/// </summary>
private void ShowErrorMsg(string message)
{
MessageBox.Show(message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 显示信息消息
/// </summary>
private void ShowInfoMsg(string message)
{
MessageBox.Show(message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void InitializeTabControl()
{
tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged;
}
private void TabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateTitleForCurrentTab();
// 根据选中的 Tab 显示/隐藏切换布局按钮
// 只有在 Form3 (Tab 2, index=2) 时显示切换按钮
buttonToggleLayout.Visible = (tabControl1.SelectedIndex == 2);
// 更新切换按钮的文本
if (tabControl1.SelectedIndex == 2 && form3Instance != null)
{
UpdateToggleButtonText();
}
}
private void UpdateTitleForCurrentTab()
{
switch (tabControl1.SelectedIndex)
{
case 0:
label1.Text = " 液体吸收时间测试报告";
break;
case 1:
label1.Text = " 液体吸收量测试报告";
break;
case 2:
label1.Text = " 液体芯吸速率测试报告";
break;
}
}
private void InitializeEmbeddedForms()
{
form1Instance = new Form1();
form1Instance.TopLevel = false;
form1Instance.FormBorderStyle = FormBorderStyle.None;
form1Instance.Dock = DockStyle.Fill;
Panel panel1 = new Panel();
panel1.Dock = DockStyle.Fill;
panel1.Controls.Add(form1Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage1.Controls.Add(panel1);
form1Instance.Show();
form2Instance = new Form2();
form2Instance.TopLevel = false;
form2Instance.FormBorderStyle = FormBorderStyle.None;
form2Instance.Dock = DockStyle.Fill;
Panel panel2 = new Panel();
panel2.Dock = DockStyle.Fill;
panel2.Controls.Add(form2Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage2.Controls.Add(panel2);
form2Instance.Show();
form3Instance = new Form3();
form3Instance.TopLevel = false;
form3Instance.FormBorderStyle = FormBorderStyle.None;
form3Instance.Dock = DockStyle.Fill;
Panel panel3 = new Panel();
panel3.Dock = DockStyle.Fill;
panel3.Controls.Add(form3Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage3.Controls.Add(panel3);
form3Instance.Show();
}
private void panelInfo_Resize(object sender, EventArgs e)
{
CenterInfoControls();
}
private void CenterInfoControls()
{
// 每列的宽度定义
int col1LabelWidth = 75; // 样品名称/仪器
int col1InputWidth = 140;
int col2LabelWidth = 75; // 物料编码/设备编号
int col2InputWidth = 140;
int col3LabelWidth = 75; // 批号/操作人员(操作人员需要更宽)
int col3InputWidth = 140;
int col4LabelWidth = 75; // 测试时间/数据文件
int col4InputWidth = 140;
int gap = 15; // 列之间的间距
// 计算总宽度
int totalWidth = col1LabelWidth + col1InputWidth + gap +
col2LabelWidth + col2InputWidth + gap +
col3LabelWidth + col3InputWidth + gap +
col4LabelWidth + col4InputWidth;
int startX = (panelInfo.Width - totalWidth) / 2;
// 第一行 Y 坐标
int y1 = 10;
int labelOffsetY = 3; // 标签相对输入框的垂直偏移
// 第一列:样品名称
int col1X = startX;
label3.Location = new Point(col1X, y1 + labelOffsetY);
textBox1.Location = new Point(col1X + col1LabelWidth, y1);
// 第二列:物料编码
int col2X = col1X + col1LabelWidth + col1InputWidth + gap;
label4.Location = new Point(col2X, y1 + labelOffsetY);
textBox2.Location = new Point(col2X + col2LabelWidth, y1);
// 第三列:批号
int col3X = col2X + col2LabelWidth + col2InputWidth + gap;
label5.Location = new Point(col3X, y1 + labelOffsetY);
textBox3.Location = new Point(col3X + col3LabelWidth, y1);
// 第四列:测试时间
int col4X = col3X + col3LabelWidth + col3InputWidth + gap;
label9.Location = new Point(col4X, y1 + labelOffsetY);
textBox7.Location = new Point(col4X + col4LabelWidth, y1);
// 第二行 Y 坐标
int y2 = 43;
// 第一列:仪器
label6.Location = new Point(col1X, y2 + labelOffsetY);
textBox4.Location = new Point(col1X + col1LabelWidth, y2);
// 第二列:设备编号
label7.Location = new Point(col2X, y2 + labelOffsetY);
textBox5.Location = new Point(col2X + col2LabelWidth, y2);
// 第三列:操作人员
label8.Location = new Point(col3X, y2 + labelOffsetY);
textBox6.Location = new Point(col3X + col3LabelWidth, y2);
// 第四列:数据文件
label10.Location = new Point(col4X, y2 + labelOffsetY);
textBox8.Location = new Point(col4X + col4LabelWidth, y2);
}
private void buttonPrint_Click(object sender, EventArgs e)
{
MessageBox.Show("打印功能开发中", "提示");
}
private void buttonExport_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Excel 文件 (*.xlsx)|*.xlsx",
FileName = $"测试报告_{DateTime.Now:yyyyMMdd_HHmmss}",
Title = "导出整合报告"
};
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
ExportIntegratedReport(saveFileDialog.FileName);
}
}
private void button5_Click(object sender, EventArgs e)
{
switch (tabControl1.SelectedIndex)
{
case 0:
form1Instance.GenerateMockData();
break;
case 1:
form2Instance.GenerateMockData();
break;
case 2:
form3Instance.GenerateMockData();
break;
}
}
private void buttonToggleLayout_Click(object sender, EventArgs e)
{
if (form3Instance != null)
{
// 调用 Form3 的公共切换方法
form3Instance.ToggleTableLayout();
// 更新按钮文本
UpdateToggleButtonText();
}
}
/// <summary>
/// 更新切换按钮的文本
/// </summary>
private void UpdateToggleButtonText()
{
if (form3Instance != null)
{
// 使用公共方法获取布局状态
bool isVertical = form3Instance.IsVerticalLayout();
buttonToggleLayout.Text = isVertical ? "🔄 横向布局" : "🔄 纵向布局";
}
}
private void ExportIntegratedReport(string filePath)
{
try
{
IWorkbook workbook = new XSSFWorkbook();
// 创建单个整合的工作表
CreateIntegratedSheet(workbook);
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(fs);
}
MessageBox.Show($"导出成功:{filePath}", "成功");
}
catch (Exception ex)
{
MessageBox.Show($"导出失败:{ex.Message}", "错误");
}
}
private void CreateIntegratedSheet(IWorkbook workbook)
{
ISheet sheet = workbook.CreateSheet("吸收性测试报告");
var styles = CreateReportStyles(workbook);
// 获取三个表单的数据
var sampleCountField1 = form1Instance.GetType()
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
int sampleCount = sampleCountField1 != null ? (int)sampleCountField1.GetValue(form1Instance) : 5;
var dataTableField1 = form1Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable1 = dataTableField1?.GetValue(form1Instance) as DataTable;
var dataTableField2 = form2Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable2 = dataTableField2?.GetValue(form2Instance) as DataTable;
var dataTableField3 = form3Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable3 = dataTableField3?.GetValue(form3Instance) as DataTable;
int currentRow = 0;
// 1. 创建总标题
IRow titleRow = sheet.CreateRow(currentRow++);
titleRow.Height = 800;
ICell titleCell = titleRow.CreateCell(0);
titleCell.SetCellValue("吸收性测试报告");
titleCell.CellStyle = styles.titleStyle;
// 为合并区域的所有单元格设置样式(总标题不需要边框,但为了一致性也设置)
for (int i = 1; i <= sampleCount + 5; i++)
{
titleRow.CreateCell(i).CellStyle = styles.titleStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, sampleCount + 5));
currentRow++; // 空行
// 2. 创建信息输入区域
CreateInfoSection(sheet, ref currentRow, sampleCount, styles);
currentRow++; // 空行
// 3. 创建液体吸收时间部分
CreateForm1Section(sheet, ref currentRow, sampleCount, dataTable1, styles);
currentRow++; // 空行
// 4. 创建液体吸收量部分
CreateForm2Section(sheet, ref currentRow, sampleCount, dataTable2, styles);
currentRow++; // 空行
// 5. 创建液体芯吸速率部分
CreateForm3Section(sheet, ref currentRow, sampleCount, dataTable3, styles);
// 设置列宽
sheet.SetColumnWidth(0, 20 * 256);
for (int i = 1; i <= sampleCount * 3; i++)
{
sheet.SetColumnWidth(i, 12 * 256);
}
}
private void CreateInfoSection(ISheet sheet, ref int currentRow, int sampleCount,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 从界面获取实际数据
string sampleName = textBox1.Text.Trim();
string materialCode = textBox2.Text.Trim();
string batchNumber = textBox3.Text.Trim();
string operatorName = textBox4.Text.Trim();
string instrument = textBox5.Text.Trim();
string deviceNumber = textBox6.Text.Trim();
string testTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 第一行信息
IRow infoRow1 = sheet.CreateRow(currentRow++);
infoRow1.Height = 400;
// 样品名称
ICell cell1 = infoRow1.CreateCell(0);
cell1.SetCellValue("样品名称:");
cell1.CellStyle = styles.dataStyle;
ICell cell2 = infoRow1.CreateCell(1);
cell2.SetCellValue(string.IsNullOrEmpty(sampleName) ? "" : sampleName);
cell2.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow1.CreateCell(2).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, 2));
// 物料编码
ICell cell3 = infoRow1.CreateCell(3);
cell3.SetCellValue("物料编码:");
cell3.CellStyle = styles.dataStyle;
ICell cell4 = infoRow1.CreateCell(4);
cell4.SetCellValue(string.IsNullOrEmpty(materialCode) ? "" : materialCode);
cell4.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow1.CreateCell(5).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 4, 5));
// 批号
ICell cell5 = infoRow1.CreateCell(6);
cell5.SetCellValue("批号:");
cell5.CellStyle = styles.dataStyle;
ICell cell6 = infoRow1.CreateCell(7);
cell6.SetCellValue(string.IsNullOrEmpty(batchNumber) ? "" : batchNumber);
cell6.CellStyle = styles.yellowStyle;
// 操作人员
ICell cell7 = infoRow1.CreateCell(8);
cell7.SetCellValue("操作人员:");
cell7.CellStyle = styles.dataStyle;
ICell cell8 = infoRow1.CreateCell(9);
cell8.SetCellValue(string.IsNullOrEmpty(operatorName) ? "" : operatorName);
cell8.CellStyle = styles.yellowStyle;
// 第二行信息
IRow infoRow2 = sheet.CreateRow(currentRow++);
infoRow2.Height = 400;
// 测试时间
ICell cell21 = infoRow2.CreateCell(0);
cell21.SetCellValue("测试时间:");
cell21.CellStyle = styles.dataStyle;
ICell cell22 = infoRow2.CreateCell(1);
cell22.SetCellValue(testTime);
cell22.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow2.CreateCell(2).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, 2));
// 仪器
ICell cell23 = infoRow2.CreateCell(3);
cell23.SetCellValue("仪器:");
cell23.CellStyle = styles.dataStyle;
ICell cell24 = infoRow2.CreateCell(4);
cell24.SetCellValue(string.IsNullOrEmpty(instrument) ? "" : instrument);
cell24.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow2.CreateCell(5).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 4, 5));
// 设备编号
ICell cell25 = infoRow2.CreateCell(6);
cell25.SetCellValue("设备编号:");
cell25.CellStyle = styles.dataStyle;
ICell cell26 = infoRow2.CreateCell(7);
cell26.SetCellValue(string.IsNullOrEmpty(deviceNumber) ? "" : deviceNumber);
cell26.CellStyle = styles.yellowStyle;
// 数据文件
ICell cell27 = infoRow2.CreateCell(8);
cell27.SetCellValue("数据文件:");
cell27.CellStyle = styles.dataStyle;
ICell cell28 = infoRow2.CreateCell(9);
cell28.SetCellValue("(报告保存路径)");
cell28.CellStyle = styles.yellowStyle;
}
private void CreateForm1Section(ISheet sheet, ref int currentRow, int sampleCount, DataTable dataTable,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 子标题
IRow subtitleRow = sheet.CreateRow(currentRow++);
subtitleRow.Height = 500;
ICell subtitleCell = subtitleRow.CreateCell(0);
subtitleCell.SetCellValue("液体吸收时间");
subtitleCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 1; i <= sampleCount; i++)
{
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount));
// 表头
IRow headerRow = sheet.CreateRow(currentRow++);
headerRow.Height = 400;
ICell seqCell = headerRow.CreateCell(0);
seqCell.SetCellValue("序号");
seqCell.CellStyle = styles.headerStyle;
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
}
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
{
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
// 如果是平均时间行,只显示一个合并的单元格
if (rowName.Contains("平均"))
{
ICell avgCell = row.CreateCell(1);
if (dataTable.Columns.Contains("试样1") && dataRow["试样1"] != DBNull.Value)
{
if (double.TryParse(dataRow["试样1"].ToString(), out double value))
{
avgCell.SetCellValue(value);
}
else
{
avgCell.SetCellValue("");
}
}
else
{
avgCell.SetCellValue("");
}
avgCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount; i++)
{
row.CreateCell(i).CellStyle = styles.yellowStyle;
}
// 合并平均时间单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount));
}
else
{
// 普通数据行,显示每个试样的数据
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = row.CreateCell(i);
if (dataTable.Columns.Contains($"试样{i}") && dataRow[$"试样{i}"] != DBNull.Value)
{
if (double.TryParse(dataRow[$"试样{i}"].ToString(), out double value))
{
cell.SetCellValue(value);
}
else
{
cell.SetCellValue("");
}
}
else
{
cell.SetCellValue("");
}
cell.CellStyle = styles.yellowStyle;
}
}
}
}
}
private void CreateForm2Section(ISheet sheet, ref int currentRow, int sampleCount, DataTable dataTable,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 子标题
IRow subtitleRow = sheet.CreateRow(currentRow++);
subtitleRow.Height = 500;
ICell subtitleCell = subtitleRow.CreateCell(0);
subtitleCell.SetCellValue("液体吸收量");
subtitleCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 1; i <= sampleCount; i++)
{
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount));
// 表头
IRow headerRow = sheet.CreateRow(currentRow++);
headerRow.Height = 400;
ICell seqCell = headerRow.CreateCell(0);
seqCell.SetCellValue("序号");
seqCell.CellStyle = styles.headerStyle;
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
}
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
{
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
// 判断是否是需要合并的行(平均值、最大值、标准偏差)
bool isMergedRow = rowName.Contains("平均值") || rowName.Contains("最大值") || rowName.Contains("标准偏差");
if (isMergedRow)
{
// 合并为一列,只显示第一个试样的值
ICell valueCell = row.CreateCell(1);
if (dataTable.Columns.Contains("试样1") && dataRow["试样1"] != DBNull.Value)
{
object value = dataRow["试样1"];
if (value != null && double.TryParse(value.ToString(), out double numValue))
{
valueCell.SetCellValue(numValue);
}
else
{
valueCell.SetCellValue("");
}
}
else
{
valueCell.SetCellValue("");
}
valueCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount; i++)
{
row.CreateCell(i).CellStyle = styles.yellowStyle;
}
// 合并单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount));
}
else
{
// 普通数据行,显示每个试样的数据
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = row.CreateCell(i);
if (dataTable.Columns.Contains($"试样{i}") && dataRow[$"试样{i}"] != DBNull.Value)
{
object value = dataRow[$"试样{i}"];
if (value != null)
{
// 尝试转换为 double
if (double.TryParse(value.ToString(), out double numValue))
{
cell.SetCellValue(numValue);
}
else
{
// 如果不是数字,直接显示字符串值
cell.SetCellValue(value.ToString());
}
}
else
{
cell.SetCellValue("");
}
}
else
{
cell.SetCellValue("");
}
cell.CellStyle = styles.yellowStyle;
}
}
}
}
}
private void CreateForm3Section(ISheet sheet, ref int currentRow, int sampleCount, DataTable dataTable,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 子标题
IRow subtitleRow = sheet.CreateRow(currentRow++);
subtitleRow.Height = 500;
ICell subtitleCell = subtitleRow.CreateCell(0);
subtitleCell.SetCellValue("液体芯吸速率");
subtitleCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 1; i <= sampleCount * 3; i++)
{
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount * 3));
// 第一行表头(纵向/横向)
IRow headerRow1 = sheet.CreateRow(currentRow);
headerRow1.Height = 400;
ICell seqCell1 = headerRow1.CreateCell(0);
seqCell1.SetCellValue("序号");
seqCell1.CellStyle = styles.headerStyle;
ICell dirCell = headerRow1.CreateCell(1);
dirCell.SetCellValue("纵向/横向(选择按钮)");
dirCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 2; i <= sampleCount * 3; i++)
{
headerRow1.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow, currentRow, 1, sampleCount * 3));
currentRow++;
// 第二行表头试样1-N每个3列
IRow headerRow2 = sheet.CreateRow(currentRow);
headerRow2.Height = 400;
// 序号列在第二行也需要创建并设置样式(用于合并)
headerRow2.CreateCell(0).CellStyle = styles.headerStyle;
int colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = headerRow2.CreateCell(colIndex);
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
headerRow2.CreateCell(colIndex + 1).CellStyle = styles.headerStyle;
headerRow2.CreateCell(colIndex + 2).CellStyle = styles.headerStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow, currentRow, colIndex, colIndex + 2));
colIndex += 3;
}
// 合并序号列(跨两行)
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow, 0, 0));
currentRow++;
// 第三行表头1、2、3
IRow headerRow3 = sheet.CreateRow(currentRow++);
headerRow3.Height = 400;
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
ICell cell = headerRow3.CreateCell(colIndex++);
cell.SetCellValue(j.ToString());
cell.CellStyle = styles.headerStyle;
}
}
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
{
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
// 判断是否是标准偏差行(需要合并为一列)
bool isStdDeviationRow = rowName.Contains("标准偏差");
if (isStdDeviationRow)
{
// 标准偏差行:合并为一列,只显示第一个值
ICell valueCell = row.CreateCell(1);
string columnName = "试样1_1";
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
{
if (double.TryParse(dataRow[columnName].ToString(), out double value))
{
if (Math.Abs(value) >= 0.001)
{
valueCell.SetCellValue(value);
}
else
{
valueCell.SetCellValue("");
}
}
else
{
valueCell.SetCellValue("");
}
}
else
{
valueCell.SetCellValue("");
}
valueCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount * 3; i++)
{
row.CreateCell(i).CellStyle = styles.yellowStyle;
}
// 合并单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount * 3));
}
else
{
// 普通数据行:显示每个试样的每次测试数据
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
ICell cell = row.CreateCell(colIndex++);
string columnName = $"试样{i}_{j}";
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
{
if (double.TryParse(dataRow[columnName].ToString(), out double value))
{
if (Math.Abs(value) >= 0.001)
{
cell.SetCellValue(value);
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
cell.CellStyle = styles.yellowStyle;
}
}
}
}
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// 停止并释放定时器
timeUpdateTimer?.Stop();
timeUpdateTimer?.Dispose();
_readTimer?.Stop();
_readTimer?.Dispose();
// 关闭Modbus和串口
try
{
_modbusMaster?.Dispose();
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
}
_serialPort?.Dispose();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"关闭串口失败:{ex.Message}");
}
Application.Exit();
}
private (ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle)
CreateReportStyles(IWorkbook workbook)
{
// 标题样式
ICellStyle titleStyle = workbook.CreateCellStyle();
IFont titleFont = workbook.CreateFont();
titleFont.FontHeightInPoints = 16;
titleFont.IsBold = true;
titleStyle.SetFont(titleFont);
titleStyle.Alignment = NPOIHorizontalAlignment.Center;
titleStyle.VerticalAlignment = VerticalAlignment.Center;
// 表头样式(灰色背景)
ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
SetBorders(headerStyle);
IFont headerFont = workbook.CreateFont();
headerFont.IsBold = true;
headerStyle.SetFont(headerFont);
headerStyle.Alignment = NPOIHorizontalAlignment.Center;
headerStyle.VerticalAlignment = VerticalAlignment.Center;
// 数据样式(白色背景)
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 (titleStyle, headerStyle, dataStyle, yellowStyle);
}
private void SetBorders(ICellStyle style)
{
style.BorderBottom = NPOIBorderStyle.Thin;
style.BorderTop = NPOIBorderStyle.Thin;
style.BorderLeft = NPOIBorderStyle.Thin;
style.BorderRight = NPOIBorderStyle.Thin;
}
}
}