C#中怎么使用Npoi导出Excel合并行列

发布时间:2022-02-22 18:36:07 作者:iii
来源:亿速云 阅读:574
# C#中怎么使用Npoi导出Excel合并行列

## 目录
1. [Npoi简介与环境配置](#npoi简介与环境配置)
2. [基础Excel导出操作](#基础excel导出操作)
3. [单元格合并基础](#单元格合并基础)
4. [行列合并实战技巧](#行列合并实战技巧)
5. [复杂合并场景实现](#复杂合并场景实现)
6. [样式与格式处理](#样式与格式处理)
7. [性能优化建议](#性能优化建议)
8. [常见问题解决方案](#常见问题解决方案)
9. [完整项目示例](#完整项目示例)
10. [总结与扩展](#总结与扩展)

## Npoi简介与环境配置

### 什么是Npoi
NPOI是.NET平台下的开源Excel操作库,可以读写Office 97-2003格式的Excel文件(.xls)和2007+格式的Excel文件(.xlsx),无需安装Microsoft Office。

### 主要功能特性
- 支持xls/xlsx格式
- 低内存消耗
- 丰富的API接口
- 支持公式计算
- 跨平台运行

### 安装配置
通过NuGet包管理器安装:
```bash
Install-Package NPOI
Install-Package NPOI.OOXML

基础引用

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel; // for xlsx
using NPOI.HSSF.UserModel; // for xls
using NPOI.SS.Util;

基础Excel导出操作

创建工作簿

// 创建2007+格式工作簿
IWorkbook workbook = new XSSFWorkbook();
// 创建97-2003格式工作簿
// IWorkbook workbook = new HSSFWorkbook();

ISheet sheet = workbook.CreateSheet("Sheet1");

写入数据基础

// 创建行
IRow row = sheet.CreateRow(0);
// 创建单元格并赋值
row.CreateCell(0).SetCellValue("姓名");
row.CreateCell(1).SetCellValue("年龄");

// 添加数据行
IRow dataRow = sheet.CreateRow(1);
dataRow.CreateCell(0).SetCellValue("张三");
dataRow.CreateCell(1).SetCellValue(25);

保存Excel文件

using (FileStream fs = new FileStream("test.xlsx", FileMode.Create))
{
    workbook.Write(fs);
}

单元格合并基础

合并单元格原理

Excel中的合并单元格是通过指定起始和结束的行列索引来实现的,NPOI提供了CellRangeAddress类来处理合并。

基本合并方法

// 合并第1行的第1到3列
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 2));

// 合并第1到3行的第1列
sheet.AddMergedRegion(new CellRangeAddress(0, 2, 0, 0));

合并注意事项

  1. 必须先创建单元格再合并
  2. 合并区域内的其他单元格会被覆盖
  3. 合并后只保留左上角单元格的值

基础合并示例

// 创建标题行并合并
IRow titleRow = sheet.CreateRow(0);
titleRow.CreateCell(0).SetCellValue("员工信息表");
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 3));

// 创建表头
IRow headerRow = sheet.CreateRow(1);
headerRow.CreateCell(0).SetCellValue("ID");
headerRow.CreateCell(1).SetCellValue("姓名");
// ...其他表头

行列合并实战技巧

横向合并(跨列)

// 合并多列创建分类标题
IRow categoryRow = sheet.CreateRow(2);
categoryRow.CreateCell(0).SetCellValue("基本信息");
sheet.AddMergedRegion(new CellRangeAddress(2, 2, 0, 2));
categoryRow.CreateCell(3).SetCellValue("联系方式");
sheet.AddMergedRegion(new CellRangeAddress(2, 2, 3, 5));

纵向合并(跨行)

// 合并多行创建部门分组
IRow deptRow = sheet.CreateRow(3);
deptRow.CreateCell(0).SetCellValue("研发部");
sheet.AddMergedRegion(new CellRangeAddress(3, 6, 0, 0));

// 添加研发部人员数据...

交叉合并(行列同时合并)

// 创建跨行跨列的合并区域
IRow crossRow = sheet.CreateRow(7);
crossRow.CreateCell(0).SetCellValue("年度汇总");
sheet.AddMergedRegion(new CellRangeAddress(7, 9, 0, 3));

动态合并实现

// 根据数据动态合并相同内容的单元格
Dictionary<string, List<int>> sameValueRows = new Dictionary<string, List<int>>();

// 收集需要合并的行
for (int i = 1; i <= 10; i++)
{
    IRow currentRow = sheet.GetRow(i);
    string cellValue = currentRow.GetCell(0).StringCellValue;
    
    if (!sameValueRows.ContainsKey(cellValue))
    {
        sameValueRows[cellValue] = new List<int>();
    }
    sameValueRows[cellValue].Add(i);
}

// 执行合并
foreach (var item in sameValueRows)
{
    if (item.Value.Count > 1)
    {
        int firstRow = item.Value.First();
        int lastRow = item.Value.Last();
        sheet.AddMergedRegion(new CellRangeAddress(firstRow, lastRow, 0, 0));
    }
}

复杂合并场景实现

多层表头合并

// 第一层表头
IRow header1 = sheet.CreateRow(0);
header1.CreateCell(0).SetCellValue("公司月度报表");
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 5));

// 第二层表头
IRow header2 = sheet.CreateRow(1);
header2.CreateCell(0).SetCellValue("销售数据");
sheet.AddMergedRegion(new CellRangeAddress(1, 1, 0, 2));
header2.CreateCell(3).SetCellValue("成本数据");
sheet.AddMergedRegion(new CellRangeAddress(1, 1, 3, 5));

// 第三层表头
IRow header3 = sheet.CreateRow(2);
header3.CreateCell(0).SetCellValue("产品A");
header3.CreateCell(1).SetCellValue("产品B");
// ...其他产品

分组数据合并

List<Employee> employees = GetEmployees(); // 获取员工数据

int currentRow = 3;
string lastDept = string.Empty;
int deptStartRow = 3;

foreach (var emp in employees.OrderBy(e => e.Department))
{
    if (emp.Department != lastDept)
    {
        if (currentRow > deptStartRow)
        {
            // 合并上一个部门的单元格
            sheet.AddMergedRegion(new CellRangeAddress(
                deptStartRow, currentRow - 1, 0, 0));
        }
        
        lastDept = emp.Department;
        deptStartRow = currentRow;
    }
    
    IRow row = sheet.CreateRow(currentRow);
    row.CreateCell(0).SetCellValue(emp.Department);
    row.CreateCell(1).SetCellValue(emp.Name);
    // ...其他列
    
    currentRow++;
}

// 合并最后一个部门的单元格
if (currentRow > deptStartRow)
{
    sheet.AddMergedRegion(new CellRangeAddress(
        deptStartRow, currentRow - 1, 0, 0));
}

交叉报表合并

// 创建交叉报表结构
IRow cornerRow = sheet.CreateRow(0);
cornerRow.CreateCell(0).SetCellValue("项目/月份");
sheet.AddMergedRegion(new CellRangeAddress(0, 1, 0, 0));

// 创建月份行
IRow monthRow = sheet.CreateRow(0);
for (int i = 1; i <= 12; i++)
{
    monthRow.CreateCell(i).SetCellValue($"{i}月");
}
sheet.AddMergedRegion(new CellRangeAddress(0, 0, 1, 12));

// 创建季度行
IRow quarterRow = sheet.CreateRow(1);
quarterRow.CreateCell(1).SetCellValue("Q1");
sheet.AddMergedRegion(new CellRangeAddress(1, 1, 1, 3));
// ...其他季度

样式与格式处理

基础样式设置

ICellStyle style = workbook.CreateCellStyle();
IFont font = workbook.CreateFont();
font.FontName = "微软雅黑";
font.FontHeightInPoints = 12;
font.Boldweight = (short)FontBoldWeight.BOLD;
style.SetFont(font);

// 水平居中
style.Alignment = HorizontalAlignment.Center;
// 垂直居中
style.VerticalAlignment = VerticalAlignment.Center;

// 应用样式
titleRow.GetCell(0).CellStyle = style;

合并单元格样式处理

ICellStyle mergedStyle = workbook.CreateCellStyle();
mergedStyle.Alignment = HorizontalAlignment.Center;
mergedStyle.VerticalAlignment = VerticalAlignment.Center;
mergedStyle.FillForegroundColor = IndexedColors.Grey25Percent.Index;
mergedStyle.FillPattern = FillPattern.SolidForeground;

// 设置边框
mergedStyle.BorderTop = BorderStyle.Thin;
mergedStyle.BorderBottom = BorderStyle.Thin;
mergedStyle.BorderLeft = BorderStyle.Thin;
mergedStyle.BorderRight = BorderStyle.Thin;

// 应用样式到合并区域
for (int i = 0; i <= 2; i++)
{
    IRow row = sheet.GetRow(i) ?? sheet.CreateRow(i);
    for (int j = 0; j <= 2; j++)
    {
        row.CreateCell(j).CellStyle = mergedStyle;
    }
}

自动调整列宽

// 自动调整列宽(合并单元格后特别重要)
for (int i = 0; i < 10; i++)
{
    sheet.AutoSizeColumn(i);
    // 设置最小宽度
    if (sheet.GetColumnWidth(i) < 3000)
    {
        sheet.SetColumnWidth(i, 3000);
    }
}

性能优化建议

大数据量导出优化

  1. 使用SXSSFWorkbook代替XSSFWorkbook
// 创建基于流的Workbook,限制内存中保留的行数
SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保留100行在内存中
  1. 分批次处理数据
int batchSize = 500;
for (int i = 0; i < totalCount; i += batchSize)
{
    var batchData = data.Skip(i).Take(batchSize);
    // 处理批次数据...
}

样式复用

// 预定义样式字典
Dictionary<string, ICellStyle> styleCache = new Dictionary<string, ICellStyle>();

ICellStyle GetOrCreateStyle(string styleKey)
{
    if (!styleCache.ContainsKey(styleKey))
    {
        ICellStyle style = workbook.CreateCellStyle();
        // 根据styleKey配置样式...
        styleCache[styleKey] = style;
    }
    return styleCache[styleKey];
}

减少合并操作开销

  1. 批量添加合并区域
// 收集所有合并区域
List<CellRangeAddress> mergedRegions = new List<CellRangeAddress>();

// 添加多个合并区域
mergedRegions.Add(new CellRangeAddress(0, 0, 0, 2));
mergedRegions.Add(new CellRangeAddress(1, 3, 0, 0));
// ...

// 一次性添加
foreach (var region in mergedRegions)
{
    sheet.AddMergedRegion(region);
}

常见问题解决方案

合并后内容不居中

// 确保合并区域的单元格都设置了居中样式
ICellStyle centerStyle = workbook.CreateCellStyle();
centerStyle.Alignment = HorizontalAlignment.Center;
centerStyle.VerticalAlignment = VerticalAlignment.Center;

// 应用样式到合并区域的所有单元格
for (int r = region.FirstRow; r <= region.LastRow; r++)
{
    IRow row = sheet.GetRow(r) ?? sheet.CreateRow(r);
    for (int c = region.FirstColumn; c <= region.LastColumn; c++)
    {
        row.GetCell(c).CellStyle = centerStyle;
    }
}

合并区域边框不完整

// 设置合并区域的完整边框
ICellStyle borderStyle = workbook.CreateCellStyle();
borderStyle.BorderTop = BorderStyle.Thin;
borderStyle.BorderBottom = BorderStyle.Thin;
borderStyle.BorderLeft = BorderStyle.Thin;
borderStyle.BorderRight = BorderStyle.Thin;

// 应用样式到合并区域的所有单元格
for (int r = region.FirstRow; r <= region.LastRow; r++)
{
    IRow row = sheet.GetRow(r) ?? sheet.CreateRow(r);
    for (int c = region.FirstColumn; c <= region.LastColumn; c++)
    {
        row.GetCell(c).CellStyle = borderStyle;
    }
}

合并后文件打开缓慢

  1. 减少不必要的合并区域
  2. 使用更高效的Workbook实现(SXSSF)
  3. 优化样式设置,尽量复用样式

完整项目示例

员工信息表导出

public void ExportEmployeeReport(List<Employee> employees, string filePath)
{
    // 创建工作簿
    IWorkbook workbook = new XSSFWorkbook();
    ISheet sheet = workbook.CreateSheet("员工信息");
    
    // 1. 创建标题行
    IRow titleRow = sheet.CreateRow(0);
    titleRow.CreateCell(0).SetCellValue("公司员工信息汇总表");
    sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 4));
    
    // 设置标题样式
    ICellStyle titleStyle = CreateTitleStyle(workbook);
    titleRow.GetCell(0).CellStyle = titleStyle;
    
    // 2. 创建表头
    IRow headerRow = sheet.CreateRow(1);
    string[] headers = { "部门", "工号", "姓名", "职位", "入职日期" };
    for (int i = 0; i < headers.Length; i++)
    {
        headerRow.CreateCell(i).SetCellValue(headers[i]);
    }
    
    // 3. 填充数据并处理合并
    int rowIndex = 2;
    string lastDept = string.Empty;
    int deptStartRow = 2;
    
    foreach (var emp in employees.OrderBy(e => e.Department))
    {
        IRow row = sheet.CreateRow(rowIndex);
        
        // 处理部门合并
        if (emp.Department != lastDept)
        {
            if (rowIndex > deptStartRow)
            {
                sheet.AddMergedRegion(new CellRangeAddress(
                    deptStartRow, rowIndex - 1, 0, 0));
            }
            lastDept = emp.Department;
            deptStartRow = rowIndex;
        }
        
        // 填充数据
        row.CreateCell(0).SetCellValue(emp.Department);
        row.CreateCell(1).SetCellValue(emp.EmployeeId);
        row.CreateCell(2).SetCellValue(emp.Name);
        row.CreateCell(3).SetCellValue(emp.Position);
        row.CreateCell(4).SetCellValue(emp.HireDate.ToString("yyyy-MM-dd"));
        
        rowIndex++;
    }
    
    // 合并最后一个部门
    if (rowIndex > deptStartRow)
    {
        sheet.AddMergedRegion(new CellRangeAddress(
            deptStartRow, rowIndex - 1, 0, 0));
    }
    
    // 4. 设置样式
    SetDataCellStyles(workbook, sheet, 1, rowIndex - 1, 0, 4);
    
    // 5. 自动调整列宽
    for (int i = 0; i < headers.Length; i++)
    {
        sheet.AutoSizeColumn(i);
    }
    
    // 6. 保存文件
    using (FileStream fs = new FileStream(filePath, FileMode.Create))
    {
        workbook.Write(fs);
    }
}

private ICellStyle CreateTitleStyle(IWorkbook workbook)
{
    ICellStyle style = workbook.CreateCellStyle();
    IFont font = workbook.CreateFont();
    font.FontName = "微软雅黑";
    font.FontHeightInPoints = 16;
    font.Boldweight = (short)FontBoldWeight.BOLD;
    style.SetFont(font);
    style.Alignment = HorizontalAlignment.Center;
    style.VerticalAlignment = VerticalAlignment.Center;
    return style;
}

private void SetDataCellStyles(IWorkbook workbook, ISheet sheet, 
    int startRow, int endRow, int startCol, int endCol)
{
    ICellStyle style = workbook.CreateCellStyle();
    style.Alignment = HorizontalAlignment.Center;
    style.VerticalAlignment = VerticalAlignment.Center;
    style.BorderTop = BorderStyle.Thin;
    style.BorderBottom = BorderStyle.Thin;
    style.BorderLeft = BorderStyle.Thin;
    style.BorderRight = BorderStyle.Thin;
    
    for (int r = startRow; r <= endRow; r++)
    {
        IRow row = sheet.GetRow(r);
        for (int c = startCol; c <= endCol; c++)
        {
            row.GetCell(c).CellStyle = style;
        }
    }
}

总结与扩展

核心要点回顾

  1. 使用CellRangeAddress定义合并区域
  2. 合并前确保单元格已创建
  3. 合并后样式需要统一处理
  4. 大数据量导出要考虑性能优化

扩展应用方向

  1. 与ASP.NET Core结合实现Web导出
  2. 导出模板设计与动态填充
  3. 复杂报表的生成(如财务报表) 4.
推荐阅读:
  1. C#使用NPOI生成Word文档的方法
  2. Vue+axios+WebApi+NPOI导出Excel文件实例方法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

npoi excel

上一篇:php提交数据的方式有哪些

下一篇:如何使用Java代码实现一个简单的扑克牌游戏

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》