Files
NonWovenFabric/WindowsFormsApp6/MainForm.cs

2168 lines
89 KiB
C#
Raw Normal View History

2025-12-31 09:43:35 +08:00
using System;
2026-01-05 09:09:25 +08:00
using System.Collections.Generic;
2025-12-31 09:43:35 +08:00
using System.Data;
using System.Drawing;
using System.Windows.Forms;
2025-12-31 17:26:27 +08:00
using System.IO;
2026-01-03 18:55:39 +08:00
using System.IO.Ports;
using System.Linq;
2025-12-31 17:26:27 +08:00
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;
using NPOIBorderStyle = NPOI.SS.UserModel.BorderStyle;
using NPOIHorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;
2026-01-03 18:55:39 +08:00
using Modbus.Device;
2025-12-31 09:43:35 +08:00
namespace WindowsFormsApp6
{
public partial class MainForm : Form
2025-12-31 17:26:27 +08:00
{
private Form1 form1Instance;
private Form2 form2Instance;
private Form3 form3Instance;
2026-01-03 15:59:38 +08:00
private System.Windows.Forms.Timer timeUpdateTimer;
2026-01-03 18:55:39 +08:00
// 串口和Modbus相关
private SerialPort _serialPort;
private IModbusMaster _modbusMaster;
private System.Windows.Forms.Timer _readTimer;
2025-12-31 09:43:35 +08:00
2026-01-05 09:09:25 +08:00
// Form1历史数据缓存用于累积显示
private List<double> form1TimeValues = new List<double>();
// Form2历史数据缓存用于累积显示
private List<Form2SampleData> form2SampleDataList = new List<Form2SampleData>();
// Form2单个试样数据结构
private class Form2SampleData
{
public double InitialWeight { get; set; }
public double AfterWeight { get; set; }
public int SoakTime { get; set; }
public int HangTime { get; set; }
public int RunSpeed { get; set; }
}
2025-12-31 09:43:35 +08:00
public MainForm()
{
InitializeComponent();
2025-12-31 17:26:27 +08:00
InitializeTabControl();
InitializeEmbeddedForms();
2026-01-03 15:59:38 +08:00
InitializeTimeUpdate();
2026-01-03 18:55:39 +08:00
InitializeSerialPortAndModbus();
2026-01-03 15:59:38 +08:00
CenterInfoControls();
2025-12-31 17:26:27 +08:00
}
2026-01-03 15:59:38 +08:00
private void InitializeTimeUpdate()
2025-12-31 17:26:27 +08:00
{
2026-01-03 15:59:38 +08:00
// 初始化时间显示
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();
2025-12-31 17:26:27 +08:00
}
2025-12-31 09:43:35 +08:00
2026-01-03 18:55:39 +08:00
/// <summary>
/// 初始化串口和Modbus通信
/// </summary>
private void InitializeSerialPortAndModbus()
{
// 初始化读取定时器(信号量触发模式)
_readTimer = new System.Windows.Forms.Timer();
_readTimer.Interval = 100; // 100ms检查一次信号量
_readTimer.Tick += ReadTimer_Tick;
2026-01-04 14:53:08 +08:00
// 尝试打开默认串口
if (!TryOpenSerialPort("COM2"))
2026-01-03 18:55:39 +08:00
{
2026-01-04 14:53:08 +08:00
// 如果默认串口失败,弹出端口选择对话框
ShowPortSelectionDialog();
}
}
2026-01-03 18:55:39 +08:00
2026-01-04 14:53:08 +08:00
/// <summary>
/// 尝试打开指定的串口
/// </summary>
private bool TryOpenSerialPort(string portName)
{
2026-01-03 18:55:39 +08:00
try
{
2026-01-04 14:53:08 +08:00
// 如果已有串口打开,先关闭
if (_serialPort != null && _serialPort.IsOpen)
2026-01-03 18:55:39 +08:00
{
2026-01-04 14:53:08 +08:00
_serialPort.Close();
_serialPort.Dispose();
2026-01-03 18:55:39 +08:00
}
2026-01-04 14:53:08 +08:00
// 配置串口参数
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = 19200,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 500,
WriteTimeout = 500,
RtsEnable = false,
DtrEnable = false
};
// 打开串口
_serialPort.Open();
// 创建Modbus RTU主站
_modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
_modbusMaster.Transport.WaitToRetryMilliseconds = 200;
_modbusMaster.Transport.Retries = 1;
_modbusMaster.Transport.ReadTimeout = 500;
// 启动定时读取
_readTimer.Start();
ShowInfoMsg($"串口 {portName} 初始化成功");
return true;
2026-01-03 18:55:39 +08:00
}
catch (Exception ex)
{
2026-01-04 14:53:08 +08:00
System.Diagnostics.Debug.WriteLine($"打开串口 {portName} 失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 显示端口选择对话框
/// </summary>
private void ShowPortSelectionDialog()
{
// 获取当前计算机所有可用串口
string[] ports = SerialPort.GetPortNames();
if (ports == null || ports.Length == 0)
{
ShowErrorMsg("未检测到可用的串口,请检查串口连接。");
return;
}
// 创建端口选择对话框
Form portDialog = new Form
{
Text = "选择串口",
Width = 350,
Height = 200,
StartPosition = FormStartPosition.CenterParent,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false
};
Label label = new Label
{
Text = "串口初始化失败,请选择可用的串口:",
Location = new Point(20, 20),
AutoSize = true
};
ComboBox comboBox = new ComboBox
{
Location = new Point(20, 50),
Width = 290,
DropDownStyle = ComboBoxStyle.DropDownList
};
comboBox.Items.AddRange(ports);
if (comboBox.Items.Count > 0)
{
comboBox.SelectedIndex = 0;
}
Button btnOK = new Button
{
Text = "确定",
DialogResult = DialogResult.OK,
Location = new Point(140, 100),
Width = 80
};
Button btnCancel = new Button
{
Text = "取消",
DialogResult = DialogResult.Cancel,
Location = new Point(230, 100),
Width = 80
};
portDialog.Controls.AddRange(new Control[] { label, comboBox, btnOK, btnCancel });
portDialog.AcceptButton = btnOK;
portDialog.CancelButton = btnCancel;
// 显示对话框
if (portDialog.ShowDialog(this) == DialogResult.OK)
{
string selectedPort = comboBox.SelectedItem?.ToString();
if (!string.IsNullOrEmpty(selectedPort))
{
if (TryOpenSerialPort(selectedPort))
{
// 成功打开
return;
}
else
{
// 选择的端口也失败,询问是否重新选择
var result = MessageBox.Show(
$"打开串口 {selectedPort} 失败,是否重新选择?",
"错误",
MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
if (result == DialogResult.Yes)
{
ShowPortSelectionDialog(); // 递归调用,重新选择
}
}
}
}
else
{
ShowErrorMsg("未选择串口,数据采集功能将不可用。");
2026-01-03 18:55:39 +08:00
}
}
/// <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数据液体吸收时间
2026-01-05 09:09:25 +08:00
/// PLC地址D200 - 时间(s)每次测试读取1个试样2个字节
2026-01-03 18:55:39 +08:00
/// 信号量M103
2026-01-05 09:09:25 +08:00
/// 每次测试动态添加一列显示每5个试样计算一次平均值
2026-01-03 18:55:39 +08:00
/// </summary>
private void ReadForm1Data(byte slaveId)
{
try
{
2026-01-05 09:09:25 +08:00
// 读取1个寄存器2个字节- D200
2026-01-04 18:16:52 +08:00
ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 1);
2026-01-03 18:55:39 +08:00
// 使用反射获取Form1的私有字段
var sampleDataTableField = form1Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2026-01-04 18:16:52 +08:00
var currentSampleCountField = form1Instance.GetType()
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2026-01-05 09:09:25 +08:00
if (sampleDataTableField == null || currentSampleCountField == null)
{
System.Diagnostics.Debug.WriteLine("无法获取Form1的私有字段");
return;
}
DataTable dataTable = sampleDataTableField.GetValue(form1Instance) as DataTable;
int currentSampleCount = (int)currentSampleCountField.GetValue(form1Instance);
if (dataTable == null)
{
System.Diagnostics.Debug.WriteLine("DataTable为空");
return;
}
// 转换寄存器值为实际时间(秒)
double timeValue = registers[0] / 10.0;
// 检查NaN
if (double.IsNaN(timeValue) || double.IsInfinity(timeValue))
{
System.Diagnostics.Debug.WriteLine("读取的时间值无效,跳过本次读取");
return;
}
// 添加到历史数据
form1TimeValues.Add(timeValue);
// 新试样的索引
int newSampleIndex = form1TimeValues.Count;
// 如果需要扩展列数
if (newSampleIndex > currentSampleCount)
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
// 添加新列到DataTable
if (!dataTable.Columns.Contains($"试样{newSampleIndex}"))
{
dataTable.Columns.Add($"试样{newSampleIndex}", typeof(double));
}
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 更新currentSampleCount
currentSampleCount = newSampleIndex;
currentSampleCountField.SetValue(form1Instance, currentSampleCount);
// 重新初始化DataGridView以显示新列
this.Invoke(new Action(() =>
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
var initMethod = form1Instance.GetType()
.GetMethod("InitializeDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
initMethod?.Invoke(form1Instance, null);
}));
// 重新获取DataTable可能已重新绑定
dataTable = sampleDataTableField.GetValue(form1Instance) as DataTable;
}
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 清空所有数据,重新构建
dataTable.Clear();
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
// 创建时间行
DataRow timeRow = dataTable.NewRow();
timeRow["序号"] = "时间(s)";
// 填充所有历史数据
for (int i = 0; i < form1TimeValues.Count; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
timeRow[$"试样{i + 1}"] = form1TimeValues[i];
}
}
// 其余列设为空
for (int i = form1TimeValues.Count + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
timeRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(timeRow);
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
// 计算并添加平均值行每5个试样一组
int totalSamples = form1TimeValues.Count;
int groupCount = (int)Math.Ceiling((double)totalSamples / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
int endSample = Math.Min(startSample + 4, totalSamples);
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
// 计算该组的平均值
double groupSum = 0;
int validCount = 0;
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
for (int i = startSample; i <= endSample; i++)
{
groupSum += form1TimeValues[i - 1];
validCount++;
}
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
if (validCount > 0)
{
double groupAvg = groupSum / validCount;
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
// 检查NaN
if (double.IsNaN(groupAvg) || double.IsInfinity(groupAvg))
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
groupAvg = 0;
2026-01-03 18:55:39 +08:00
}
2026-01-04 18:16:52 +08:00
2026-01-05 09:09:25 +08:00
// 创建平均值行
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "平均时间(s)";
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 将平均值显示在该组的第一个试样列
avgRow[$"试样{startSample}"] = groupAvg;
// 其他列设置为空
for (int i = 1; i <= currentSampleCount; i++)
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
if (i != startSample && dataTable.Columns.Contains($"试样{i}"))
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
avgRow[$"试样{i}"] = DBNull.Value;
2026-01-03 18:55:39 +08:00
}
}
2026-01-04 18:38:18 +08:00
2026-01-05 09:09:25 +08:00
dataTable.Rows.Add(avgRow);
2026-01-03 18:55:39 +08:00
}
}
2026-01-05 09:09:25 +08:00
// 刷新显示
dataTable.AcceptChanges();
this.Invoke(new Action(() =>
{
var refreshMethod = form1Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form1Instance, null);
}));
System.Diagnostics.Debug.WriteLine($"Form1数据读取成功试样{newSampleIndex},时间={timeValue:F2}秒,累计{totalSamples}个试样");
2026-01-03 18:55:39 +08:00
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form1数据失败{ex.Message}");
}
}
/// <summary>
/// 读取Form2数据液体吸收量
2026-01-04 10:47:39 +08:00
/// PLC地址D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间, D310 - 运行速度
2026-01-03 18:55:39 +08:00
/// 信号量M252
2026-01-05 09:09:25 +08:00
/// 每次测试动态添加一列显示每5个试样计算平均值、最大值、标准偏差
2026-01-04 18:38:18 +08:00
/// </summary>
2026-01-03 18:55:39 +08:00
private void ReadForm2Data(byte slaveId)
{
try
{
2026-01-04 18:38:18 +08:00
// 读取当前试样的数据每次读取2个字节
// 读取初始重量D4202字节
ushort[] initialWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 420, 2);
double initialWeight = ConvertRegistersToDouble(initialWeightReg);
2026-01-04 10:47:39 +08:00
2026-01-04 18:38:18 +08:00
// 读取浸润后重量D4222字节
ushort[] afterWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 422, 2);
double afterWeight = ConvertRegistersToDouble(afterWeightReg);
// 读取浸润时间D4022字节
ushort[] soakTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 402, 2);
int soakTime = ConvertRegistersToInt(soakTimeReg);
// 读取悬挂时间D4062字节
ushort[] hangTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 406, 2);
int hangTime = ConvertRegistersToInt(hangTimeReg);
// 读取运行速度D3102字节
ushort[] runSpeedReg = _modbusMaster.ReadHoldingRegisters(slaveId, 310, 2);
int runSpeed = ConvertRegistersToInt(runSpeedReg);
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 使用反射获取Form2的私有字段
var sampleDataTableField = form2Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var currentSampleCountField = form2Instance.GetType()
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
if (sampleDataTableField == null || currentSampleCountField == null)
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
System.Diagnostics.Debug.WriteLine("无法获取Form2的私有字段");
return;
}
DataTable dataTable = sampleDataTableField.GetValue(form2Instance) as DataTable;
int currentSampleCount = (int)currentSampleCountField.GetValue(form2Instance);
if (dataTable == null)
{
System.Diagnostics.Debug.WriteLine("DataTable为空");
return;
}
// 添加到历史数据
form2SampleDataList.Add(new Form2SampleData
{
InitialWeight = initialWeight,
AfterWeight = afterWeight,
SoakTime = soakTime,
HangTime = hangTime,
RunSpeed = runSpeed
});
// 新试样的索引
int newSampleIndex = form2SampleDataList.Count;
// 如果需要扩展列数
if (newSampleIndex > currentSampleCount)
{
// 添加新列到DataTable
if (!dataTable.Columns.Contains($"试样{newSampleIndex}"))
{
dataTable.Columns.Add($"试样{newSampleIndex}", typeof(object));
}
// 更新currentSampleCount
currentSampleCount = newSampleIndex;
2026-01-04 18:38:18 +08:00
currentSampleCountField.SetValue(form2Instance, currentSampleCount);
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 重新初始化DataGridView以显示新列
2026-01-04 18:38:18 +08:00
this.Invoke(new Action(() =>
2026-01-03 18:55:39 +08:00
{
2026-01-05 09:09:25 +08:00
var initMethod = form2Instance.GetType()
.GetMethod("InitializeDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
initMethod?.Invoke(form2Instance, null);
2026-01-04 18:38:18 +08:00
}));
2026-01-05 09:09:25 +08:00
// 重新获取DataTable可能已重新绑定
2026-01-04 18:38:18 +08:00
dataTable = sampleDataTableField.GetValue(form2Instance) as DataTable;
}
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 清空所有数据,重新构建
dataTable.Clear();
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
int totalSamples = form2SampleDataList.Count;
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 1. 初始重量行
DataRow initialRow = dataTable.NewRow();
initialRow["序号"] = "初始重量(g)";
for (int i = 0; i < totalSamples; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
initialRow[$"试样{i + 1}"] = form2SampleDataList[i].InitialWeight;
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
initialRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(initialRow);
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 2. 浸润后重量行
DataRow afterRow = dataTable.NewRow();
afterRow["序号"] = "浸润后重量(g)";
for (int i = 0; i < totalSamples; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
afterRow[$"试样{i + 1}"] = form2SampleDataList[i].AfterWeight;
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
afterRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(afterRow);
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
// 3. 液体吸收量行 (%)
DataRow absorptionRow = dataTable.NewRow();
absorptionRow["序号"] = "液体吸收量(%)";
double[] absorptions = new double[totalSamples];
for (int i = 0; i < totalSamples; i++)
{
if (form2SampleDataList[i].InitialWeight > 0)
{
absorptions[i] = ((form2SampleDataList[i].AfterWeight - form2SampleDataList[i].InitialWeight)
/ form2SampleDataList[i].InitialWeight) * 100;
// 检查NaN
if (double.IsNaN(absorptions[i]) || double.IsInfinity(absorptions[i]))
{
absorptions[i] = 0;
}
}
else
{
absorptions[i] = 0;
}
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
absorptionRow[$"试样{i + 1}"] = absorptions[i];
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
absorptionRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(absorptionRow);
// 4. 浸润时间行
DataRow soakTimeRow = dataTable.NewRow();
soakTimeRow["序号"] = "浸润时间";
for (int i = 0; i < totalSamples; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
soakTimeRow[$"试样{i + 1}"] = $"{form2SampleDataList[i].SoakTime}s";
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
soakTimeRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(soakTimeRow);
// 5. 悬挂时间行
DataRow hangTimeRow = dataTable.NewRow();
hangTimeRow["序号"] = "悬挂时间";
for (int i = 0; i < totalSamples; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
hangTimeRow[$"试样{i + 1}"] = $"{form2SampleDataList[i].HangTime}s";
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
hangTimeRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(hangTimeRow);
// 6. 运行速度行
DataRow runSpeedRow = dataTable.NewRow();
runSpeedRow["序号"] = "运行速度";
for (int i = 0; i < totalSamples; i++)
{
if (dataTable.Columns.Contains($"试样{i + 1}"))
{
runSpeedRow[$"试样{i + 1}"] = $"{form2SampleDataList[i].RunSpeed}mm/min";
}
}
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
runSpeedRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(runSpeedRow);
// 计算并添加统计行每5个试样一组
int groupCount = (int)Math.Ceiling((double)totalSamples / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
int endSample = Math.Min(startSample + 4, totalSamples);
// 获取该组的吸收量数据
List<double> groupAbsorptions = new List<double>();
for (int i = startSample - 1; i < endSample; i++)
{
groupAbsorptions.Add(absorptions[i]);
}
if (groupAbsorptions.Count > 0)
{
// 7. 液体吸收量平均值行
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "液体吸收量平均值(%)";
double avgAbsorption = groupAbsorptions.Average();
// 检查NaN
if (double.IsNaN(avgAbsorption) || double.IsInfinity(avgAbsorption))
{
avgAbsorption = 0;
}
avgRow[$"试样{startSample}"] = avgAbsorption;
for (int i = 1; i <= currentSampleCount; i++)
{
if (i != startSample && dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(avgRow);
// 8. 液体吸收量最大值行
DataRow maxRow = dataTable.NewRow();
maxRow["序号"] = "液体吸收量最大值(%)";
double maxAbsorption = groupAbsorptions.Max();
// 检查NaN
if (double.IsNaN(maxAbsorption) || double.IsInfinity(maxAbsorption))
{
maxAbsorption = 0;
}
maxRow[$"试样{startSample}"] = maxAbsorption;
for (int i = 1; i <= currentSampleCount; i++)
{
if (i != startSample && dataTable.Columns.Contains($"试样{i}"))
{
maxRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(maxRow);
// 9. 标准偏差行
DataRow stdDevRow = dataTable.NewRow();
stdDevRow["序号"] = "标准偏差";
double stdDev = groupAbsorptions.Count > 1 ? CalculateStandardDeviation(groupAbsorptions.ToArray()) : 0;
// 检查NaN
if (double.IsNaN(stdDev) || double.IsInfinity(stdDev))
{
stdDev = 0;
}
stdDevRow[$"试样{startSample}"] = stdDev;
for (int i = 1; i <= currentSampleCount; i++)
{
if (i != startSample && dataTable.Columns.Contains($"试样{i}"))
{
stdDevRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(stdDevRow);
}
}
// 刷新显示
dataTable.AcceptChanges();
2026-01-04 18:38:18 +08:00
this.Invoke(new Action(() =>
{
var refreshMethod = form2Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form2Instance, null);
}));
2026-01-03 18:55:39 +08:00
2026-01-05 09:09:25 +08:00
System.Diagnostics.Debug.WriteLine($"Form2数据读取成功试样{newSampleIndex},初始重量={initialWeight:F2}g浸润后重量={afterWeight:F2}g累计{totalSamples}个试样");
2026-01-04 18:38:18 +08:00
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form2数据失败{ex.Message}");
}
}
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
/// <summary>
/// 将寄存器数据转换为double假设寄存器值为实际值*100
/// </summary>
private double ConvertRegistersToDouble(ushort[] registers)
{
if (registers == null || registers.Length < 2) return 0.0;
// 组合两个寄存器为32位整数高位在前
uint value = ((uint)registers[0] << 16) | registers[1];
2026-01-05 09:09:25 +08:00
double result = value / 100.0;
// 检查NaN
if (double.IsNaN(result) || double.IsInfinity(result))
{
return 0.0;
}
return result;
2026-01-04 18:38:18 +08:00
}
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
/// <summary>
/// 将寄存器数据转换为int
/// </summary>
private int ConvertRegistersToInt(ushort[] registers)
{
if (registers == null || registers.Length < 2) return 0;
// 组合两个寄存器为32位整数高位在前
return (registers[0] << 16) | registers[1];
}
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
/// <summary>
/// 获取下一个试样索引
/// </summary>
private int GetNextSampleIndex(DataTable dataTable, int currentSampleCount)
{
if (dataTable.Rows.Count == 0) return 0;
// 查找初始重量行
var initialRow = dataTable.AsEnumerable()
.FirstOrDefault(r => r.Field<string>("序号") == "初始重量(g)");
if (initialRow == null) return 0;
// 查找第一个空的试样列
for (int i = 0; i < currentSampleCount; i++)
{
var value = initialRow[$"试样{i + 1}"];
if (value == null || value == DBNull.Value ||
(value is double d && d == 0.0))
{
return i;
2026-01-03 18:55:39 +08:00
}
2026-01-04 18:38:18 +08:00
}
// 所有列都有数据,返回下一个索引
return currentSampleCount;
}
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
/// <summary>
/// 从现有DataTable中提取已有的试样数据
/// </summary>
private void ExtractExistingData(DataTable dataTable, double[] initialWeights, double[] afterWeights,
string[] soakTimes, string[] hangTimes, string[] runSpeeds, int excludeIndex)
{
if (dataTable.Rows.Count == 0) return;
var initialRow = dataTable.AsEnumerable().FirstOrDefault(r => r.Field<string>("序号") == "初始重量(g)");
var afterRow = dataTable.AsEnumerable().FirstOrDefault(r => r.Field<string>("序号") == "浸润后重量(g)");
var soakRow = dataTable.AsEnumerable().FirstOrDefault(r => r.Field<string>("序号") == "浸润时间");
var hangRow = dataTable.AsEnumerable().FirstOrDefault(r => r.Field<string>("序号") == "悬挂时间");
var speedRow = dataTable.AsEnumerable().FirstOrDefault(r => r.Field<string>("序号") == "运行速度");
for (int i = 0; i < initialWeights.Length; i++)
{
if (i == excludeIndex) continue; // 跳过当前要更新的索引
string colName = $"试样{i + 1}";
if (initialRow != null && initialRow.Table.Columns.Contains(colName))
{
var val = initialRow[colName];
initialWeights[i] = (val != null && val != DBNull.Value) ? Convert.ToDouble(val) : 0.0;
}
if (afterRow != null && afterRow.Table.Columns.Contains(colName))
{
var val = afterRow[colName];
afterWeights[i] = (val != null && val != DBNull.Value) ? Convert.ToDouble(val) : 0.0;
}
if (soakRow != null && soakRow.Table.Columns.Contains(colName))
{
var val = soakRow[colName];
soakTimes[i] = (val != null && val != DBNull.Value) ? val.ToString() : "0s";
}
if (hangRow != null && hangRow.Table.Columns.Contains(colName))
{
var val = hangRow[colName];
hangTimes[i] = (val != null && val != DBNull.Value) ? val.ToString() : "0s";
}
if (speedRow != null && speedRow.Table.Columns.Contains(colName))
{
var val = speedRow[colName];
runSpeeds[i] = (val != null && val != DBNull.Value) ? val.ToString() : "0mm/min";
}
2026-01-03 18:55:39 +08:00
}
2026-01-04 18:38:18 +08:00
}
/// <summary>
/// 更新Form2显示精确匹配GenerateMockData的逻辑
/// </summary>
private void UpdateForm2Display(DataTable dataTable, double[] initialWeights, double[] afterWeights,
string[] soakTimes, string[] hangTimes, string[] runSpeeds, int count)
{
dataTable.Clear();
// 1. 初始重量行
DataRow initialRow = dataTable.NewRow();
initialRow["序号"] = "初始重量(g)";
for (int i = 0; i < count; i++)
2026-01-03 18:55:39 +08:00
{
2026-01-04 18:38:18 +08:00
initialRow[$"试样{i + 1}"] = initialWeights[i];
}
dataTable.Rows.Add(initialRow);
// 2. 浸润后重量行
DataRow afterRow = dataTable.NewRow();
afterRow["序号"] = "浸润后重量(g)";
for (int i = 0; i < count; i++)
{
afterRow[$"试样{i + 1}"] = afterWeights[i];
}
dataTable.Rows.Add(afterRow);
// 3. 液体吸收量行 (%)
DataRow absorptionRow = dataTable.NewRow();
absorptionRow["序号"] = "液体吸收量(%)";
double[] absorptions = new double[count];
for (int i = 0; i < count; i++)
{
if (initialWeights[i] > 0)
{
absorptions[i] = ((afterWeights[i] - initialWeights[i]) / initialWeights[i]) * 100;
2026-01-05 09:09:25 +08:00
// 检查NaN
if (double.IsNaN(absorptions[i]) || double.IsInfinity(absorptions[i]))
{
absorptions[i] = 0;
}
}
else
{
absorptions[i] = 0;
2026-01-04 18:38:18 +08:00
}
absorptionRow[$"试样{i + 1}"] = absorptions[i];
2026-01-03 18:55:39 +08:00
}
2026-01-04 18:38:18 +08:00
dataTable.Rows.Add(absorptionRow);
// 4. 浸润时间行
DataRow soakTimeRow = dataTable.NewRow();
soakTimeRow["序号"] = "浸润时间";
for (int i = 0; i < count; i++)
{
soakTimeRow[$"试样{i + 1}"] = soakTimes[i];
}
dataTable.Rows.Add(soakTimeRow);
// 5. 悬挂时间行
DataRow hangTimeRow = dataTable.NewRow();
hangTimeRow["序号"] = "悬挂时间";
for (int i = 0; i < count; i++)
{
hangTimeRow[$"试样{i + 1}"] = hangTimes[i];
}
dataTable.Rows.Add(hangTimeRow);
// 6. 运行速度行
DataRow runSpeedRow = dataTable.NewRow();
runSpeedRow["序号"] = "运行速度";
for (int i = 0; i < count; i++)
{
runSpeedRow[$"试样{i + 1}"] = runSpeeds[i];
}
dataTable.Rows.Add(runSpeedRow);
2026-01-05 09:09:25 +08:00
// 计算有效试样数量(初始重量>0的试样且吸收量不是NaN
var validAbsorptions = absorptions
.Where((a, idx) => initialWeights[idx] > 0 && !double.IsNaN(a) && !double.IsInfinity(a))
.ToArray();
2026-01-04 18:38:18 +08:00
int validCount = validAbsorptions.Length;
// 7. 液体吸收量平均值行
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "液体吸收量平均值(%)";
double avgAbsorption = validCount > 0 ? validAbsorptions.Average() : 0;
2026-01-05 09:09:25 +08:00
// 检查NaN
if (double.IsNaN(avgAbsorption) || double.IsInfinity(avgAbsorption))
{
avgAbsorption = 0;
}
2026-01-04 18:38:18 +08:00
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 = validCount > 0 ? validAbsorptions.Max() : 0;
2026-01-05 09:09:25 +08:00
// 检查NaN
if (double.IsNaN(maxAbsorption) || double.IsInfinity(maxAbsorption))
{
maxAbsorption = 0;
}
2026-01-04 18:38:18 +08:00
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 = validCount > 1 ? CalculateStandardDeviation(validAbsorptions) : 0;
stdDevRow["试样1"] = stdDev;
for (int i = 2; i <= count; i++)
{
stdDevRow[$"试样{i}"] = DBNull.Value;
}
dataTable.Rows.Add(stdDevRow);
dataTable.AcceptChanges();
2026-01-03 18:55:39 +08:00
}
/// <summary>
/// 读取Form3数据液体芯吸速率
2026-01-04 18:38:18 +08:00
/// PLC地址D200 - 吸水时间(s)D454 - 吸芯高度(mm)每次测试读取1个试样各2个字节
2026-01-03 18:55:39 +08:00
/// 信号量M310
2026-01-04 18:38:18 +08:00
/// 每次测试添加一个试样的一次测试数据,试样数量动态增长
2026-01-03 18:55:39 +08:00
/// </summary>
private void ReadForm3Data(byte slaveId)
{
try
{
2026-01-04 18:38:18 +08:00
// 读取吸水时间D2002个字节
ushort[] timeRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 2);
// 读取吸芯高度D4542个字节
ushort[] heightRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 454, 2);
2026-01-03 18:55:39 +08:00
// 使用反射获取Form3的私有字段
var sampleDataTableField = form3Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2026-01-04 18:38:18 +08:00
var currentSampleCountField = form3Instance.GetType()
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var isVerticalLayoutField = form3Instance.GetType()
.GetField("isVerticalLayout", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (sampleDataTableField == null || currentSampleCountField == null || isVerticalLayoutField == null)
{
System.Diagnostics.Debug.WriteLine("无法获取Form3的私有字段");
return;
}
DataTable dataTable = sampleDataTableField.GetValue(form3Instance) as DataTable;
int currentSampleCount = (int)currentSampleCountField.GetValue(form3Instance);
bool isVerticalLayout = (bool)isVerticalLayoutField.GetValue(form3Instance);
if (dataTable == null || dataTable.Rows.Count == 0)
2026-01-03 18:55:39 +08:00
{
2026-01-04 18:38:18 +08:00
System.Diagnostics.Debug.WriteLine("DataTable为空或没有行");
return;
}
// 将寄存器值转换为实际数据
double wickingTime = ConvertRegistersToDouble(timeRegisters); // 吸水时间(秒)
double wickingHeight = ConvertRegistersToDouble(heightRegisters); // 吸芯高度mm
// 确定当前要填充的位置(试样索引和测试次数)
var position = GetNextForm3Position(dataTable, currentSampleCount, isVerticalLayout);
int sampleIndex = position.Item1; // 试样索引1-based
int testIndex = position.Item2; // 测试次数1-3
// 如果需要扩展试样数量
if (sampleIndex > currentSampleCount)
{
currentSampleCount = sampleIndex;
currentSampleCountField.SetValue(form3Instance, currentSampleCount);
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
// 重新初始化DataTable和DataGridView
this.Invoke(new Action(() =>
{
var setSampleCountMethod = form3Instance.GetType()
.GetMethod("SetSampleCount", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
setSampleCountMethod?.Invoke(form3Instance, new object[] { currentSampleCount });
}));
// 重新获取更新后的DataTable
dataTable = sampleDataTableField.GetValue(form3Instance) as DataTable;
}
// 根据布局方式填充数据精确参考GenerateMockData的方式
if (isVerticalLayout)
{
// 纵向布局第1行是吸水时间第2行是吸芯高度
if (dataTable.Rows.Count >= 2)
2026-01-03 18:55:39 +08:00
{
DataRow timeRow = dataTable.Rows[0];
2026-01-04 18:38:18 +08:00
DataRow heightRow = dataTable.Rows[1];
string columnName = $"试样{sampleIndex}_{testIndex}";
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
// 填充吸水时间
if (timeRow.Table.Columns.Contains(columnName))
2026-01-03 18:55:39 +08:00
{
2026-01-04 18:38:18 +08:00
timeRow[columnName] = wickingTime;
2026-01-03 18:55:39 +08:00
}
2026-01-04 18:38:18 +08:00
// 填充吸芯高度
if (heightRow.Table.Columns.Contains(columnName))
2026-01-03 18:55:39 +08:00
{
2026-01-04 18:38:18 +08:00
heightRow[columnName] = wickingHeight;
}
2026-01-03 18:55:39 +08:00
}
}
2026-01-04 18:38:18 +08:00
else
{
// 横向布局:每行是一个试样的一次测试
// 计算行索引:(sampleIndex-1) * 3 + (testIndex-1)
int rowIndex = (sampleIndex - 1) * 3 + (testIndex - 1);
if (rowIndex < dataTable.Rows.Count)
{
DataRow row = dataTable.Rows[rowIndex];
string rowName = $"试样{sampleIndex}_{testIndex}";
// 确保序号正确
row["序号"] = rowName;
// 填充吸水时间列
if (row.Table.Columns.Contains("吸水时间(s)"))
{
row["吸水时间(s)"] = wickingTime;
}
// 填充吸芯高度列
if (row.Table.Columns.Contains("吸芯高度(mm)"))
{
row["吸芯高度(mm)"] = wickingHeight;
}
}
}
// 调用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);
}));
2026-01-03 18:55:39 +08:00
2026-01-04 18:38:18 +08:00
System.Diagnostics.Debug.WriteLine($"Form3数据读取成功 - 试样{sampleIndex}第{testIndex}次测试,吸水时间:{wickingTime:F2}秒,吸芯高度:{wickingHeight:F2}mm");
2026-01-03 18:55:39 +08:00
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form3数据失败{ex.Message}");
}
}
2026-01-04 18:38:18 +08:00
/// <summary>
/// 获取Form3下一个要填充的位置试样索引和测试次数
/// 返回:(试样索引, 测试次数) - 都是1-based
/// </summary>
private Tuple<int, int> GetNextForm3Position(DataTable dataTable, int currentSampleCount, bool isVerticalLayout)
{
if (isVerticalLayout)
{
// 纵向布局第1行是吸水时间行
if (dataTable.Rows.Count > 0)
{
DataRow timeRow = dataTable.Rows[0];
// 遍历所有试样的所有测试次数,找到第一个空位
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
string columnName = $"试样{i}_{j}";
if (timeRow.Table.Columns.Contains(columnName))
{
var value = timeRow[columnName];
if (value == null || value == DBNull.Value ||
(value is double d && Math.Abs(d) < 0.001))
{
return Tuple.Create(i, j);
}
}
}
}
// 所有位置都已填充返回下一个试样的第1次测试
return Tuple.Create(currentSampleCount + 1, 1);
}
}
else
{
// 横向布局:每行是一个试样的一次测试
for (int i = 1; i <= currentSampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
int rowIndex = (i - 1) * 3 + (j - 1);
if (rowIndex < dataTable.Rows.Count)
{
DataRow row = dataTable.Rows[rowIndex];
if (row.Table.Columns.Contains("吸水时间(s)"))
{
var value = row["吸水时间(s)"];
if (value == null || value == DBNull.Value ||
(value is double d && Math.Abs(d) < 0.001))
{
return Tuple.Create(i, j);
}
}
}
}
}
// 所有位置都已填充返回下一个试样的第1次测试
return Tuple.Create(currentSampleCount + 1, 1);
}
// 默认返回第1个试样的第1次测试
return Tuple.Create(1, 1);
}
2026-01-03 18:55:39 +08:00
/// <summary>
/// 计算标准偏差
/// </summary>
private double CalculateStandardDeviation(double[] values)
{
if (values == null || values.Length <= 1) return 0;
2026-01-05 09:09:25 +08:00
// 过滤掉NaN和Infinity值
var validValues = values.Where(v => !double.IsNaN(v) && !double.IsInfinity(v)).ToArray();
if (validValues.Length <= 1) return 0;
double avg = validValues.Average();
double sumOfSquares = validValues.Sum(val => Math.Pow(val - avg, 2));
double result = Math.Sqrt(sumOfSquares / (validValues.Length - 1));
// 检查结果是否为NaN
if (double.IsNaN(result) || double.IsInfinity(result))
{
return 0;
}
return result;
2026-01-03 18:55:39 +08:00
}
/// <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);
}
2025-12-31 17:26:27 +08:00
private void InitializeTabControl()
{
tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged;
}
2025-12-31 09:43:35 +08:00
2025-12-31 17:26:27 +08:00
private void TabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateTitleForCurrentTab();
2025-12-31 17:41:01 +08:00
// 根据选中的 Tab 显示/隐藏切换布局按钮
// 只有在 Form3 (Tab 2, index=2) 时显示切换按钮
buttonToggleLayout.Visible = (tabControl1.SelectedIndex == 2);
// 更新切换按钮的文本
if (tabControl1.SelectedIndex == 2 && form3Instance != null)
{
UpdateToggleButtonText();
}
2025-12-31 09:43:35 +08:00
}
2025-12-31 17:26:27 +08:00
private void UpdateTitleForCurrentTab()
2025-12-31 09:43:35 +08:00
{
2025-12-31 17:26:27 +08:00
switch (tabControl1.SelectedIndex)
2025-12-31 09:43:35 +08:00
{
2025-12-31 17:26:27 +08:00
case 0:
label1.Text = " 液体吸收时间测试报告";
break;
case 1:
label1.Text = " 液体吸收量测试报告";
break;
case 2:
label1.Text = " 液体芯吸速率测试报告";
break;
2025-12-31 09:43:35 +08:00
}
}
2025-12-31 17:26:27 +08:00
private void InitializeEmbeddedForms()
2025-12-31 09:43:35 +08:00
{
2025-12-31 17:26:27 +08:00
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();
2025-12-31 09:43:35 +08:00
}
2026-01-03 15:59:38 +08:00
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);
}
2025-12-31 17:26:27 +08:00
private void buttonPrint_Click(object sender, EventArgs e)
2025-12-31 09:43:35 +08:00
{
2025-12-31 17:26:27 +08:00
MessageBox.Show("打印功能开发中", "提示");
2025-12-31 09:43:35 +08:00
}
2025-12-31 17:26:27 +08:00
private void buttonExport_Click(object sender, EventArgs e)
2025-12-31 09:43:35 +08:00
{
2025-12-31 17:26:27 +08:00
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;
}
}
2025-12-31 17:41:01 +08:00
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 ? "🔄 横向布局" : "🔄 纵向布局";
}
}
2025-12-31 17:26:27 +08:00
private void ExportIntegratedReport(string filePath)
{
try
{
IWorkbook workbook = new XSSFWorkbook();
2025-12-31 18:36:52 +08:00
// 创建单个整合的工作表
CreateIntegratedSheet(workbook);
2025-12-31 17:26:27 +08:00
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(fs);
}
MessageBox.Show($"导出成功:{filePath}", "成功");
}
catch (Exception ex)
{
MessageBox.Show($"导出失败:{ex.Message}", "错误");
}
2025-12-31 09:43:35 +08:00
}
2025-12-31 18:36:52 +08:00
private void CreateIntegratedSheet(IWorkbook workbook)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
ISheet sheet = workbook.CreateSheet("吸收性测试报告");
2025-12-31 17:54:34 +08:00
var styles = CreateReportStyles(workbook);
2025-12-31 18:36:52 +08:00
// 获取三个表单的数据
var sampleCountField1 = form1Instance.GetType()
2025-12-31 17:54:34 +08:00
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2025-12-31 18:36:52 +08:00
int sampleCount = sampleCountField1 != null ? (int)sampleCountField1.GetValue(form1Instance) : 5;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
var dataTableField1 = form1Instance.GetType()
2025-12-31 17:54:34 +08:00
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
2025-12-31 18:36:52 +08:00
DataTable dataTable1 = dataTableField1?.GetValue(form1Instance) as DataTable;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
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;
2025-12-31 17:54:34 +08:00
int currentRow = 0;
2025-12-31 18:36:52 +08:00
// 1. 创建总标题
2025-12-31 17:54:34 +08:00
IRow titleRow = sheet.CreateRow(currentRow++);
2025-12-31 18:36:52 +08:00
titleRow.Height = 800;
2025-12-31 17:54:34 +08:00
ICell titleCell = titleRow.CreateCell(0);
2025-12-31 18:36:52 +08:00
titleCell.SetCellValue("吸收性测试报告");
2025-12-31 17:54:34 +08:00
titleCell.CellStyle = styles.titleStyle;
2025-12-31 18:36:52 +08:00
// 为合并区域的所有单元格设置样式(总标题不需要边框,但为了一致性也设置)
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);
2025-12-31 17:54:34 +08:00
currentRow++; // 空行
2025-12-31 18:36:52 +08:00
// 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)
{
2025-12-31 18:45:00 +08:00
// 从界面获取实际数据
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");
2025-12-31 18:36:52 +08:00
// 第一行信息
IRow infoRow1 = sheet.CreateRow(currentRow++);
infoRow1.Height = 400;
// 样品名称
ICell cell1 = infoRow1.CreateCell(0);
cell1.SetCellValue("样品名称:");
cell1.CellStyle = styles.dataStyle;
ICell cell2 = infoRow1.CreateCell(1);
2025-12-31 18:45:00 +08:00
cell2.SetCellValue(string.IsNullOrEmpty(sampleName) ? "" : sampleName);
2025-12-31 18:36:52 +08:00
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);
2025-12-31 18:45:00 +08:00
cell4.SetCellValue(string.IsNullOrEmpty(materialCode) ? "" : materialCode);
2025-12-31 18:36:52 +08:00
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);
2025-12-31 18:45:00 +08:00
cell6.SetCellValue(string.IsNullOrEmpty(batchNumber) ? "" : batchNumber);
2025-12-31 18:36:52 +08:00
cell6.CellStyle = styles.yellowStyle;
// 操作人员
ICell cell7 = infoRow1.CreateCell(8);
cell7.SetCellValue("操作人员:");
cell7.CellStyle = styles.dataStyle;
ICell cell8 = infoRow1.CreateCell(9);
2025-12-31 18:45:00 +08:00
cell8.SetCellValue(string.IsNullOrEmpty(operatorName) ? "" : operatorName);
2025-12-31 18:36:52 +08:00
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);
2025-12-31 18:45:00 +08:00
cell22.SetCellValue(testTime);
2025-12-31 18:36:52 +08:00
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);
2025-12-31 18:45:00 +08:00
cell24.SetCellValue(string.IsNullOrEmpty(instrument) ? "" : instrument);
2025-12-31 18:36:52 +08:00
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);
2025-12-31 18:45:00 +08:00
cell26.SetCellValue(string.IsNullOrEmpty(deviceNumber) ? "" : deviceNumber);
2025-12-31 18:36:52 +08:00
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));
// 表头
2025-12-31 17:54:34 +08:00
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;
}
2025-12-31 18:36:52 +08:00
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
2025-12-31 17:54:34 +08:00
2026-01-03 14:04:08 +08:00
// 如果是平均时间行,只显示一个合并的单元格
if (rowName.Contains("平均"))
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
ICell avgCell = row.CreateCell(1);
if (dataTable.Columns.Contains("试样1") && dataRow["试样1"] != DBNull.Value)
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
if (double.TryParse(dataRow["试样1"].ToString(), out double value))
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
avgCell.SetCellValue(value);
2025-12-31 18:36:52 +08:00
}
else
{
2026-01-03 14:04:08 +08:00
avgCell.SetCellValue("");
2025-12-31 18:36:52 +08:00
}
}
else
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
avgCell.SetCellValue("");
}
avgCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount; i++)
{
row.CreateCell(i).CellStyle = styles.yellowStyle;
2025-12-31 17:54:34 +08:00
}
2026-01-03 14:04:08 +08:00
// 合并平均时间单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount));
2025-12-31 18:36:52 +08:00
}
2026-01-03 14:04:08 +08:00
else
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
// 普通数据行,显示每个试样的数据
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;
}
2025-12-31 17:54:34 +08:00
}
}
}
2025-12-31 18:36:52 +08:00
}
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
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;
// 为合并区域的所有单元格设置样式
2026-01-03 14:04:08 +08:00
for (int i = 1; i <= sampleCount; i++)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
2025-12-31 17:54:34 +08:00
}
2026-01-03 14:04:08 +08:00
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount));
2025-12-31 18:36:52 +08:00
2026-01-03 14:04:08 +08:00
// 表头
IRow headerRow = sheet.CreateRow(currentRow++);
headerRow.Height = 400;
ICell seqCell = headerRow.CreateCell(0);
seqCell.SetCellValue("序号");
seqCell.CellStyle = styles.headerStyle;
2025-12-31 17:54:34 +08:00
for (int i = 1; i <= sampleCount; i++)
{
2026-01-03 14:04:08 +08:00
ICell cell = headerRow.CreateCell(i);
2025-12-31 17:54:34 +08:00
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
}
2025-12-31 18:36:52 +08:00
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
2025-12-31 17:54:34 +08:00
2026-01-03 14:04:08 +08:00
// 判断是否是需要合并的行(平均值、最大值、标准偏差)
bool isMergedRow = rowName.Contains("平均值") || rowName.Contains("最大值") || rowName.Contains("标准偏差");
if (isMergedRow)
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
// 合并为一列,只显示第一个试样的值
ICell valueCell = row.CreateCell(1);
if (dataTable.Columns.Contains("试样1") && dataRow["试样1"] != DBNull.Value)
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
object value = dataRow["试样1"];
2025-12-31 18:36:52 +08:00
if (value != null && double.TryParse(value.ToString(), out double numValue))
{
2026-01-03 14:04:08 +08:00
valueCell.SetCellValue(numValue);
2025-12-31 18:36:52 +08:00
}
else
{
2026-01-03 14:04:08 +08:00
valueCell.SetCellValue("");
2025-12-31 18:36:52 +08:00
}
}
else
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
valueCell.SetCellValue("");
2025-12-31 17:54:34 +08:00
}
2026-01-03 14:04:08 +08:00
valueCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount; i++)
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
row.CreateCell(i).CellStyle = styles.yellowStyle;
2025-12-31 18:36:52 +08:00
}
2026-01-03 14:04:08 +08:00
// 合并单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount));
2025-12-31 18:36:52 +08:00
}
2026-01-03 14:04:08 +08:00
else
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
// 普通数据行,显示每个试样的数据
for (int i = 1; i <= sampleCount; i++)
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
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;
2025-12-31 18:36:52 +08:00
}
}
}
2025-12-31 17:54:34 +08:00
}
}
2025-12-31 18:36:52 +08:00
private void CreateForm3Section(ISheet sheet, ref int currentRow, int sampleCount, DataTable dataTable,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
// 子标题
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++)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
2025-12-31 17:54:34 +08:00
}
2025-12-31 18:36:52 +08:00
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount * 3));
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
// 第一行表头(纵向/横向)
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));
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
currentRow++;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
// 第二行表头试样1-N每个3列
IRow headerRow2 = sheet.CreateRow(currentRow);
headerRow2.Height = 400;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
// 序号列在第二行也需要创建并设置样式(用于合并)
headerRow2.CreateCell(0).CellStyle = styles.headerStyle;
2025-12-31 17:54:34 +08:00
int colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
2025-12-31 18:36:52 +08:00
ICell cell = headerRow2.CreateCell(colIndex);
2025-12-31 17:54:34 +08:00
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
2025-12-31 18:36:52 +08:00
// 为合并区域的所有单元格设置样式
headerRow2.CreateCell(colIndex + 1).CellStyle = styles.headerStyle;
headerRow2.CreateCell(colIndex + 2).CellStyle = styles.headerStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow, currentRow, colIndex, colIndex + 2));
2025-12-31 17:54:34 +08:00
colIndex += 3;
}
2025-12-31 18:36:52 +08:00
// 合并序号列(跨两行)
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow, 0, 0));
currentRow++;
// 第三行表头1、2、3
IRow headerRow3 = sheet.CreateRow(currentRow++);
headerRow3.Height = 400;
2025-12-31 17:54:34 +08:00
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
2025-12-31 18:36:52 +08:00
ICell cell = headerRow3.CreateCell(colIndex++);
2025-12-31 17:54:34 +08:00
cell.SetCellValue(j.ToString());
cell.CellStyle = styles.headerStyle;
}
}
2025-12-31 18:36:52 +08:00
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
2025-12-31 17:54:34 +08:00
{
2025-12-31 18:36:52 +08:00
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
2025-12-31 17:54:34 +08:00
2025-12-31 18:36:52 +08:00
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
2026-01-03 14:04:08 +08:00
// 判断是否是标准偏差行(需要合并为一列)
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
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
// 普通数据行:显示每个试样的每次测试数据
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
for (int j = 1; j <= 3; j++)
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
ICell cell = row.CreateCell(colIndex++);
string columnName = $"试样{i}_{j}";
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
2025-12-31 17:54:34 +08:00
{
2026-01-03 14:04:08 +08:00
if (double.TryParse(dataRow[columnName].ToString(), out double value))
2025-12-31 18:36:52 +08:00
{
2026-01-03 14:04:08 +08:00
if (Math.Abs(value) >= 0.001)
{
cell.SetCellValue(value);
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
2025-12-31 18:36:52 +08:00
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
2025-12-31 17:54:34 +08:00
}
2026-01-03 14:04:08 +08:00
cell.CellStyle = styles.yellowStyle;
2025-12-31 17:54:34 +08:00
}
}
}
}
}
}
2025-12-31 18:36:52 +08:00
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
2025-12-31 17:54:34 +08:00
{
2026-01-03 18:55:39 +08:00
// 停止并释放定时器
2026-01-03 15:59:38 +08:00
timeUpdateTimer?.Stop();
timeUpdateTimer?.Dispose();
2026-01-03 18:55:39 +08:00
_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}");
}
2025-12-31 18:36:52 +08:00
Application.Exit();
2025-12-31 17:54:34 +08:00
}
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;
}
2025-12-31 09:43:35 +08:00
}
}