C#工具库-NPOI

news2024/9/20 10:29:58

一、简介

           NPOI是一个基于c#语言的,开源的,能够在不安装Microsoft Office组件的条件下读写Microsoft Office 的库。前身是Java的POI库,有“先贤”将其翻译成了c#语言的库,而这种由java到c#库的演变并非个例,比如DotNetty之于Netty,NetTopologySuite 之于TopologySuite

           在我的有限认知里面它算是c#里面读写excel最好的库(主观)。开源协议宽松和读写速度快。

          C#编程中引用NPOI类库的方法也很简单,通过Nuget进行包管理。项目中需要引用第三方库时,强烈建议优先使用nuget方式进行引用。

二、使用NPOI操作Excel文件

1、概述

        类库中主要的对象逻辑层级是按Book->Sheet->Row->Cell这种方式展开的,其他类比如样式,公式等都是对这个骨架的扩充。其实类似的表格都可以组织成这样的数据结构,如果我们自己有类似的显示需求,也可以按照此种方式展开设计。接下来两节2、读流程,3、写流程会以一些代码形式去串讲简单的续写逻辑。为什么会在文中放置写代码,甚至代码占了相当的篇幅,为了使读者在读的过程中在大脑中去过一遍这个库的常用方法的使用,增加对这个库的数据度,虽然这个熟悉度还是很浅的级别,但还是有其作用的,我尽量让NPOI在读者大脑中留下点儿东西。

       

2、读流程

         读excel要比写excel容易一些,这是由【读】的场景决定的,读excel的读更多的应用场景是读自己的配置文件、读程序间交换的数据。在这样的大前提下,我们不需要对excel的样式字体进行太多的关注,数据组织方式也可以是最简单的形式,类似数据库的那种关系表格。搞程序,一定要在工程和技术上做好权衡,保持足够简单,足够和简单要慢慢读,不要连起来,要细品。没必要的事尽量少做,代码多,出bug的可能就会大,但要适当保持代码的灵活性。

  2.1 初始化IWorkbook对象

         IWorkbook的子类有两个XSSFWorkbook(对应Excel2007以上版本,.xlsx)、HSSFWorkbook(对应Excel2003版本,xls)。现在估计没人再使用2003版本了,所以我们在封装自己的工具类库时,也没有什么必要再去兼容HSSFWorkbook了。

        一定要提供通过【流】方式初始化IWorkbook的方法,因为在有些用户的电脑上,办公文件是自动加密的,如果你的某些配置文件是以Excel的格式发布的,那么大概率程序就不能正常运行了。所为我建议,当读程序自身excel格式的配置文件时,处理策略是,将excel文件以嵌入资源的形式编译到dll中,使用的时候直接通过资源流的形式初始化IWorkbook。

public static IWorkbook GetWorkbook(string filePath)
{
     if (!File.Exists(filePath))
           return null;
      var file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
      if (filePath.IndexOf(".xlsx") > 0)
      {
         //2007版本
          return new XSSFWorkbook(file);
      }

      return null;
}
 public static IWorkbook GetWorkbook(Stream stream)
 {

     try
     {
         return new XSSFWorkbook(stream);      
     }
     catch (Exception ex)
     {
         Debug.Write(ex.Message);
     }
     return null;
 }
2.2 获取ISheet

    获取sheet可以根据名称获取,也可以根据索引获取,索引是基于0的;

ISheet IWorkbook.GetSheet(string name);
ISheet  IWorkbook.GetSheetAt(int index);

如果需要取出所有所有sheet,可以结合IWorkbook.NumberOfSheets属性进行遍历

for (int sheetIndex = 0; sheetIndex < workbook.NumberOfSheets; sheetIndex++)
{
    try
    {

       var sheet = workbook.GetSheetAt(sheetIndex);

    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

获取Sheet中最大使用行数,sheet.LastRowNum是基于0的

rowCount=sheet.LastRowNum + 1

获取Sheet中最大使用列数,最大使用列数没有直接提供,需要便利所有IRow的Cell数去求最大值:

/// <summary>
 /// 获取最后可用的列数
 /// </summary>
 /// <param name="sheet"></param>
 /// <returns></returns>
 public static int LastColumnNum(this ISheet sheet)
 {
     int cellCount = 0;
     for (int i = 0; i <= sheet.LastRowNum; i++)
     {
         IRow row = sheet.GetRow(i);
         if (row != null && cellCount < row.LastCellNum)
         {
             cellCount = row.LastCellNum;
         }
     }
     return cellCount;
 }
2.3 获取IRow

  sheet通过行索引获取指定的行信息,索引基于0,如果行不存在则返回null

IRow ISheet.GetRow(int rownum)
2.4 获取ICell

   row通过列索引获取指定的单元格信息,索引基于0,如果行不存在则返回null

ICell IRow.GetCell(int cellnum)

   sheet直接通过行索引和列索引去取cell

/// <summary>
/// 获取指定索引的Cell(如果索引无效则返回null)
/// </summary>
/// <param name="sheet"></param>
/// <param name="rowIndex"></param>
/// <param name="columnIndex"></param>
/// <returns></returns>
public static ICell GetCell(this ISheet sheet, int rowIndex, int columnIndex)
{
    if (rowIndex >= 0 && sheet.LastRowNum > rowIndex)
    {
        IRow row = sheet.GetRow(rowIndex);
        if (row == null)
            return null;
        if (columnIndex >= 0 && row.LastCellNum > columnIndex)
        {
            ICell cell = row.GetCell(columnIndex);
            return cell;
        }
    }
    return null; ;
}

  ICell有可能是合并的单元格,合并的单元格的有效显示值实际上是整个合并区(CellRangeAddress)的左上角索引单元格的值

public static CellRangeAddress GetCell(this ISheet sheet, int rowIndex, int columnIndex)
{
   for (int i = 0; i < sheet.NumMergedRegions; i++)
 {
     CellRangeAddress range = sheet.GetMergedRegion(i);
     if(!sheet.IsMergedRegion(range)
        continue;
     if (range.InRange(rowIndex, columnIndex))
     {
         return range;
     }
 }
    return null; ;
}

ICell取值,需要按照ICell的数据类型去处理,粗略参考:

public static object GetCellValue(this ICell cell)
 {
     if (cell == null)
         return null;
     if (cell.IsMergedCell)
     {
         var useCell = 【通过合并区域获取左上角显示单元格值】;
         cell = useCell ?? cell;
     }
     switch (cell.CellType)
     {
         case CellType.Blank: //BLANK:  
             return null;
         case CellType.Boolean: //BOOLEAN:  
             return cell.BooleanCellValue;
         case CellType.Numeric: //NUMERIC:  
             return cell.NumericCellValue;
         case CellType.String: //STRING:  
             return cell.StringCellValue;
         case CellType.Error: //ERROR:  
             return cell.ErrorCellValue;
         case CellType.Formula:
             cell.SetCellType(CellType.String);
             return cell.StringCellValue;
         default:
             return "=" + cell.CellFormula;

     }
 }

         至此,通过NPOI读取excel数据流程涉及的到的基本环节算是介绍完了,在实际项目使用过程中还需要多加尝试,封装自己顺手的方法。

3、写流程

     写流程与上述的读流程大体类似,毕竟拿到ICell才能对他进行写入嘛。写操作的难点在于去按需求去设置各种配置,如字号,字体,列宽,行高,公式等,这些就不展开说了,随需随查知道有这么格事儿就行了

3.1构造IWorkbook

直接安普通类方式初始化就行了。

 IWorkbook workBook = new XSSFWorkbook();
3.2 创建ISheet

先获取sheet,看看是否存在,如果不存在就创建一个。后面涉及到的IRow,和ICell都是这样的操作

/// <summary>
/// 获取sheet,存在返回,没有创建
/// </summary>
/// <param name="book"></param>
/// <param name="sheetName"></param>
/// <returns></returns>
public static ISheet GetSheet2(this IWorkbook book, string sheetName)
{
    return book.GetSheet(sheetName) ?? book.CreateSheet(sheetName);
}
3.3创建IRow

 通用创建Row的方法

 var useRow = sheet.GetRow(rowIndex);
 if (useRow == null)
 {
     useRow = sheet.CreateRow(rowIndex);
 }
3.4创建ICell

通用创建Cell的方法

var useCell = useRow.GetCell(columnIndex);
 if (useCell == null)
 {
     useCell = useRow.CreateCell(columnIndex);
 }

给ICell赋值的通用方式

/// <summary>
 /// 设置Excel的值信息
 /// </summary>
 /// <param name="cell"></param>
 /// <param name="value"></param>
 /// <param name="valueType">0:真实Value值,1:公式表达式;其他不合法值,按0处理</param>
 public static void SetCellValue(ICell cell,object value,int valueType)
 {
     if (cell == null)
         return;
     if (valueType == 1)
     {
         cell.SetCellFormula(value?.ToString());
     }
     else
     {
         if (value == null)
         {
             cell.SetBlank();
         }
         else if (value is string strValue)
         {
             cell.SetCellValue(strValue);
         }
         else if (value is double dValue)
         {
             cell.SetCellValue(dValue);
         }
         else if (value is bool bValue)
         {
             cell.SetCellValue(bValue);
         }
         else if (value is DateTime dtValue)
         {
             cell.SetCellValue(dtValue);
         }
         else
         {
             cell.SetCellValue(value.ToString());
         }
     }
 }

如果需要给ICell设置特殊样式的话,要通过IWorkbok创建,这个应该是为了方便管理和复用

var headCellStyle = workBook.CreateCellStyle();
  var f = workBook.CreateFont();
  f.IsBold = true;
  f.FontName = "等线";
  headCellStyle.SetFont(f);

三、注意事项

  • NPOI导出的excel文件,打开提示需要修复的问题。

     两个可能注意的点

     1、当使用stream转换成数组时,使用ToArray()方法。

     2文件保存前,如果文件存在,先将旧文件删除,再创建新文件

/// <summary>
/// 保存excel信息
/// </summary>
/// <param name="book"></param>
/// <param name="filePath"></param>
/// <param name="closeBook">保存前,是否关闭流。如果使用文件被当前excel打开,必须关闭才能写入</param>
/// <returns></returns>
public static bool Save(this IWorkbook book, string filePath, bool closeBook)
{
    if (string.IsNullOrWhiteSpace(filePath))
        throw new ArgumentNullException(nameof(filePath));
    using (MemoryStream ms = new MemoryStream())
    {
        book.Write(ms);
        if (closeBook)
        {
            book.Close();
        }
        var directory = Directory.GetParent(filePath);
        if (!directory.Exists)
        {
            directory.Create();
        }
        //不要使用OpenCreate形式,如果原始文件存在,可能出现需要修复的错误
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
        {
            byte[] data = ms.ToArray();
            fs.Write(data, 0, data.Length);
            fs.Flush();
        }
        return true;
    }
}
  • 创建单元格时,直接设置Cell的公式,然后随即取值可能不生效,需要手动触发计算

   

HSSFFormulaEvaluator e = new HSSFFormulaEvaluator(iworkbook);
 var icell = e.EvaluateInCell(icell);
  • 行高和列宽问题

          1.行高

                 Height 属性后面的值的单位是:1/20个点,所以要想得到一个点的话,需要乘以20。

                 HeightInPoints后面的单位是点,可以不用乘。

                 sheet.DefaultRowHeight = 23*20;

         2.列宽

               SetColumnWidth方法里的第二个参数要乘以256,因为这个参数的单位是1/256个字符宽          度,所以要乘以256才是一整个字符宽度。

                 sheet.SetColumnWidth(0, 15*256);

                但是这个计算出来的值并不是excel实际的类款有偏差,这个没有再深入的了解,涉及的          列宽单位应该也是PT形式的,按字符计算可能从本质上将就是不对的。

                pt:英文中的磅值,自号中的那个数字也是这个意思。

                参考资料: px,pt,em换算表 | 菜鸟教程 (runoob.com)

  • 列索引转成列名形式(A,B,C形式)这个可能在编辑公式时用的到
/// <summary>
  /// 列索引转列名,索引从0开始计算
  /// </summary>
  /// <param name="columnIndex"></param>
  /// <returns></returns>
  public static string GetColumnName(int columnIndex)
  {
      columnIndex = Math.Max(0, columnIndex);
      int calc = columnIndex + 1;
      int system = 26;
      StringBuilder sb = new StringBuilder();
      do
      {
          calc--;
          int mod = calc % system;
          int div = calc / system;
          sb.Insert(0, (char)(mod + 'A'));
          calc = div;
      } while (calc > 0);
      return sb.ToString();
  }
  • ICSharpCode.SharpZipLib报错:

          NPOI引用ICSharpCode.SharpZipLib对文件进行压缩处理,该压缩库做为很多第三方库的基础组件,所需要需要特别注意版本的一致性

          当文件进行了加密处理时,同样会报与ICSharpCode.SharpZipLib相关的错误,下图是一个原始错误形式:这个错误困扰了我很久

Wrong Local header signature: 0x65231462:   在 ICSharpCode.SharpZipLib.Zip.ZipInputStream.GetNextEntry()

           后来发现是我研发环境下的电脑会自动加密文件导致文件读取识别不了的,同样,读有损坏的文件应该也会报这个错。

  • 替代产品

        由于NPOI类库使用较广,在进行二次开发的项目时如果和其他插件引用的NPOI库冲突了,是一个很麻烦的事情,尤其是当自身开发的软件产品地位不如对方时,就需要咱们做出妥协。要么和别人保持一致的版本,要么替换一个不太常见的库。EPPlus是一个选择,当然还有其它选择,这里只是把这类问题抛砖引玉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2047946.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云动态摘要 2024-08-17

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 注册阿里云免费领云服务器_云服务器ECS_阿里云 阿里云 2024-08-14 云上试用新玩法&#xff0c;个人享300元免费额度&#xff0c;企业享660元免费额度&#xff0c;多种规格随心试 [免费体验…

恒创科技:云服务器的备份和快照哪个更好?

云服务器的备份和快照都是为了保护数据和恢复系统状态的重要手段&#xff0c;但它们有不同的应用场景和特点。在本指南中&#xff0c;我们将探讨这些数据管理方法之间的差异、它们的优点和局限性。 什么是备份? 想象一下&#xff0c;备份就是数据的“时间胶囊”。它们就像老相…

蓝盆花:神秘而迷人的自然之美

一、蓝盆花的形态特征 蓝盆花是多年生草本植物&#xff0c;植株高度通常在30 – 80厘米之间。茎直立&#xff0c;黄白色或带紫色&#xff0c;具棱&#xff0c;自基部分枝&#xff0c;节间长6 – 12厘米&#xff0c;疏或密被贴伏白色短柔毛。 蓝盆花的叶形态多样。基生叶成丛&a…

Nginx 核心配置详解

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 配置文件说明 1.1 nginx 配置文件格式说明 1.2 Nginx 主配置文件的配置指令方式&#xff1a; 1.3 主配置文件结构&#xff1a;四部分 1.4 nginx 文件作用解…

1. 数据结构——顺序表的主要操作

1. 内容 顺序表的初始化、插入、删除、按值查找、输出以及其时间复杂度的计算。 2.代码 #include<stdio.h> #include<stdlib.h> //函数结果状态代码 #define OK 1 #define OVERFLOW -2 #define ERROR 0 #define MAXSIZE 100typedef int ElemType; //顺序表每个…

Vue.js入门系列(九):表单数据处理、过滤器及常用指令

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

Linux环境开发工具【yum与vim】

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1.Linux软件包管理器yum1.1 快速使用yum 2. Linux编辑器-vim的使用2.1 vim的基本…

机器学习——XGBoost

目录 一、初识XGBoost 1. 介绍 2. 使用 XGBoost 的方法 &#xff08;1&#xff09;直接使用xgboost库自己的建模流程 &#xff08;2&#xff09;使用xgboost库中的sklearn的API 3. XGBoost的三大板块 4. 提升集成算法 5. 建模流程 二、模型常用参数 1. n_estimators …

Leetcode每日刷题之611.有效三角形的个数(C++)

1. 思路解析 根据题意我们可知&#xff0c;我们需要在指定数组中找出任意三个数并判断是否可以组成一个三角形&#xff0c;即任意两数之和大于第三个数&#xff0c;任意两数之差小于第三个数&#xff0c;如果有数组元素相同的数组&#xff0c;由于取出的元素只是数值相同而实际…

Final Cut Pro Mac(fcpx专业视频剪辑软件) 10.8 中文版安装

Final Cut Pro 是苹果公司为专业视频编辑人士量身打造的非线性编辑软件&#xff0c;以其卓越的性能和深度定制的工具集&#xff0c;在影视制作、电视广播、广告创意等多个领域占据了重要地位。凭借其对高分辨率视频的无缝支持和实时剪辑的流畅体验&#xff0c;Final Cut Pro 成…

3.3 关系模型与关系代数

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

链表专题.

Leetcode 160. 相交链表 解题思路&#xff1a; 代码实现&#xff1a;时间复杂度O(nm) 空间复杂度O(1) 我的实现方式 class Solution { public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode* pa headA, *pb headB;bool f1 false, f2 fals…

数学建模预测类—【多元线性回归】

每日名言&#xff1a;成名每在穷苦日&#xff0c;败事多因得意时 目录 文章目录 前言 二、参数估计 三、多元线性回归模型和回归系数的检验 四、预测 总结 前言 本文将根据回归建模过程来讲解多元线性回归模型&#xff0c;有关回归分析的知识以及一元线性回归的内容可以戳…

[C++][opencv]基于opencv实现photoshop算法图像旋转

【测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 //图像旋转: src为原图像&#xff0c; dst为新图像, angle为旋转角度, isClip表示是采取缩小图片的方式 int imageRotate4(InputArray src, OutputArray dst, double angle, bool isClip) {Mat input src.…

大模型应用开发基础知识

一、LangChain介绍 背景 一个应用程序通常需要多次对大语言模型写提示并对它输出的结果进行解析。因此&#xff0c;需要写很多胶水代码。而LangChain的目的使这个开发过程变得更容易。目的 LangChain是一个构建大语言模型应用的开源框架。当有人在开发LLM的复杂应用&#xff0…

ModuleNotFoundError: No module named ‘transformers_modules.chatglm-6b-v1‘

ModuleNotFoundError: No module named transformers_modules.chatglm-6b-v1 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;…

基于Java语言的充电桩平台 包含(充电桩系统 汽车充电桩云快充协议 电动自行车充电桩协议 中电联充电桩互联互通协议)

介绍 SpringBoot 框架&#xff0c;充电桩平台充电桩系统充电平台充电桩互联互通协议云快充协议1.5-1.6协议新能源汽车二轮车公交车二轮车充电-四轮车充电充电源代码充电平台源码Java源码 软件架构 软件架构说明 软件功能 小程序端&#xff1a;城市切换、附近电站、电桩详情…

【RAG实战】基于TextIn打造上市公司财务报表智能问答系统

今天介绍一个项目案例,利用大语言模型打造上市公司财务报表智能问答系统。 在当今竞争激烈的市场环境中,企业和投资者对财务信息的获取与分析要求越来越高。上市公司财务报表作为评估公司财务健康和未来发展的重要依据,提供了大量关键信息。 然而,传统的财务报表分析技术…

尚品汇-前端调用搜索实现(三十二)

目录&#xff1a; &#xff08;1&#xff09;修改web-all模块 &#xff08;2&#xff09;配置网关 &#xff08;3&#xff09;页面渲染 &#xff08;4&#xff09;面包屑处理 &#xff08;1&#xff09;修改web-all模块 修改pom.xml文件 <dependencies><depende…

QT串口通信

查看详情http://100bcw.com/qt6.htm 前言&#xff1a;如果用qt写程序作为上位机&#xff0c;然后通过和usb和下位机通信的时候&#xff0c;就需要用到qt中的串口通信了。 使用qt中的串口通信的时候需要用到的两个头文件分别为&#xff1a; #include <QtSerialPort/QSeria…