Files
NonWovenFabric/WindowsFormsApp6/MainForm.cs
GukSang.Jin c4a9162499 更新
2026-01-06 11:18:59 +08:00

2333 lines
97 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.IO.Ports;
using System.Linq;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;
using NPOIBorderStyle = NPOI.SS.UserModel.BorderStyle;
using NPOIHorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;
using Modbus.Device;
namespace WindowsFormsApp6
{
public partial class MainForm : Form
{
private Form1 form1Instance;
private Form2 form2Instance;
private Form3 form3Instance;
private System.Windows.Forms.Timer timeUpdateTimer;
// 串口和Modbus相关
private SerialPort _serialPort;
private IModbusMaster _modbusMaster;
private System.Windows.Forms.Timer _readTimer;
// 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; }
}
public MainForm()
{
InitializeComponent();
InitializeTabControl();
InitializeEmbeddedForms();
InitializeTimeUpdate();
InitializeSerialPortAndModbus();
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>
/// 初始化串口和Modbus通信
/// </summary>
private void InitializeSerialPortAndModbus()
{
// 初始化读取定时器(信号量触发模式)
_readTimer = new System.Windows.Forms.Timer();
_readTimer.Interval = 100; // 100ms检查一次信号量
_readTimer.Tick += ReadTimer_Tick;
// 尝试打开默认串口
if (!TryOpenSerialPort("COM14"))
{
// 如果默认串口失败,弹出端口选择对话框
ShowPortSelectionDialog();
}
}
/// <summary>
/// 尝试打开指定的串口
/// </summary>
private bool TryOpenSerialPort(string portName)
{
try
{
// 如果已有串口打开,先关闭
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
_serialPort.Dispose();
}
// 配置串口参数
_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;
}
catch (Exception ex)
{
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("未选择串口,数据采集功能将不可用。");
}
}
/// <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 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}");
}
}
/// <summary>
/// 读取单个线圈状态
/// </summary>
private bool ReadCoil(byte slaveId, ushort address)
{
try
{
bool[] coils = _modbusMaster.ReadCoils(slaveId, address, 1);
return coils[0];
}
catch
{
return false;
}
}
/// <summary>
/// 写入单个线圈状态
/// </summary>
private void WriteCoil(byte slaveId, ushort address, bool value)
{
try
{
_modbusMaster.WriteSingleCoil(slaveId, address, value);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"写入线圈失败:{ex.Message}");
}
}
/// <summary>
/// 读取Form1数据液体吸收时间
/// PLC地址D200 - 时间(s)每次测试读取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}";
// 将平均值显示在该组的所有试样列(合并显示效果)
for (int i = startSample; i <= endSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = groupAvg;
}
}
// 其他列设置为空
for (int i = 1; i < startSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = DBNull.Value;
}
}
for (int i = endSample + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
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}");
}
}
/// <summary>
/// 读取Form2数据液体吸收量
/// PLC地址D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间, D410 - 运行速度
/// 信号量M252
/// 每次测试动态添加一列显示每5个试样计算平均值、最大值、标准偏差
/// </summary>
private void ReadForm2Data(byte slaveId)
{
try
{
// 读取初始重量D4202个寄存器浮点数单位g精度0.01g
ushort[] initialWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 420, 2);
double initialWeight = ConvertRegistersToDouble(initialWeightReg);
// 读取浸润后重量D4222个寄存器浮点数单位g精度0.01g
ushort[] afterWeightReg = _modbusMaster.ReadHoldingRegisters(slaveId, 422, 2);
double afterWeight = ConvertRegistersToDouble(afterWeightReg);
// 读取浸润时间D4021个寄存器整数单位
ushort[] soakTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 402, 1);
int soakTime = ConvertSingleRegisterToInt(soakTimeReg[0]);
// 读取悬挂时间D4061个寄存器整数单位
ushort[] hangTimeReg = _modbusMaster.ReadHoldingRegisters(slaveId, 406, 1);
int hangTime = ConvertSingleRegisterToInt(hangTimeReg[0]);
// 读取运行速度D4101个寄存器整数单位mm/min
ushort[] runSpeedReg = _modbusMaster.ReadHoldingRegisters(slaveId, 410, 1);
int runSpeed = ConvertSingleRegisterToInt(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 (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["序号"] = $"液体吸收量平均值(%) 试样{startSample}-{endSample}";
double avgAbsorption = groupAbsorptions.Average();
// 检查NaN
if (double.IsNaN(avgAbsorption) || double.IsInfinity(avgAbsorption))
{
avgAbsorption = 0;
}
// 将平均值显示在该组的所有试样列(合并显示效果)
for (int i = startSample; i <= endSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = avgAbsorption;
}
}
// 其他列设置为空
for (int i = 1; i < startSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = DBNull.Value;
}
}
for (int i = endSample + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
avgRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(avgRow);
// 8. 液体吸收量最大值行
DataRow maxRow = dataTable.NewRow();
maxRow["序号"] = $"液体吸收量最大值(%) 试样{startSample}-{endSample}";
double maxAbsorption = groupAbsorptions.Max();
// 检查NaN
if (double.IsNaN(maxAbsorption) || double.IsInfinity(maxAbsorption))
{
maxAbsorption = 0;
}
// 将最大值显示在该组的所有试样列(合并显示效果)
for (int i = startSample; i <= endSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
maxRow[$"试样{i}"] = maxAbsorption;
}
}
// 其他列设置为空
for (int i = 1; i < startSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
maxRow[$"试样{i}"] = DBNull.Value;
}
}
for (int i = endSample + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
maxRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(maxRow);
// 9. 标准偏差行
DataRow stdDevRow = dataTable.NewRow();
stdDevRow["序号"] = $"标准偏差 试样{startSample}-{endSample}";
double stdDev = groupAbsorptions.Count > 1 ? CalculateStandardDeviation(groupAbsorptions.ToArray()) : 0;
// 检查NaN
if (double.IsNaN(stdDev) || double.IsInfinity(stdDev))
{
stdDev = 0;
}
// 将标准偏差显示在该组的所有试样列(合并显示效果)
for (int i = startSample; i <= endSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
stdDevRow[$"试样{i}"] = stdDev;
}
}
// 其他列设置为空
for (int i = 1; i < startSample; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
stdDevRow[$"试样{i}"] = DBNull.Value;
}
}
for (int i = endSample + 1; i <= currentSampleCount; i++)
{
if (dataTable.Columns.Contains($"试样{i}"))
{
stdDevRow[$"试样{i}"] = DBNull.Value;
}
}
dataTable.Rows.Add(stdDevRow);
}
}
// 刷新显示
dataTable.AcceptChanges();
this.Invoke(new Action(() =>
{
var refreshMethod = form2Instance.GetType()
.GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
refreshMethod?.Invoke(form2Instance, null);
}));
System.Diagnostics.Debug.WriteLine($"Form2数据读取成功试样{newSampleIndex},初始重量={initialWeight:F2}g浸润后重量={afterWeight:F2}g累计{totalSamples}个试样");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form2数据失败{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 (initialWeights[i] > 0)
{
absorptions[i] = ((afterWeights[i] - initialWeights[i]) / initialWeights[i]) * 100;
// 检查NaN
if (double.IsNaN(absorptions[i]) || double.IsInfinity(absorptions[i]))
{
absorptions[i] = 0;
}
}
else
{
absorptions[i] = 0;
}
absorptionRow[$"试样{i + 1}"] = absorptions[i];
}
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);
// 计算有效试样数量(初始重量>0的试样且吸收量不是NaN
var validAbsorptions = absorptions
.Where((a, idx) => initialWeights[idx] > 0 && !double.IsNaN(a) && !double.IsInfinity(a))
.ToArray();
int validCount = validAbsorptions.Length;
// 7. 液体吸收量平均值行
DataRow avgRow = dataTable.NewRow();
avgRow["序号"] = "液体吸收量平均值(%)";
double avgAbsorption = validCount > 0 ? validAbsorptions.Average() : 0;
// 检查NaN
if (double.IsNaN(avgAbsorption) || double.IsInfinity(avgAbsorption))
{
avgAbsorption = 0;
}
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;
// 检查NaN
if (double.IsNaN(maxAbsorption) || double.IsInfinity(maxAbsorption))
{
maxAbsorption = 0;
}
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();
}
/// <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
{
// 读取吸水时间D2001个寄存器整数单位
ushort[] timeRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 1);
// 读取吸芯高度D4542个寄存器浮点数单位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 = ConvertSingleRegisterToInt(timeRegisters[0]);
// 吸芯高度2个寄存器浮点数单位mm精度0.01mm
double wickingHeight = ConvertRegistersToDouble(heightRegisters);
// 检查数据有效性
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行是吸芯高度
if (dataTable.Rows.Count >= 2)
{
DataRow timeRow = dataTable.Rows[0];
DataRow heightRow = dataTable.Rows[1];
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("吸水时间(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);
}));
System.Diagnostics.Debug.WriteLine($"Form3数据读取成功 - 试样{sampleIndex}第{testIndex}次测试,吸水时间:{wickingTime:F2}秒,吸芯高度:{wickingHeight:F2}mm");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取Form3数据失败{ex.Message}");
}
}
/// <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);
}
/// <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;
}
/// <summary>
/// 显示错误消息
/// </summary>
private void ShowErrorMsg(string message)
{
MessageBox.Show(message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 显示信息消息
/// </summary>
private void ShowInfoMsg(string message)
{
MessageBox.Show(message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void InitializeTabControl()
{
tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged;
}
private void TabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateTitleForCurrentTab();
// 根据选中的 Tab 显示/隐藏切换布局按钮
// 只有在 Form3 (Tab 2, index=2) 时显示切换按钮
buttonToggleLayout.Visible = (tabControl1.SelectedIndex == 2);
// 更新切换按钮的文本
if (tabControl1.SelectedIndex == 2 && form3Instance != null)
{
UpdateToggleButtonText();
}
}
private void UpdateTitleForCurrentTab()
{
switch (tabControl1.SelectedIndex)
{
case 0:
label1.Text = " 液体吸收时间测试报告";
break;
case 1:
label1.Text = " 液体吸收量测试报告";
break;
case 2:
label1.Text = " 液体芯吸速率测试报告";
break;
}
}
private void InitializeEmbeddedForms()
{
form1Instance = new Form1();
form1Instance.TopLevel = false;
form1Instance.FormBorderStyle = FormBorderStyle.None;
form1Instance.Dock = DockStyle.Fill;
Panel panel1 = new Panel();
panel1.Dock = DockStyle.Fill;
panel1.Controls.Add(form1Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage1.Controls.Add(panel1);
form1Instance.Show();
form2Instance = new Form2();
form2Instance.TopLevel = false;
form2Instance.FormBorderStyle = FormBorderStyle.None;
form2Instance.Dock = DockStyle.Fill;
Panel panel2 = new Panel();
panel2.Dock = DockStyle.Fill;
panel2.Controls.Add(form2Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage2.Controls.Add(panel2);
form2Instance.Show();
form3Instance = new Form3();
form3Instance.TopLevel = false;
form3Instance.FormBorderStyle = FormBorderStyle.None;
form3Instance.Dock = DockStyle.Fill;
Panel panel3 = new Panel();
panel3.Dock = DockStyle.Fill;
panel3.Controls.Add(form3Instance.Controls["tableLayoutPanel1"].Controls["panel3"]);
tabPage3.Controls.Add(panel3);
form3Instance.Show();
}
private void PanelInfo_Resize(object sender, EventArgs e)
{
CenterInfoControls();
}
private void CenterInfoControls()
{
// 每列的宽度定义
int col1LabelWidth = 75; // 样品名称/仪器
int col1InputWidth = 140;
int col2LabelWidth = 75; // 物料编码/设备编号
int col2InputWidth = 140;
int col3LabelWidth = 75; // 批号/操作人员(操作人员需要更宽)
int col3InputWidth = 140;
int col4LabelWidth = 75; // 测试时间/数据文件
int col4InputWidth = 140;
int gap = 15; // 列之间的间距
// 计算总宽度
int totalWidth = col1LabelWidth + col1InputWidth + gap +
col2LabelWidth + col2InputWidth + gap +
col3LabelWidth + col3InputWidth + gap +
col4LabelWidth + col4InputWidth;
int startX = (panelInfo.Width - totalWidth) / 2;
// 第一行 Y 坐标
int y1 = 10;
int labelOffsetY = 3; // 标签相对输入框的垂直偏移
// 第一列:样品名称
int col1X = startX;
label3.Location = new Point(col1X, y1 + labelOffsetY);
textBox1.Location = new Point(col1X + col1LabelWidth, y1);
// 第二列:物料编码
int col2X = col1X + col1LabelWidth + col1InputWidth + gap;
label4.Location = new Point(col2X, y1 + labelOffsetY);
textBox2.Location = new Point(col2X + col2LabelWidth, y1);
// 第三列:批号
int col3X = col2X + col2LabelWidth + col2InputWidth + gap;
label5.Location = new Point(col3X, y1 + labelOffsetY);
textBox3.Location = new Point(col3X + col3LabelWidth, y1);
// 第四列:测试时间
int col4X = col3X + col3LabelWidth + col3InputWidth + gap;
label9.Location = new Point(col4X, y1 + labelOffsetY);
textBox7.Location = new Point(col4X + col4LabelWidth, y1);
// 第二行 Y 坐标
int y2 = 43;
// 第一列:仪器
label6.Location = new Point(col1X, y2 + labelOffsetY);
textBox4.Location = new Point(col1X + col1LabelWidth, y2);
// 第二列:设备编号
label7.Location = new Point(col2X, y2 + labelOffsetY);
textBox5.Location = new Point(col2X + col2LabelWidth, y2);
// 第三列:操作人员
label8.Location = new Point(col3X, y2 + labelOffsetY);
textBox6.Location = new Point(col3X + col3LabelWidth, y2);
// 第四列:数据文件
label10.Location = new Point(col4X, y2 + labelOffsetY);
textBox8.Location = new Point(col4X + col4LabelWidth, y2);
}
/// <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 buttonWidth = 140;
int buttonGap = 20; // 按钮之间的间距
int buttonY = 17;
// 计算总宽度
int totalWidth = visibleButtons.Count * buttonWidth + (visibleButtons.Count - 1) * buttonGap;
// 计算起始X坐标居中
int startX = (panelBottom.Width - totalWidth) / 2;
// 依次设置每个按钮的位置
for (int i = 0; i < visibleButtons.Count; i++)
{
int x = startX + i * (buttonWidth + buttonGap);
visibleButtons[i].Location = new Point(x, buttonY);
}
}
private void ButtonPrint_Click(object sender, EventArgs e)
{
MessageBox.Show("打印功能开发中", "提示");
}
private void ButtonExport_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "Excel 文件 (*.xlsx)|*.xlsx",
FileName = $"测试报告_{DateTime.Now:yyyyMMdd_HHmmss}",
Title = "导出整合报告"
};
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
ExportIntegratedReport(saveFileDialog.FileName);
}
}
private void Button5_Click(object sender, EventArgs e)
{
switch (tabControl1.SelectedIndex)
{
case 0:
form1Instance.GenerateMockData();
break;
case 1:
form2Instance.GenerateMockData();
break;
case 2:
form3Instance.GenerateMockData();
break;
}
}
private void ButtonToggleLayout_Click(object sender, EventArgs e)
{
if (form3Instance != null)
{
// 调用 Form3 的公共切换方法
form3Instance.ToggleTableLayout();
// 更新按钮文本
UpdateToggleButtonText();
}
}
/// <summary>
/// 更新切换按钮的文本
/// </summary>
private void UpdateToggleButtonText()
{
if (form3Instance != null)
{
// 使用公共方法获取布局状态
bool isVertical = form3Instance.IsVerticalLayout();
buttonToggleLayout.Text = isVertical ? "🔄 横向布局" : "🔄 纵向布局";
}
}
private void ExportIntegratedReport(string filePath)
{
try
{
IWorkbook workbook = new XSSFWorkbook();
// 创建单个整合的工作表
CreateIntegratedSheet(workbook);
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(fs);
}
MessageBox.Show($"导出成功:{filePath}", "成功");
}
catch (Exception ex)
{
MessageBox.Show($"导出失败:{ex.Message}", "错误");
}
}
private void CreateIntegratedSheet(IWorkbook workbook)
{
ISheet sheet = workbook.CreateSheet("吸收性测试报告");
var styles = CreateReportStyles(workbook);
// 获取三个表单的数据
var sampleCountField1 = form1Instance.GetType()
.GetField("currentSampleCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
int sampleCount = sampleCountField1 != null ? (int)sampleCountField1.GetValue(form1Instance) : 5;
var dataTableField1 = form1Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable1 = dataTableField1?.GetValue(form1Instance) as DataTable;
var dataTableField2 = form2Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable2 = dataTableField2?.GetValue(form2Instance) as DataTable;
var dataTableField3 = form3Instance.GetType()
.GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
DataTable dataTable3 = dataTableField3?.GetValue(form3Instance) as DataTable;
int currentRow = 0;
// 1. 创建总标题
IRow titleRow = sheet.CreateRow(currentRow++);
titleRow.Height = 800;
ICell titleCell = titleRow.CreateCell(0);
titleCell.SetCellValue("吸收性测试报告");
titleCell.CellStyle = styles.titleStyle;
// 为合并区域的所有单元格设置样式(总标题不需要边框,但为了一致性也设置)
for (int i = 1; i <= sampleCount + 5; i++)
{
titleRow.CreateCell(i).CellStyle = styles.titleStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, sampleCount + 5));
currentRow++; // 空行
// 2. 创建信息输入区域
CreateInfoSection(sheet, ref currentRow, sampleCount, styles);
currentRow++; // 空行
// 3. 创建液体吸收时间部分
CreateForm1Section(sheet, ref currentRow, sampleCount, dataTable1, styles);
currentRow++; // 空行
// 4. 创建液体吸收量部分
CreateForm2Section(sheet, ref currentRow, sampleCount, dataTable2, styles);
currentRow++; // 空行
// 5. 创建液体芯吸速率部分
CreateForm3Section(sheet, ref currentRow, sampleCount, dataTable3, styles);
// 设置列宽
sheet.SetColumnWidth(0, 20 * 256);
for (int i = 1; i <= sampleCount * 3; i++)
{
sheet.SetColumnWidth(i, 12 * 256);
}
}
private void CreateInfoSection(ISheet sheet, ref int currentRow, int sampleCount,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 从界面获取实际数据
string sampleName = textBox1.Text.Trim();
string materialCode = textBox2.Text.Trim();
string batchNumber = textBox3.Text.Trim();
string operatorName = textBox4.Text.Trim();
string instrument = textBox5.Text.Trim();
string deviceNumber = textBox6.Text.Trim();
string testTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 第一行信息
IRow infoRow1 = sheet.CreateRow(currentRow++);
infoRow1.Height = 400;
// 样品名称
ICell cell1 = infoRow1.CreateCell(0);
cell1.SetCellValue("样品名称:");
cell1.CellStyle = styles.dataStyle;
ICell cell2 = infoRow1.CreateCell(1);
cell2.SetCellValue(string.IsNullOrEmpty(sampleName) ? "" : sampleName);
cell2.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow1.CreateCell(2).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, 2));
// 物料编码
ICell cell3 = infoRow1.CreateCell(3);
cell3.SetCellValue("物料编码:");
cell3.CellStyle = styles.dataStyle;
ICell cell4 = infoRow1.CreateCell(4);
cell4.SetCellValue(string.IsNullOrEmpty(materialCode) ? "" : materialCode);
cell4.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow1.CreateCell(5).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 4, 5));
// 批号
ICell cell5 = infoRow1.CreateCell(6);
cell5.SetCellValue("批号:");
cell5.CellStyle = styles.dataStyle;
ICell cell6 = infoRow1.CreateCell(7);
cell6.SetCellValue(string.IsNullOrEmpty(batchNumber) ? "" : batchNumber);
cell6.CellStyle = styles.yellowStyle;
// 操作人员
ICell cell7 = infoRow1.CreateCell(8);
cell7.SetCellValue("操作人员:");
cell7.CellStyle = styles.dataStyle;
ICell cell8 = infoRow1.CreateCell(9);
cell8.SetCellValue(string.IsNullOrEmpty(operatorName) ? "" : operatorName);
cell8.CellStyle = styles.yellowStyle;
// 第二行信息
IRow infoRow2 = sheet.CreateRow(currentRow++);
infoRow2.Height = 400;
// 测试时间
ICell cell21 = infoRow2.CreateCell(0);
cell21.SetCellValue("测试时间:");
cell21.CellStyle = styles.dataStyle;
ICell cell22 = infoRow2.CreateCell(1);
cell22.SetCellValue(testTime);
cell22.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow2.CreateCell(2).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, 2));
// 仪器
ICell cell23 = infoRow2.CreateCell(3);
cell23.SetCellValue("仪器:");
cell23.CellStyle = styles.dataStyle;
ICell cell24 = infoRow2.CreateCell(4);
cell24.SetCellValue(string.IsNullOrEmpty(instrument) ? "" : instrument);
cell24.CellStyle = styles.yellowStyle;
// 为合并区域的所有单元格设置样式
infoRow2.CreateCell(5).CellStyle = styles.yellowStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 4, 5));
// 设备编号
ICell cell25 = infoRow2.CreateCell(6);
cell25.SetCellValue("设备编号:");
cell25.CellStyle = styles.dataStyle;
ICell cell26 = infoRow2.CreateCell(7);
cell26.SetCellValue(string.IsNullOrEmpty(deviceNumber) ? "" : deviceNumber);
cell26.CellStyle = styles.yellowStyle;
// 数据文件
ICell cell27 = infoRow2.CreateCell(8);
cell27.SetCellValue("数据文件:");
cell27.CellStyle = styles.dataStyle;
ICell cell28 = infoRow2.CreateCell(9);
cell28.SetCellValue("");
cell28.CellStyle = styles.yellowStyle;
}
private void CreateForm1Section(ISheet sheet, ref int currentRow, int sampleCount, DataTable dataTable,
(ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle) styles)
{
// 子标题
IRow subtitleRow = sheet.CreateRow(currentRow++);
subtitleRow.Height = 500;
ICell subtitleCell = subtitleRow.CreateCell(0);
subtitleCell.SetCellValue("液体吸收时间");
subtitleCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 1; i <= sampleCount; i++)
{
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount));
// 表头
IRow headerRow = sheet.CreateRow(currentRow++);
headerRow.Height = 400;
ICell seqCell = headerRow.CreateCell(0);
seqCell.SetCellValue("序号");
seqCell.CellStyle = styles.headerStyle;
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
}
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
{
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
// 如果是平均时间行按每5个试样一组显示
if (rowName.Contains("平均"))
{
// 计算有多少组每5个试样一组
int groupCount = (int)Math.Ceiling((double)sampleCount / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
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)
{
// 按每5个试样一组显示
int groupCount = (int)Math.Ceiling((double)sampleCount / 5.0);
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++)
{
int startSample = groupIndex * 5 + 1;
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)
{
// 子标题
IRow subtitleRow = sheet.CreateRow(currentRow++);
subtitleRow.Height = 500;
ICell subtitleCell = subtitleRow.CreateCell(0);
subtitleCell.SetCellValue("液体芯吸速率");
subtitleCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 1; i <= sampleCount * 3; i++)
{
subtitleRow.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, sampleCount * 3));
// 第一行表头(纵向/横向)
IRow headerRow1 = sheet.CreateRow(currentRow);
headerRow1.Height = 400;
ICell seqCell1 = headerRow1.CreateCell(0);
seqCell1.SetCellValue("序号");
seqCell1.CellStyle = styles.headerStyle;
ICell dirCell = headerRow1.CreateCell(1);
dirCell.SetCellValue("纵向/横向(选择按钮)");
dirCell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
for (int i = 2; i <= sampleCount * 3; i++)
{
headerRow1.CreateCell(i).CellStyle = styles.headerStyle;
}
sheet.AddMergedRegion(new CellRangeAddress(currentRow, currentRow, 1, sampleCount * 3));
currentRow++;
// 第二行表头试样1-N每个3列
IRow headerRow2 = sheet.CreateRow(currentRow);
headerRow2.Height = 400;
// 序号列在第二行也需要创建并设置样式(用于合并)
headerRow2.CreateCell(0).CellStyle = styles.headerStyle;
int colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
ICell cell = headerRow2.CreateCell(colIndex);
cell.SetCellValue($"试样{i}");
cell.CellStyle = styles.headerStyle;
// 为合并区域的所有单元格设置样式
headerRow2.CreateCell(colIndex + 1).CellStyle = styles.headerStyle;
headerRow2.CreateCell(colIndex + 2).CellStyle = styles.headerStyle;
sheet.AddMergedRegion(new CellRangeAddress(currentRow, currentRow, colIndex, colIndex + 2));
colIndex += 3;
}
// 合并序号列(跨两行)
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow, 0, 0));
currentRow++;
// 第三行表头1、2、3
IRow headerRow3 = sheet.CreateRow(currentRow++);
headerRow3.Height = 400;
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
ICell cell = headerRow3.CreateCell(colIndex++);
cell.SetCellValue(j.ToString());
cell.CellStyle = styles.headerStyle;
}
}
// 数据行
if (dataTable != null && dataTable.Rows.Count > 0)
{
foreach (DataRow dataRow in dataTable.Rows)
{
string rowName = dataRow["序号"]?.ToString() ?? "";
if (string.IsNullOrEmpty(rowName)) continue;
IRow row = sheet.CreateRow(currentRow++);
row.Height = 380;
ICell nameCell = row.CreateCell(0);
nameCell.SetCellValue(rowName);
nameCell.CellStyle = styles.dataStyle;
// 判断是否是标准偏差行(需要合并为一列)
bool isStdDeviationRow = rowName.Contains("标准偏差");
if (isStdDeviationRow)
{
// 标准偏差行:合并为一列,只显示第一个值
ICell valueCell = row.CreateCell(1);
string columnName = "试样1_1";
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
{
if (double.TryParse(dataRow[columnName].ToString(), out double value))
{
if (Math.Abs(value) >= 0.001)
{
valueCell.SetCellValue(value);
}
else
{
valueCell.SetCellValue("");
}
}
else
{
valueCell.SetCellValue("");
}
}
else
{
valueCell.SetCellValue("");
}
valueCell.CellStyle = styles.yellowStyle;
// 为合并区域的其他单元格设置样式
for (int i = 2; i <= sampleCount * 3; i++)
{
row.CreateCell(i).CellStyle = styles.yellowStyle;
}
// 合并单元格
sheet.AddMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 1, sampleCount * 3));
}
else
{
// 普通数据行:显示每个试样的每次测试数据
colIndex = 1;
for (int i = 1; i <= sampleCount; i++)
{
for (int j = 1; j <= 3; j++)
{
ICell cell = row.CreateCell(colIndex++);
string columnName = $"试样{i}_{j}";
if (dataTable.Columns.Contains(columnName) && dataRow[columnName] != DBNull.Value)
{
if (double.TryParse(dataRow[columnName].ToString(), out double value))
{
if (Math.Abs(value) >= 0.001)
{
cell.SetCellValue(value);
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
}
else
{
// 所有空数据都显示为空白
cell.SetCellValue("");
}
cell.CellStyle = styles.yellowStyle;
}
}
}
}
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// 停止并释放定时器
timeUpdateTimer?.Stop();
timeUpdateTimer?.Dispose();
_readTimer?.Stop();
_readTimer?.Dispose();
// 关闭Modbus和串口
try
{
_modbusMaster?.Dispose();
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
}
_serialPort?.Dispose();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"关闭串口失败:{ex.Message}");
}
Application.Exit();
}
private (ICellStyle titleStyle, ICellStyle headerStyle, ICellStyle dataStyle, ICellStyle yellowStyle)
CreateReportStyles(IWorkbook workbook)
{
// 标题样式
ICellStyle titleStyle = workbook.CreateCellStyle();
IFont titleFont = workbook.CreateFont();
titleFont.FontHeightInPoints = 16;
titleFont.IsBold = true;
titleStyle.SetFont(titleFont);
titleStyle.Alignment = NPOIHorizontalAlignment.Center;
titleStyle.VerticalAlignment = VerticalAlignment.Center;
// 表头样式(灰色背景)
ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
SetBorders(headerStyle);
IFont headerFont = workbook.CreateFont();
headerFont.IsBold = true;
headerStyle.SetFont(headerFont);
headerStyle.Alignment = NPOIHorizontalAlignment.Center;
headerStyle.VerticalAlignment = VerticalAlignment.Center;
// 数据样式(白色背景)
ICellStyle dataStyle = workbook.CreateCellStyle();
SetBorders(dataStyle);
dataStyle.Alignment = NPOIHorizontalAlignment.Center;
dataStyle.VerticalAlignment = VerticalAlignment.Center;
// 黄色背景样式
ICellStyle yellowStyle = workbook.CreateCellStyle();
yellowStyle.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.LightYellow.Index;
yellowStyle.FillPattern = FillPattern.SolidForeground;
SetBorders(yellowStyle);
yellowStyle.Alignment = NPOIHorizontalAlignment.Center;
yellowStyle.VerticalAlignment = VerticalAlignment.Center;
return (titleStyle, headerStyle, dataStyle, yellowStyle);
}
private void SetBorders(ICellStyle style)
{
style.BorderBottom = NPOIBorderStyle.Thin;
style.BorderTop = NPOIBorderStyle.Thin;
style.BorderLeft = NPOIBorderStyle.Thin;
style.BorderRight = NPOIBorderStyle.Thin;
}
}
}