2573 lines
102 KiB
C#
2573 lines
102 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Windows.Forms;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net.Sockets;
|
||
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;
|
||
|
||
// TCP和Modbus相关
|
||
private const string ModbusTcpHost = "192.168.1.10";
|
||
private const int ModbusTcpPort = 502;
|
||
private const int ModbusTcpConnectTimeoutMilliseconds = 1000;
|
||
private const int ModbusTcpReconnectIntervalMilliseconds = 2000;
|
||
private const string FORM3_ROW_WICKING_TIME = "吸水时间(s)";
|
||
private const string FORM3_ROW_WICKING_HEIGHT = "吸芯高度(mm)";
|
||
|
||
private TcpClient _tcpClient;
|
||
private IModbusMaster _modbusMaster;
|
||
private System.Windows.Forms.Timer _readTimer;
|
||
private System.Windows.Forms.Timer _reconnectTimer;
|
||
private bool _isConnecting;
|
||
|
||
// 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 double RunSpeed { get; set; }
|
||
}
|
||
|
||
public MainForm()
|
||
{
|
||
InitializeComponent();
|
||
InitializeTabControl();
|
||
InitializeEmbeddedForms();
|
||
InitializeTimeUpdate();
|
||
InitializeTcpModbus();
|
||
CenterInfoControls();
|
||
CenterBottomButtons();
|
||
|
||
// 监听窗体大小变化以重新居中按钮
|
||
this.Resize += (s, e) => CenterBottomButtons();
|
||
}
|
||
|
||
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>
|
||
/// 初始化TCP和Modbus通信
|
||
/// </summary>
|
||
private void InitializeTcpModbus()
|
||
{
|
||
// 初始化读取定时器(信号量触发模式)
|
||
_readTimer = new System.Windows.Forms.Timer();
|
||
_readTimer.Interval = 100; // 100ms检查一次信号量
|
||
_readTimer.Tick += ReadTimer_Tick;
|
||
|
||
_reconnectTimer = new System.Windows.Forms.Timer();
|
||
_reconnectTimer.Interval = ModbusTcpReconnectIntervalMilliseconds;
|
||
_reconnectTimer.Tick += ReconnectTimer_Tick;
|
||
|
||
if (!TryConnectTcpModbus())
|
||
{
|
||
_reconnectTimer.Start();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重连定时器
|
||
/// </summary>
|
||
private void ReconnectTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
if (_modbusMaster != null && IsTcpConnected())
|
||
{
|
||
_reconnectTimer.Stop();
|
||
return;
|
||
}
|
||
|
||
TryConnectTcpModbus();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试连接Modbus TCP设备
|
||
/// </summary>
|
||
private bool TryConnectTcpModbus()
|
||
{
|
||
if (_isConnecting)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
_isConnecting = true;
|
||
try
|
||
{
|
||
DisposeModbusConnection();
|
||
|
||
var tcpClient = new TcpClient();
|
||
IAsyncResult connectResult = tcpClient.BeginConnect(ModbusTcpHost, ModbusTcpPort, null, null);
|
||
bool connected = connectResult.AsyncWaitHandle.WaitOne(ModbusTcpConnectTimeoutMilliseconds);
|
||
connectResult.AsyncWaitHandle.Close();
|
||
|
||
if (!connected)
|
||
{
|
||
tcpClient.Close();
|
||
throw new TimeoutException($"连接 {ModbusTcpHost}:{ModbusTcpPort} 超时");
|
||
}
|
||
|
||
tcpClient.EndConnect(connectResult);
|
||
tcpClient.ReceiveTimeout = 500;
|
||
tcpClient.SendTimeout = 500;
|
||
|
||
_tcpClient = tcpClient;
|
||
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
|
||
_modbusMaster.Transport.WaitToRetryMilliseconds = 200;
|
||
_modbusMaster.Transport.Retries = 1;
|
||
_modbusMaster.Transport.ReadTimeout = 500;
|
||
|
||
_reconnectTimer.Stop();
|
||
_readTimer.Start();
|
||
|
||
System.Diagnostics.Debug.WriteLine($"Modbus TCP连接成功:{ModbusTcpHost}:{ModbusTcpPort}");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"Modbus TCP连接失败:{ex.Message}");
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
_isConnecting = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 定时读取Modbus数据(信号量触发模式)
|
||
/// </summary>
|
||
private void ReadTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
if (_modbusMaster == null || !IsTcpConnected())
|
||
{
|
||
HandleModbusDisconnected("Modbus TCP连接不可用");
|
||
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 signalM253 = ReadCoil(slaveId, 253); // Form2 设备停止信号量
|
||
bool signalM310 = ReadCoil(slaveId, 310); // Form3信号量
|
||
|
||
// 根据信号量触发相应的数据读取
|
||
if (signalM103)
|
||
{
|
||
ReadForm1Data(slaveId);
|
||
// 读取完成后清除信号量
|
||
WriteCoil(slaveId, 103, false);
|
||
}
|
||
|
||
// Form2数据读取:只有M252为true且M253为false时才读取数据
|
||
// M253为true表示设备停止,防止误触发
|
||
if (signalM252 && !signalM253)
|
||
{
|
||
ReadForm2Data(slaveId);
|
||
// 读取完成后清除信号量
|
||
WriteCoil(slaveId, 252, false);
|
||
}
|
||
|
||
if (signalM310)
|
||
{
|
||
ReadForm3Data(slaveId);
|
||
// 读取完成后清除信号量
|
||
WriteCoil(slaveId, 310, false);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 读取失败时不弹窗,避免频繁打扰用户
|
||
System.Diagnostics.Debug.WriteLine($"Modbus读取失败:{ex.Message}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取单个线圈状态
|
||
/// </summary>
|
||
private bool ReadCoil(byte slaveId, ushort address)
|
||
{
|
||
try
|
||
{
|
||
bool[] coils = _modbusMaster.ReadCoils(slaveId, address, 1);
|
||
return coils[0];
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取线圈失败:{ex.Message}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
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}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查TCP连接状态
|
||
/// </summary>
|
||
private bool IsTcpConnected()
|
||
{
|
||
try
|
||
{
|
||
return _tcpClient != null && _tcpClient.Client != null && _tcpClient.Connected;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放当前Modbus TCP连接
|
||
/// </summary>
|
||
private void DisposeModbusConnection()
|
||
{
|
||
try
|
||
{
|
||
_modbusMaster?.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"释放Modbus主站失败:{ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_modbusMaster = null;
|
||
}
|
||
|
||
try
|
||
{
|
||
_tcpClient?.Close();
|
||
_tcpClient?.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"关闭TCP连接失败:{ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_tcpClient = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 进入断线重连流程
|
||
/// </summary>
|
||
private void HandleModbusDisconnected(string reason)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"Modbus TCP断开,准备重连:{reason}");
|
||
_readTimer?.Stop();
|
||
DisposeModbusConnection();
|
||
|
||
if (_reconnectTimer != null && !_reconnectTimer.Enabled)
|
||
{
|
||
_reconnectTimer.Start();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取Form1数据(液体吸收时间)
|
||
/// PLC地址:D200 - 时间(s),每次测试读取1个试样(2个字节)
|
||
/// 信号量:M103
|
||
/// 每次测试动态添加一列显示,每5个试样计算一次平均值
|
||
/// </summary>
|
||
private void ReadForm1Data(byte slaveId)
|
||
{
|
||
try
|
||
{
|
||
// 读取1个寄存器(2个字节)- D200
|
||
ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 1);
|
||
|
||
// 使用反射获取Form1的私有字段
|
||
var sampleDataTableField = form1Instance.GetType()
|
||
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
var currentSampleCountField = form1Instance.GetType()
|
||
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
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];
|
||
|
||
// 检查NaN
|
||
if (double.IsNaN(timeValue) || double.IsInfinity(timeValue))
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("读取的时间值无效,跳过本次读取");
|
||
return;
|
||
}
|
||
|
||
// 添加到历史数据
|
||
form1TimeValues.Add(timeValue);
|
||
|
||
// 新试样的索引
|
||
int newSampleIndex = form1TimeValues.Count;
|
||
|
||
// 如果需要扩展列数
|
||
if (newSampleIndex > currentSampleCount)
|
||
{
|
||
// 添加新列到DataTable
|
||
if (!dataTable.Columns.Contains($"试样{newSampleIndex}"))
|
||
{
|
||
dataTable.Columns.Add($"试样{newSampleIndex}", typeof(double));
|
||
}
|
||
|
||
// 更新currentSampleCount
|
||
currentSampleCount = newSampleIndex;
|
||
currentSampleCountField.SetValue(form1Instance, currentSampleCount);
|
||
|
||
// 重新初始化DataGridView以显示新列
|
||
this.Invoke(new Action(() =>
|
||
{
|
||
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;
|
||
}
|
||
|
||
// 清空所有数据,重新构建
|
||
dataTable.Clear();
|
||
|
||
// 创建时间行
|
||
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);
|
||
|
||
// 计算并添加平均值行:每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);
|
||
|
||
// 计算该组的平均值
|
||
double groupSum = 0;
|
||
int validCount = 0;
|
||
|
||
for (int i = startSample; i <= endSample; i++)
|
||
{
|
||
groupSum += form1TimeValues[i - 1];
|
||
validCount++;
|
||
}
|
||
|
||
if (validCount > 0)
|
||
{
|
||
double groupAvg = groupSum / validCount;
|
||
|
||
// 检查NaN
|
||
if (double.IsNaN(groupAvg) || double.IsInfinity(groupAvg))
|
||
{
|
||
groupAvg = 0;
|
||
}
|
||
|
||
// 创建平均值行
|
||
DataRow avgRow = dataTable.NewRow();
|
||
avgRow["序号"] = $"平均时间(s) 试样{startSample}-{endSample}";
|
||
|
||
// 只在该组第一个试样列显示平均值,其他列设置为DBNull
|
||
for (int i = 1; i <= currentSampleCount; i++)
|
||
{
|
||
if (dataTable.Columns.Contains($"试样{i}"))
|
||
{
|
||
// 只在该组第一个试样列显示平均值
|
||
if (i == startSample)
|
||
{
|
||
avgRow[$"试样{i}"] = groupAvg;
|
||
}
|
||
else
|
||
{
|
||
avgRow[$"试样{i}"] = DBNull.Value;
|
||
}
|
||
}
|
||
}
|
||
|
||
dataTable.Rows.Add(avgRow);
|
||
}
|
||
}
|
||
|
||
// 刷新显示
|
||
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}个试样");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取Form1数据失败:{ex.Message}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取Form2数据(液体吸收量)
|
||
/// PLC地址:D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间, D310 - 运行速度
|
||
/// 信号量:M252
|
||
/// 每次测试动态添加一列显示,每5个试样计算平均值、最大值、标准偏差
|
||
/// </summary>
|
||
private void ReadForm2Data(byte slaveId)
|
||
{
|
||
try
|
||
{
|
||
// 读取初始重量(D420,2个寄存器,浮点数,单位:g,精度0.01g)
|
||
ushort[] initialWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 420, 2);
|
||
double initialWeight = ConvertRegistersToDouble(initialWeightReg);
|
||
initialWeight = ModbusUshortToFloat(initialWeightReg[1], initialWeightReg[0]);
|
||
|
||
// 读取浸润后重量(D422,2个寄存器,浮点数,单位:g,精度0.01g)
|
||
ushort[] afterWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 422, 2);
|
||
double afterWeight = ConvertRegistersToDouble(afterWeightReg);
|
||
afterWeight = ModbusUshortToFloat(afterWeightReg[1], afterWeightReg[0]);
|
||
|
||
// 读取浸润时间(D402,1个寄存器,整数,单位:秒)
|
||
ushort[] soakTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 402, 1);
|
||
int soakTime = ConvertSingleRegisterToInt(soakTimeReg[0]);
|
||
|
||
// 读取悬挂时间(D406,1个寄存器,整数,单位:秒)
|
||
ushort[] hangTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 406, 1);
|
||
int hangTime = ConvertSingleRegisterToInt(hangTimeReg[0]);
|
||
|
||
// 读取运行速度(D310,2个寄存器,浮点数,单位:mm/min)
|
||
ushort[] runSpeedReg = _modbusMaster.ReadHoldingRegisters(slaveId, 310, 2);
|
||
double runSpeed = ConvertRegistersToDouble(runSpeedReg);
|
||
runSpeed = ModbusUshortToFloat(runSpeedReg[1], runSpeedReg[0]);
|
||
|
||
// 使用反射获取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);
|
||
|
||
if (sampleDataTableField == null || currentSampleCountField == null)
|
||
{
|
||
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;
|
||
currentSampleCountField.SetValue(form2Instance, currentSampleCount);
|
||
|
||
// 重新初始化DataGridView以显示新列
|
||
this.Invoke(new Action(() =>
|
||
{
|
||
var initMethod = form2Instance.GetType()
|
||
.GetMethod("InitializeDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
initMethod?.Invoke(form2Instance, null);
|
||
}));
|
||
|
||
// 重新获取DataTable(可能已重新绑定)
|
||
dataTable = sampleDataTableField.GetValue(form2Instance) as DataTable;
|
||
}
|
||
|
||
// 清空所有数据,重新构建
|
||
dataTable.Clear();
|
||
|
||
int totalSamples = form2SampleDataList.Count;
|
||
|
||
// 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);
|
||
|
||
// 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);
|
||
|
||
// 3. 液体吸收量行 (%)
|
||
DataRow absorptionRow = dataTable.NewRow();
|
||
absorptionRow["序号"] = "液体吸收量(%)";
|
||
double?[] absorptions = new double?[totalSamples];
|
||
for (int i = 0; i < totalSamples; i++)
|
||
{
|
||
if (TryCalculateAbsorption(form2SampleDataList[i].InitialWeight, form2SampleDataList[i].AfterWeight, out double absorption))
|
||
{
|
||
absorptions[i] = absorption;
|
||
absorptionRow[$"试样{i + 1}"] = absorption;
|
||
}
|
||
else
|
||
{
|
||
absorptionRow[$"试样{i + 1}"] = DBNull.Value;
|
||
}
|
||
}
|
||
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:0.##}mm/min";
|
||
}
|
||
}
|
||
for (int i = totalSamples + 1; i <= currentSampleCount; i++)
|
||
{
|
||
if (dataTable.Columns.Contains($"试样{i}"))
|
||
{
|
||
runSpeedRow[$"试样{i}"] = DBNull.Value;
|
||
}
|
||
}
|
||
dataTable.Rows.Add(runSpeedRow);
|
||
|
||
AddAbsorptionStatRows(dataTable, absorptions, totalSamples);
|
||
|
||
// 刷新显示
|
||
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数据读取成功:试样{newSampleIndex},初始重量={initialWeight:F2}g,浸润后重量={afterWeight:F2}g,累计{totalSamples}个试样");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取Form2数据失败:{ex.Message}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <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];
|
||
double result = value / 100.0;
|
||
|
||
// 检查NaN
|
||
if (double.IsNaN(result) || double.IsInfinity(result))
|
||
{
|
||
return 0.0;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将寄存器数据转换为int(用于时间、速度等整数数据)
|
||
/// </summary>
|
||
private int ConvertRegistersToInt(ushort[] registers)
|
||
{
|
||
if (registers == null || registers.Length < 2) return 0;
|
||
|
||
// 组合两个寄存器为32位整数(高位在前)
|
||
return (registers[0] << 16) | registers[1];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将单个寄存器数据转换为int(用于时间等单寄存器整数数据)
|
||
/// </summary>
|
||
private int ConvertSingleRegisterToInt(ushort register)
|
||
{
|
||
return register;
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
}
|
||
|
||
// 所有列都有数据,返回下一个索引
|
||
return currentSampleCount;
|
||
}
|
||
|
||
/// <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";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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++)
|
||
{
|
||
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 (TryCalculateAbsorption(initialWeights[i], afterWeights[i], out double absorption))
|
||
{
|
||
absorptions[i] = absorption;
|
||
absorptionRow[$"试样{i + 1}"] = absorption;
|
||
}
|
||
else
|
||
{
|
||
absorptionRow[$"试样{i + 1}"] = DBNull.Value;
|
||
}
|
||
}
|
||
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);
|
||
|
||
AddAbsorptionStatRows(dataTable, absorptions, count);
|
||
|
||
dataTable.AcceptChanges();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取Form3数据(液体芯吸速率)
|
||
/// PLC地址:D200 - 吸水时间(s),D454 - 吸芯高度(mm),每次测试读取1个试样(各1个寄存器用于时间,2个寄存器用于高度)
|
||
///
|
||
/// 数据结构说明:
|
||
/// - 每组试样包含3次测试数据(试样1_1, 试样1_2, 试样1_3)
|
||
/// - 每5个试样为一组,显示5列
|
||
/// - 平均芯吸速率:计算每个试样3次测试的平均值,合并显示在该试样的第3列
|
||
/// - 标准偏差:计算每组5个试样的标准偏差,合并显示在第1列
|
||
/// - 试样数量动态增长
|
||
///
|
||
/// 信号量:M310
|
||
/// </summary>
|
||
private void ReadForm3Data(byte slaveId)
|
||
{
|
||
try
|
||
{
|
||
// 读取吸水时间(D200,1个寄存器,整数,单位:秒)
|
||
ushort[] timeRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 212, 2);
|
||
|
||
// 读取吸芯高度(D454,2个寄存器,浮点数,单位:mm,精度0.01mm)
|
||
ushort[] heightRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 454, 2);
|
||
|
||
// 使用反射获取Form3的私有字段
|
||
var sampleDataTableField = form3Instance.GetType()
|
||
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
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)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("DataTable为空或没有行");
|
||
return;
|
||
}
|
||
|
||
// 将寄存器值转换为实际数据
|
||
// 吸水时间:单个寄存器,整数,单位:秒
|
||
int wickingTime = timeRegisters[0];
|
||
//wickingTime = ModbusUshortToInt(timeRegisters[1], timeRegisters[0]);
|
||
// 吸芯高度:2个寄存器,浮点数,单位:mm,精度0.01mm
|
||
double wickingHeight = ConvertRegistersToDouble(heightRegisters);
|
||
wickingHeight = ModbusUshortToFloat(heightRegisters[1], heightRegisters[0]);
|
||
// 检查数据有效性
|
||
if (wickingTime <= 0 || double.IsNaN(wickingHeight) || double.IsInfinity(wickingHeight))
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("读取的数据无效,跳过本次读取");
|
||
return;
|
||
}
|
||
|
||
// 确定当前要填充的位置(试样索引和测试次数)
|
||
var position = GetNextForm3Position(dataTable, currentSampleCount, isVerticalLayout);
|
||
int sampleIndex = position.Item1; // 试样索引(1-based)
|
||
int testIndex = position.Item2; // 测试次数(1-3)
|
||
|
||
// Form3限制:最多5组试样(15次测试)
|
||
const int MAX_FORM3_SAMPLES = 5;
|
||
|
||
// 如果超过最大试样数量,显示警告并停止
|
||
if (sampleIndex > MAX_FORM3_SAMPLES)
|
||
{
|
||
this.Invoke(new Action(() =>
|
||
{
|
||
MessageBox.Show($"Form3已达到最大试样数量限制({MAX_FORM3_SAMPLES}组,共{MAX_FORM3_SAMPLES * 3}次测试)\n" +
|
||
"无法继续添加数据。",
|
||
"数据已满", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
}));
|
||
return;
|
||
}
|
||
|
||
// 如果需要扩展试样数量(动态增长,但不超过最大值)
|
||
if (sampleIndex > currentSampleCount && sampleIndex <= MAX_FORM3_SAMPLES)
|
||
{
|
||
currentSampleCount = sampleIndex;
|
||
currentSampleCountField.SetValue(form3Instance, currentSampleCount);
|
||
|
||
// 重新初始化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;
|
||
}
|
||
|
||
// 根据布局方式填充数据
|
||
if (isVerticalLayout)
|
||
{
|
||
// 纵向布局:第1行是吸水时间,第2行是吸芯高度
|
||
DataRow timeRow = FindDataRow(dataTable, FORM3_ROW_WICKING_TIME);
|
||
DataRow heightRow = FindDataRow(dataTable, FORM3_ROW_WICKING_HEIGHT);
|
||
if (timeRow != null && heightRow != null)
|
||
{
|
||
string columnName = $"试样{sampleIndex}_{testIndex}";
|
||
|
||
// 填充吸水时间
|
||
if (timeRow.Table.Columns.Contains(columnName))
|
||
{
|
||
timeRow[columnName] = wickingTime;
|
||
}
|
||
|
||
// 填充吸芯高度
|
||
if (heightRow.Table.Columns.Contains(columnName))
|
||
{
|
||
heightRow[columnName] = wickingHeight;
|
||
}
|
||
}
|
||
}
|
||
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(FORM3_ROW_WICKING_TIME))
|
||
{
|
||
row[FORM3_ROW_WICKING_TIME] = wickingTime;
|
||
}
|
||
|
||
// 填充吸芯高度列
|
||
if (row.Table.Columns.Contains(FORM3_ROW_WICKING_HEIGHT))
|
||
{
|
||
row[FORM3_ROW_WICKING_HEIGHT] = 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);
|
||
}));
|
||
|
||
System.Diagnostics.Debug.WriteLine($"Form3数据读取成功 - 试样{sampleIndex}第{testIndex}次测试,吸水时间:{wickingTime:F2}秒,吸芯高度:{wickingHeight:F2}mm");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取Form3数据失败:{ex.Message}");
|
||
HandleModbusDisconnected(ex.Message);
|
||
}
|
||
}
|
||
|
||
private float ModbusUshortToFloat(ushort highReg, ushort lowReg)
|
||
{
|
||
byte[] floatBytes = new byte[4];
|
||
// 高寄存器→高2字节,低寄存器→低2字节(与读取时的顺序一致)
|
||
floatBytes[0] = (byte)(highReg >> 8);
|
||
floatBytes[1] = (byte)highReg;
|
||
floatBytes[2] = (byte)(lowReg >> 8);
|
||
floatBytes[3] = (byte)lowReg;
|
||
|
||
if (BitConverter.IsLittleEndian)
|
||
{
|
||
Array.Reverse(floatBytes);
|
||
}
|
||
|
||
return BitConverter.ToSingle(floatBytes, 0);
|
||
}
|
||
|
||
private int ModbusUshortToInt(ushort highReg, ushort lowReg)
|
||
{
|
||
byte[] floatBytes = new byte[4];
|
||
// 高寄存器→高2字节,低寄存器→低2字节(与读取时的顺序一致)
|
||
floatBytes[0] = (byte)(highReg >> 8);
|
||
floatBytes[1] = (byte)highReg;
|
||
floatBytes[2] = (byte)(lowReg >> 8);
|
||
floatBytes[3] = (byte)lowReg;
|
||
|
||
if (BitConverter.IsLittleEndian)
|
||
{
|
||
Array.Reverse(floatBytes);
|
||
}
|
||
|
||
return BitConverter.ToInt32(floatBytes, 0);
|
||
}
|
||
/// <summary>
|
||
/// 获取Form3下一个要填充的位置(试样索引和测试次数)
|
||
/// 返回:(试样索引, 测试次数) - 都是1-based
|
||
/// </summary>
|
||
private Tuple<int, int> GetNextForm3Position(DataTable dataTable, int currentSampleCount, bool isVerticalLayout)
|
||
{
|
||
if (isVerticalLayout)
|
||
{
|
||
// 纵向布局:第1行是吸水时间行
|
||
DataRow timeRow = FindDataRow(dataTable, FORM3_ROW_WICKING_TIME);
|
||
if (timeRow != null)
|
||
{
|
||
// 遍历所有试样的所有测试次数,找到第一个空位
|
||
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 (IsEmptyForm3Value(value))
|
||
{
|
||
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(FORM3_ROW_WICKING_TIME))
|
||
{
|
||
var value = row[FORM3_ROW_WICKING_TIME];
|
||
if (IsEmptyForm3Value(value))
|
||
{
|
||
return Tuple.Create(i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 所有位置都已填充,返回下一个试样的第1次测试
|
||
return Tuple.Create(currentSampleCount + 1, 1);
|
||
}
|
||
|
||
// 默认返回第1个试样的第1次测试
|
||
return Tuple.Create(1, 1);
|
||
}
|
||
|
||
private DataRow FindDataRow(DataTable dataTable, string rowName)
|
||
{
|
||
if (dataTable == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
foreach (DataRow row in dataTable.Rows)
|
||
{
|
||
if (string.Equals(row["序号"]?.ToString(), rowName, StringComparison.Ordinal))
|
||
{
|
||
return row;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private bool IsEmptyForm3Value(object value)
|
||
{
|
||
if (value == null || value == DBNull.Value)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (double.TryParse(value.ToString(), out double number))
|
||
{
|
||
return Math.Abs(number) < 0.001;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算标准偏差
|
||
/// </summary>
|
||
private double CalculateStandardDeviation(double[] values)
|
||
{
|
||
if (values == null || values.Length <= 1) return 0;
|
||
|
||
// 过滤掉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;
|
||
}
|
||
|
||
private bool TryCalculateAbsorption(double initialWeight, double afterWeight, out double absorption)
|
||
{
|
||
absorption = 0;
|
||
if (initialWeight <= 0 ||
|
||
double.IsNaN(initialWeight) ||
|
||
double.IsInfinity(initialWeight) ||
|
||
double.IsNaN(afterWeight) ||
|
||
double.IsInfinity(afterWeight))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
absorption = ((afterWeight - initialWeight) / initialWeight) * 100;
|
||
return !double.IsNaN(absorption) && !double.IsInfinity(absorption);
|
||
}
|
||
|
||
private IEnumerable<(int Start, int End)> GetSampleGroups(int sampleCount)
|
||
{
|
||
for (int start = 1; start <= sampleCount; start += 5)
|
||
{
|
||
yield return (start, Math.Min(start + 4, sampleCount));
|
||
}
|
||
}
|
||
|
||
private bool IsAbsorptionStatRow(string rowName)
|
||
{
|
||
return !string.IsNullOrEmpty(rowName) &&
|
||
(rowName.StartsWith("液体吸收量平均值(%)") ||
|
||
rowName.StartsWith("液体吸收量最大值(%)") ||
|
||
rowName.StartsWith("标准偏差"));
|
||
}
|
||
|
||
private void AddAbsorptionStatRows(DataTable dataTable, double?[] absorptions, int sampleCount)
|
||
{
|
||
foreach (var group in GetSampleGroups(sampleCount))
|
||
{
|
||
double[] groupValues = Enumerable.Range(group.Start, group.End - group.Start + 1)
|
||
.Where(index => index - 1 < absorptions.Length)
|
||
.Select(index => absorptions[index - 1])
|
||
.Where(value => value.HasValue)
|
||
.Select(value => value.Value)
|
||
.ToArray();
|
||
|
||
AddAbsorptionStatRow(dataTable, $"液体吸收量平均值(%) 试样{group.Start}-{group.End}",
|
||
group.Start, sampleCount, groupValues.Length > 0 ? (object)groupValues.Average() : DBNull.Value);
|
||
|
||
AddAbsorptionStatRow(dataTable, $"液体吸收量最大值(%) 试样{group.Start}-{group.End}",
|
||
group.Start, sampleCount, groupValues.Length > 0 ? (object)groupValues.Max() : DBNull.Value);
|
||
|
||
AddAbsorptionStatRow(dataTable, $"标准偏差 试样{group.Start}-{group.End}",
|
||
group.Start, sampleCount, groupValues.Length > 1 ? (object)CalculateStandardDeviation(groupValues) : DBNull.Value);
|
||
}
|
||
}
|
||
|
||
private void AddAbsorptionStatRow(DataTable dataTable, string rowName, int startSample, int sampleCount, object statValue)
|
||
{
|
||
DataRow row = dataTable.NewRow();
|
||
row["序号"] = rowName;
|
||
|
||
for (int i = 1; i <= sampleCount; i++)
|
||
{
|
||
if (dataTable.Columns.Contains($"试样{i}"))
|
||
{
|
||
row[$"试样{i}"] = i == startSample ? statValue : DBNull.Value;
|
||
}
|
||
}
|
||
|
||
dataTable.Rows.Add(row);
|
||
}
|
||
|
||
private int FindStatStartSample(DataRow row, int sampleCount)
|
||
{
|
||
for (int i = 1; i <= sampleCount; i++)
|
||
{
|
||
string columnName = $"试样{i}";
|
||
if (row.Table.Columns.Contains(columnName) &&
|
||
row[columnName] != null &&
|
||
row[columnName] != DBNull.Value)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
|
||
string rowName = row["序号"]?.ToString();
|
||
int parsedStart = ParseStatStartSample(rowName, sampleCount);
|
||
if (parsedStart > 0)
|
||
{
|
||
return parsedStart;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
private int ParseStatStartSample(string rowName, int sampleCount)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(rowName))
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int markerIndex = rowName.IndexOf("试样", StringComparison.Ordinal);
|
||
if (markerIndex < 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int numberStart = markerIndex + "试样".Length;
|
||
int numberEnd = numberStart;
|
||
while (numberEnd < rowName.Length && char.IsDigit(rowName[numberEnd]))
|
||
{
|
||
numberEnd++;
|
||
}
|
||
|
||
if (numberEnd == numberStart)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
string startText = rowName.Substring(numberStart, numberEnd - numberStart);
|
||
if (int.TryParse(startText, out int startSample) &&
|
||
startSample >= 1 &&
|
||
startSample <= sampleCount)
|
||
{
|
||
return startSample;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/// <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;
|
||
panelBottom.Resize += (s, e) => CenterBottomButtons();
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
CenterBottomButtons();
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动态居中底部按钮
|
||
/// </summary>
|
||
private void CenterBottomButtons()
|
||
{
|
||
// 收集所有可见的按钮
|
||
List<Button> visibleButtons = new List<Button>();
|
||
|
||
if (buttonToggleLayout.Visible) visibleButtons.Add(buttonToggleLayout);
|
||
if (button5.Visible) visibleButtons.Add(button5);
|
||
if (buttonPrint.Visible) visibleButtons.Add(buttonPrint);
|
||
if (buttonExport.Visible) visibleButtons.Add(buttonExport);
|
||
|
||
if (visibleButtons.Count == 0) return;
|
||
|
||
// 按钮参数
|
||
int buttonGap = 20; // 按钮之间的间距
|
||
int buttonY = (panelBottom.ClientSize.Height - visibleButtons[0].Height) / 2;
|
||
|
||
// 计算总宽度
|
||
int totalWidth = visibleButtons.Sum(button => button.Width) + (visibleButtons.Count - 1) * buttonGap;
|
||
|
||
// 计算起始X坐标(居中)
|
||
int startX = (panelBottom.Width - totalWidth) / 2;
|
||
if (startX < panelBottom.Padding.Left)
|
||
{
|
||
startX = panelBottom.Padding.Left;
|
||
}
|
||
|
||
// 依次设置每个按钮的位置
|
||
int currentX = startX;
|
||
for (int i = 0; i < visibleButtons.Count; i++)
|
||
{
|
||
visibleButtons[i].Location = new Point(currentX, buttonY);
|
||
currentX += visibleButtons[i].Width + buttonGap;
|
||
}
|
||
}
|
||
|
||
private void ButtonPrint_Click(object sender, EventArgs e)
|
||
{
|
||
MessageBox.Show("打印功能开发中", "提示");
|
||
}
|
||
|
||
private void ButtonExport_Click(object sender, EventArgs e)
|
||
{
|
||
// 显示导出选项对话框
|
||
var exportOptions = ShowExportOptionsDialog();
|
||
if (exportOptions == null)
|
||
{
|
||
// 用户取消了导出
|
||
return;
|
||
}
|
||
|
||
SaveFileDialog saveFileDialog = new SaveFileDialog
|
||
{
|
||
Filter = "Excel 文件 (*.xlsx)|*.xlsx",
|
||
FileName = $"测试报告_{DateTime.Now:yyyyMMdd_HHmmss}",
|
||
Title = "导出整合报告"
|
||
};
|
||
|
||
if (saveFileDialog.ShowDialog() == DialogResult.OK)
|
||
{
|
||
textBox8.Text = saveFileDialog.FileName;
|
||
ExportIntegratedReport(saveFileDialog.FileName, exportOptions);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示导出选项对话框
|
||
/// </summary>
|
||
private ExportOptions ShowExportOptionsDialog()
|
||
{
|
||
// 创建对话框
|
||
Form optionsDialog = new Form
|
||
{
|
||
Text = "选择导出内容",
|
||
Width = 350,
|
||
Height = 250,
|
||
StartPosition = FormStartPosition.CenterParent,
|
||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||
MaximizeBox = false,
|
||
MinimizeBox = false
|
||
};
|
||
|
||
Label label = new Label
|
||
{
|
||
Text = "请选择要导出的测试项目:",
|
||
Location = new Point(20, 20),
|
||
AutoSize = true,
|
||
Font = new Font("微软雅黑", 10F)
|
||
};
|
||
|
||
CheckBox chkAbsorptionTime = new CheckBox
|
||
{
|
||
Text = "液体吸收时间",
|
||
Location = new Point(40, 60),
|
||
AutoSize = true,
|
||
Checked = true,
|
||
Font = new Font("微软雅黑", 9F)
|
||
};
|
||
|
||
CheckBox chkAbsorptionAmount = new CheckBox
|
||
{
|
||
Text = "液体吸收量",
|
||
Location = new Point(40, 90),
|
||
AutoSize = true,
|
||
Checked = true,
|
||
Font = new Font("微软雅黑", 9F)
|
||
};
|
||
|
||
CheckBox chkWickingRate = new CheckBox
|
||
{
|
||
Text = "液体芯吸速率",
|
||
Location = new Point(40, 120),
|
||
AutoSize = true,
|
||
Checked = true,
|
||
Font = new Font("微软雅黑", 9F)
|
||
};
|
||
|
||
Button btnOK = new Button
|
||
{
|
||
Text = "确定",
|
||
DialogResult = DialogResult.OK,
|
||
Location = new Point(100, 160),
|
||
Width = 80,
|
||
Height = 30
|
||
};
|
||
|
||
Button btnCancel = new Button
|
||
{
|
||
Text = "取消",
|
||
DialogResult = DialogResult.Cancel,
|
||
Location = new Point(190, 160),
|
||
Width = 80,
|
||
Height = 30
|
||
};
|
||
|
||
optionsDialog.Controls.AddRange(new Control[] {
|
||
label,
|
||
chkAbsorptionTime,
|
||
chkAbsorptionAmount,
|
||
chkWickingRate,
|
||
btnOK,
|
||
btnCancel
|
||
});
|
||
optionsDialog.AcceptButton = btnOK;
|
||
optionsDialog.CancelButton = btnCancel;
|
||
|
||
// 显示对话框
|
||
if (optionsDialog.ShowDialog(this) == DialogResult.OK)
|
||
{
|
||
// 检查是否至少选择了一项
|
||
if (!chkAbsorptionTime.Checked && !chkAbsorptionAmount.Checked && !chkWickingRate.Checked)
|
||
{
|
||
MessageBox.Show("请至少选择一个导出项目!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return null;
|
||
}
|
||
|
||
return new ExportOptions
|
||
{
|
||
IncludeAbsorptionTime = chkAbsorptionTime.Checked,
|
||
IncludeAbsorptionAmount = chkAbsorptionAmount.Checked,
|
||
IncludeWickingRate = chkWickingRate.Checked
|
||
};
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出选项类
|
||
/// </summary>
|
||
private class ExportOptions
|
||
{
|
||
public bool IncludeAbsorptionTime { get; set; }
|
||
public bool IncludeAbsorptionAmount { get; set; }
|
||
public bool IncludeWickingRate { get; set; }
|
||
}
|
||
|
||
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();
|
||
CenterBottomButtons();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新切换按钮的文本
|
||
/// </summary>
|
||
private void UpdateToggleButtonText()
|
||
{
|
||
if (form3Instance != null)
|
||
{
|
||
// 使用公共方法获取布局状态
|
||
bool isVertical = form3Instance.IsVerticalLayout();
|
||
buttonToggleLayout.Text = isVertical ? "🔄 横向布局" : "🔄 纵向布局";
|
||
CenterBottomButtons();
|
||
}
|
||
}
|
||
|
||
private void ExportIntegratedReport(string filePath, ExportOptions options)
|
||
{
|
||
try
|
||
{
|
||
IWorkbook workbook = new XSSFWorkbook();
|
||
|
||
// 创建单个整合的工作表
|
||
CreateIntegratedSheet(workbook, options, filePath);
|
||
|
||
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, ExportOptions options, string filePath)
|
||
{
|
||
ISheet sheet = workbook.CreateSheet("吸收性测试报告");
|
||
var styles = CreateReportStyles(workbook);
|
||
|
||
// 获取三个表单的数据
|
||
int form1SampleCount = GetCurrentSampleCount(form1Instance, 5);
|
||
int form2SampleCount = GetCurrentSampleCount(form2Instance, 5);
|
||
int form3SampleCount = GetCurrentSampleCount(form3Instance, 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 maxDataColumn = 0;
|
||
if (options.IncludeAbsorptionTime) maxDataColumn = Math.Max(maxDataColumn, form1SampleCount);
|
||
if (options.IncludeAbsorptionAmount) maxDataColumn = Math.Max(maxDataColumn, form2SampleCount);
|
||
if (options.IncludeWickingRate)
|
||
{
|
||
maxDataColumn = Math.Max(maxDataColumn,
|
||
dataTable3 != null && dataTable3.Columns.Contains(FORM3_ROW_WICKING_TIME) ? 5 : form3SampleCount * 3);
|
||
}
|
||
int lastColumn = Math.Max(9, maxDataColumn);
|
||
|
||
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 <= lastColumn; i++)
|
||
{
|
||
titleRow.CreateCell(i).CellStyle = styles.titleStyle;
|
||
}
|
||
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, lastColumn));
|
||
|
||
currentRow++; // 空行
|
||
|
||
// 2. 创建信息输入区域
|
||
CreateInfoSection(sheet, ref currentRow, lastColumn, styles, filePath);
|
||
|
||
currentRow++; // 空行
|
||
|
||
// 3. 创建液体吸收时间部分(根据选项)
|
||
if (options.IncludeAbsorptionTime)
|
||
{
|
||
CreateForm1Section(sheet, ref currentRow, form1SampleCount, dataTable1, styles);
|
||
currentRow++; // 空行
|
||
}
|
||
|
||
// 4. 创建液体吸收量部分(根据选项)
|
||
if (options.IncludeAbsorptionAmount)
|
||
{
|
||
CreateForm2Section(sheet, ref currentRow, form2SampleCount, dataTable2, styles);
|
||
currentRow++; // 空行
|
||
}
|
||
|
||
// 5. 创建液体芯吸速率部分(根据选项)
|
||
if (options.IncludeWickingRate)
|
||
{
|
||
CreateForm3Section(sheet, ref currentRow, form3SampleCount, dataTable3, styles);
|
||
}
|
||
|
||
// 设置列宽
|
||
sheet.SetColumnWidth(0, 20 * 256);
|
||
for (int i = 1; i <= lastColumn; i++)
|
||
{
|
||
sheet.SetColumnWidth(i, 12 * 256);
|
||
}
|
||
}
|
||
|
||
private int GetCurrentSampleCount(Form form, int defaultValue)
|
||
{
|
||
if (form == null)
|
||
{
|
||
return defaultValue;
|
||
}
|
||
|
||
var sampleCountField = form.GetType()
|
||
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
if (sampleCountField == null)
|
||
{
|
||
return defaultValue;
|
||
}
|
||
|
||
object value = sampleCountField.GetValue(form);
|
||
return value is int count && count > 0 ? count : defaultValue;
|
||
}
|
||
|
||
private void CreateInfoSection(ISheet sheet, ref int currentRow, int sampleCount,
|
||
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles,
|
||
string filePath)
|
||
{
|
||
// 从界面获取实际数据
|
||
string sampleName = textBox1.Text.Trim();
|
||
string materialCode = textBox2.Text.Trim();
|
||
string batchNumber = textBox3.Text.Trim();
|
||
string instrument = textBox4.Text.Trim();
|
||
string deviceNumber = textBox5.Text.Trim();
|
||
string operatorName = textBox6.Text.Trim();
|
||
string testTime = string.IsNullOrWhiteSpace(textBox7.Text)
|
||
? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||
: textBox7.Text.Trim();
|
||
string dataFile = string.IsNullOrWhiteSpace(filePath) ? textBox8.Text.Trim() : filePath;
|
||
|
||
// 第一行信息
|
||
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(string.IsNullOrEmpty(dataFile) ? "" : dataFile);
|
||
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;
|
||
|
||
// 如果是平均时间行,按每5个试样一组显示
|
||
if (rowName.Contains("平均"))
|
||
{
|
||
int startSample = FindStatStartSample(dataRow, sampleCount);
|
||
if (startSample > 0)
|
||
{
|
||
int endSample = Math.Min(startSample + 4, sampleCount);
|
||
ICell avgCell = row.CreateCell(startSample);
|
||
|
||
if (dataTable.Columns.Contains($"试样{startSample}") && dataRow[$"试样{startSample}"] != DBNull.Value)
|
||
{
|
||
if (double.TryParse(dataRow[$"试样{startSample}"].ToString(), out double value))
|
||
{
|
||
avgCell.SetCellValue(value);
|
||
}
|
||
else
|
||
{
|
||
avgCell.SetCellValue("");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
avgCell.SetCellValue("");
|
||
}
|
||
avgCell.CellStyle = styles.yellowStyle;
|
||
|
||
// 为该组的其他单元格设置样式
|
||
for (int i = startSample + 1; i <= endSample; i++)
|
||
{
|
||
row.CreateCell(i).CellStyle = styles.yellowStyle;
|
||
}
|
||
|
||
// 合并该组的单元格
|
||
if (endSample > startSample)
|
||
{
|
||
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, startSample, endSample));
|
||
}
|
||
}
|
||
}
|
||
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)
|
||
{
|
||
int startSample = FindStatStartSample(dataRow, sampleCount);
|
||
if (startSample > 0)
|
||
{
|
||
int endSample = Math.Min(startSample + 4, sampleCount);
|
||
ICell valueCell = row.CreateCell(startSample);
|
||
|
||
if (dataTable.Columns.Contains($"试样{startSample}") && dataRow[$"试样{startSample}"] != DBNull.Value)
|
||
{
|
||
object value = dataRow[$"试样{startSample}"];
|
||
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 = startSample + 1; i <= endSample; i++)
|
||
{
|
||
row.CreateCell(i).CellStyle = styles.yellowStyle;
|
||
}
|
||
|
||
// 合并该组的单元格
|
||
if (endSample > startSample)
|
||
{
|
||
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, startSample, endSample));
|
||
}
|
||
}
|
||
}
|
||
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)
|
||
{
|
||
if (dataTable != null && dataTable.Columns.Contains(FORM3_ROW_WICKING_TIME))
|
||
{
|
||
CreateForm3HorizontalSection(sheet, ref currentRow, dataTable, styles);
|
||
return;
|
||
}
|
||
|
||
// 子标题
|
||
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))
|
||
{
|
||
valueCell.SetCellValue(value);
|
||
}
|
||
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))
|
||
{
|
||
bool sourceRow = rowName == FORM3_ROW_WICKING_TIME ||
|
||
rowName == FORM3_ROW_WICKING_HEIGHT;
|
||
if (sourceRow && Math.Abs(value) < 0.001)
|
||
{
|
||
cell.SetCellValue("");
|
||
}
|
||
else
|
||
{
|
||
cell.SetCellValue(value);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 所有空数据都显示为空白
|
||
cell.SetCellValue("");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 所有空数据都显示为空白
|
||
cell.SetCellValue("");
|
||
}
|
||
cell.CellStyle = styles.yellowStyle;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CreateForm3HorizontalSection(ISheet sheet, ref int currentRow, DataTable dataTable,
|
||
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
|
||
{
|
||
string[] headers =
|
||
{
|
||
"序号",
|
||
FORM3_ROW_WICKING_TIME,
|
||
FORM3_ROW_WICKING_HEIGHT,
|
||
"芯吸速率(mm/min)",
|
||
"平均芯吸速率(mm/min)",
|
||
"标准偏差"
|
||
};
|
||
|
||
IRow subtitleRow = sheet.CreateRow(currentRow++);
|
||
subtitleRow.Height = 500;
|
||
ICell subtitleCell = subtitleRow.CreateCell(0);
|
||
subtitleCell.SetCellValue("液体芯吸速率");
|
||
subtitleCell.CellStyle = styles.headerStyle;
|
||
for (int i = 1; i < headers.Length; i++)
|
||
{
|
||
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
|
||
}
|
||
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, headers.Length - 1));
|
||
|
||
IRow headerRow = sheet.CreateRow(currentRow++);
|
||
headerRow.Height = 400;
|
||
for (int i = 0; i < headers.Length; i++)
|
||
{
|
||
ICell cell = headerRow.CreateCell(i);
|
||
cell.SetCellValue(headers[i]);
|
||
cell.CellStyle = styles.headerStyle;
|
||
}
|
||
|
||
if (dataTable == null || dataTable.Rows.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach (DataRow dataRow in dataTable.Rows)
|
||
{
|
||
IRow row = sheet.CreateRow(currentRow++);
|
||
row.Height = 380;
|
||
|
||
for (int i = 0; i < headers.Length; i++)
|
||
{
|
||
ICell cell = row.CreateCell(i);
|
||
string columnName = headers[i];
|
||
|
||
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
|
||
{
|
||
object value = dataRow[columnName];
|
||
if (double.TryParse(value.ToString(), out double numValue))
|
||
{
|
||
if ((columnName == FORM3_ROW_WICKING_TIME ||
|
||
columnName == FORM3_ROW_WICKING_HEIGHT) &&
|
||
Math.Abs(numValue) < 0.001)
|
||
{
|
||
cell.SetCellValue("");
|
||
}
|
||
else
|
||
{
|
||
cell.SetCellValue(numValue);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
cell.SetCellValue(value?.ToString() ?? "");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
cell.SetCellValue("");
|
||
}
|
||
|
||
cell.CellStyle = i == 0 ? styles.dataStyle : styles.yellowStyle;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
// 停止并释放定时器
|
||
timeUpdateTimer?.Stop();
|
||
timeUpdateTimer?.Dispose();
|
||
|
||
_readTimer?.Stop();
|
||
_readTimer?.Dispose();
|
||
|
||
_reconnectTimer?.Stop();
|
||
_reconnectTimer?.Dispose();
|
||
|
||
// 关闭Modbus TCP连接
|
||
try
|
||
{
|
||
DisposeModbusConnection();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"关闭Modbus TCP失败:{ex.Message}");
|
||
}
|
||
|
||
Application.Exit();
|
||
}
|
||
|
||
private (ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle)
|
||
CreateReportStyles(IWorkbook workbook)
|
||
{
|
||
short numberFormat = workbook.CreateDataFormat().GetFormat("0.00");
|
||
|
||
// 标题样式
|
||
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;
|
||
dataStyle.DataFormat = numberFormat;
|
||
|
||
// 黄色背景样式
|
||
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;
|
||
yellowStyle.DataFormat = numberFormat;
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|