C# 上位机 Excel 报表导入导出(工控场景最佳实践)
核心用 2 个库:NPOI(无 Office 依赖,工控首选)、EPPlus(仅支持 xlsx,需授权),优先选 NPOI 适配现场无 Office 环境。✅全是可直接复用代码 + 工控适配要点,贴合 WinForm/WPF 场景
✨一、先装 NuGet 包(必看)
- NPOI(推荐):Install-Package NPOI
- EPPlus(备选):Install-Package EPPlus
✨二、核心导出(工控常用 2 种场景)
场景 1:实时采集数据导出(比如 PLC 采集的产线数据)
cs
// 通用导出方法(直接复制用)
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
public void ExportPLCDataToExcel(List<PLCData> dataList, string savePath)
{
// 1.创建工作簿(xlsx用XSSFWorkbook,xls用HSSFWorkbook)
IWorkbook workbook = Path.GetExtension(savePath).ToLower() == ".xlsx"
? new XSSFWorkbook() : new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet("产线采集数据");
// 2.写表头(工控报表固定表头,适配产线、时间、数值、状态)
IRow headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("采集时间");
headerRow.CreateCell(1).SetCellValue("产线编号");
headerRow.CreateCell(2).SetCellValue("温度(℃)");
headerRow.CreateCell(3).SetCellValue("压力(MPa)");
headerRow.CreateCell(4).SetCellValue("运行状态");
// 3.填数据(适配工控数据类型,避免空值报错)
for (int i = 0; i < dataList.Count; i++)
{
IRow row = sheet.CreateRow(i + 1);
row.CreateCell(0).SetCellValue(dataList[i].CollectTime.ToString("yyyy-MM-dd HH:mm:ss"));
row.CreateCell(1).SetCellValue(dataList[i].LineNo);
row.CreateCell(2).SetCellValue(dataList[i].Temp);
row.CreateCell(3).SetCellValue(dataList[i].Pressure);
row.CreateCell(4).SetCellValue(dataList[i].IsRunning ? "运行中" : "停机");
}
// 4.自适应列宽(工控报表必备)
for (int i = 0; i < 5; i++) sheet.AutoSizeColumn(i);
// 5.保存文件(上位机弹窗选路径用SaveFileDialog)
using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
workbook.Write(fs);
}
}
// 工控数据模型(对应PLC采集)
public class PLCData
{
public DateTime CollectTime { get; set; } //采集时间
public string LineNo { get; set; } //产线号
public decimal Temp { get; set; } //温度
public decimal Pressure { get; set; } //压力
public bool IsRunning { get; set; } //运行状态
}
场景 2:导出模板(让现场人员填写参数再导入)
cs
// 导出参数模板(比如配方参数、设备参数)
public void ExportParamTemplate(string savePath)
{
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("设备参数模板");
IRow header = sheet.CreateRow(0);
// 模板表头+备注,引导现场填写
header.CreateCell(0).SetCellValue("设备编号(必填)");
header.CreateCell(1).SetCellValue("设定转速(rpm)");
header.CreateCell(2).SetCellValue("设定阈值");
header.CreateCell(3).SetCellValue("备注");
// 锁定表头,仅允许编辑数据行(工控模板防误改)
sheet.CreateRow(1); //留一行示例行
using (FileStream fs = new FileStream(savePath, FileMode.Create))
{
workbook.Write(fs);
}
}
✨三、核心导入(工控 2 种常用场景)
场景 1:导入参数(模板导入,校验必填项)
cs
// 导入设备参数,带校验(工控必加,防止无效数据)
public List<DeviceParam> ImportDeviceParam(string filePath)
{
List<DeviceParam> paramList = new List<DeviceParam>();
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
IWorkbook workbook = Path.GetExtension(filePath).ToLower() == ".xlsx"
? new XSSFWorkbook(fs) : new HSSFWorkbook(fs);
ISheet sheet = workbook.GetSheetAt(0);
// 从第2行开始读(跳过表头)
for (int i = 1; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
if (row == null) continue;
// 工控关键:必填项校验(设备编号不能为空)
if (row.GetCell(0) == null || string.IsNullOrEmpty(row.GetCell(0).ToString().Trim()))
{
throw new Exception($"第{i+1}行:设备编号不能为空!");
}
DeviceParam param = new DeviceParam();
param.DeviceNo = row.GetCell(0).ToString().Trim();
param.SetSpeed = row.GetCell(1)?.NumericCellValue ?? 0; //空值赋默认
param.SetThreshold = row.GetCell(2)?.NumericCellValue ?? 0;
param.Remarks = row.GetCell(3)?.ToString() ?? "";
paramList.Add(param);
}
}
return paramList;
}
// 设备参数模型
public class DeviceParam
{
public string DeviceNo { get; set; }
public double SetSpeed { get; set; }
public double SetThreshold { get; set; }
public string Remarks { get; set; }
}
场景 2:导入批量数据(比如批量导入生产计划)
cs
// 批量导入,兼容空行、格式错误
public List<ProducePlan> ImportProducePlan(string filePath)
{
List<ProducePlan> plans = new List<ProducePlan>();
using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
IWorkbook workbook = new XSSFWorkbook(fs);
ISheet sheet = workbook.GetSheetAt(0);
for (int i = 1; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
if (row == null || row.GetCell(0) == null) continue;
// 格式转换容错(工控数据常出现格式错误)
var plan = new ProducePlan
{
PlanNo = row.GetCell(0).ToString().Trim(),
ProductName = row.GetCell(1)?.ToString() ?? "",
// 日期格式容错
StartDate = DateTime.TryParse(row.GetCell(2)?.ToString(), out var dt) ? dt : DateTime.Now,
TargetQty = row.GetCell(3)?.NumericCellValue ?? 0
};
plans.Add(plan);
}
}
return plans;
}
✨四、工控场景关键优化(必加)
-
无 Office 依赖:必须用 NPOI,现场工控机大概率没装 Office,EPPlus 仅 xlsx 且商用需授权
-
大文件处理:导入导出超过 1 万行,用
分批读写,避免内存溢出 -
数据校验:必填项、数据范围(比如转速不能为负)、格式校验,防止写入 PLC 出错
-
路径选择:上位机用
SaveFileDialog(导出)和OpenFileDialog(导入),贴合 WinForm/WPFcs// 快速调用弹窗选路径 public string GetSaveExcelPath() { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter = "Excel文件|*.xlsx;*.xls"; return sfd.ShowDialog() == DialogResult.OK ? sfd.FileName : ""; } -
异常捕获:包裹 try-catch,提示具体行号,方便现场排查
✨五、快速调用示例(WinForm/WPF 直接用)
cs
// 导出按钮点击事件
private void btnExport_Click(object sender, EventArgs e)
{
try
{
string path = GetSaveExcelPath();
if (string.IsNullOrEmpty(path)) return;
// plcDataList是你从PLC采集到的数据
ExportPLCDataToExcel(plcDataList, path);
MessageBox.Show("导出成功!");
}
catch (Exception ex)
{
MessageBox.Show("导出失败:" + ex.Message);
}
}
// 导入按钮点击事件
private void btnImport_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Excel文件|*.xlsx;*.xls";
if (ofd.ShowDialog() != DialogResult.OK) return;
var paramList = ImportDeviceParam(ofd.FileName);
// 导入后可直接写入PLC或保存数据库
MessageBox.Show($"导入成功,共{paramList.Count}条参数!");
}
catch (Exception ex)
{
MessageBox.Show("导入失败:" + ex.Message);
}
}