.NET Core使用NPOI導出複雜Word詳解

  • 2019 年 10 月 3 日
  • 筆記

前言:

  最近使用NPOI做了個導出Word文檔的功能,關於使用.NET Core 導出Word文檔的方式有很多。最終我為什麼選擇了NPOI來實現了這個功能,首先是NPOI是一個開源,免費且容易上手的第三方框架(並且現在已支援.NET Core,GitHub源碼地址:https://github.com/tonyqus/npoi)。因為之前使用NPOI導出Execl比較多,這次第一次使用NPOI 來導出Word文檔還真沒有什麼頭緒。首先看了下GItHub中的源碼有一個簡單Word導出的示例,然後在看了網上有很多關於NPOI導出Word文檔的案例,發現一個特點網上的好像都差不多,對於我而言網上的這些案例完全能夠實現我的這個功能,但是感覺看了網上這些案例對NPOI實例化段落,表格和設置相關樣式不太清楚(可能是因為自己笨),並且假如使用網上的方法來實現我的功能的話程式碼量會比較大,而且感覺程式碼非常的冗餘(我是一個追求程式碼簡潔的人,怎麼能夠容忍這樣的事情發生呢!),因此通過查閱了一些資料和自己的理解,把關於使用NPOI導出Word時所要涉及的一些段落,表格樣式做了相關注釋,和把段落和表格的創建實例,設置文字、字體、對齊方式都封裝了起了(為了少寫程式碼),文章末尾會附上一個完整的案例下載地址。

一、首先引入NPOI NuGet:

版本說明:

  NPOI 2.4.1 (注意不同版本可能使用的姿勢有點小差別)

程式包管理器控制台輸入一下命令安裝:

Install-Package NPOI -Version 2.4.1  

通過NuGet管理解決方案安裝:

二、導出的Work文檔內容格式樣式:

 

三、NPOI中的XWPFRun文本對象創建和屬性簡單概述:

XWPFRun文本對象說明:

  XWPFRun是段落的文本對象,先創建段落對象才能夠在段落對象的基礎上創建文本對象,並設置相關文本樣式。

如下所示:

        /// <summary>          /// 創建word文檔中的段落對象和設置段落文本的基本樣式(字體大小,字體,字體顏色,字體對齊位置)          /// </summary>          /// <param name="document">document文檔對象</param>          /// <param name="fillContent">段落第一個文本對象填充的內容</param>          /// <param name="isBold">是否加粗</param>          /// <param name="fontSize">字體大小</param>          /// <param name="fontFamily">字體</param>          /// <param name="paragraphAlign">段落排列(左對齊,居中,右對齊)</param>          /// <returns></returns>          private static XWPFParagraph ParagraphInstanceSetting(XWPFDocument document, string fillContent, bool isBold, int fontSize, string fontFamily, ParagraphAlignment paragraphAlign,)          {              XWPFParagraph paragraph = document.CreateParagraph();//創建段落對象              paragraph.Alignment = paragraphAlign;//文字顯示位置,段落排列(左對齊,居中,右對齊)                XWPFRun xwpfRun = paragraph.CreateRun();//創建段落文本對象              xwpfRun.IsBold = isBold;//文字加粗              xwpfRun.SetText(fillContent);//填充內容              xwpfRun.FontSize = fontSize;//設置文字大小              xwpfRun.SetFontFamily(fontFamily, FontCharRange.None); //設置標題樣式如:(微軟雅黑,隸書,楷體)根據自己的需求而定              return paragraph;          }

 

XWPFRun文本對象的屬性比較多,以下我簡單說明常用的幾種方式:

            XWPFParagraph paragraph = document.CreateParagraph();//創建段落對象                XWPFRun xwpfRun= paragraph.CreateRun();//創建段落文本對象              xwpfRun.IsBold = isBold;//文字加粗              xwpfRun.SetText(fillContent);//填充內容              xwpfRun.FontSize = fontSize;//設置文字大小              xwpfRun.SetFontFamily(fontFamily, FontCharRange.None); //設置標題樣式如:(微軟雅黑,隸書,楷體)根據自己的需求而定              xwpfRun.SetColor("BED4F1");//設置字體顏色--十六進位              xwpfRun.IsDoubleStrikeThrough=true;//是否顯示雙刪除線              xwpfRun.IsStrikeThrough = true;//是否顯示單刪除線              xwpfRun.SetUnderline(UnderlinePatterns.Dash);//設置下劃線,枚舉類型              xwpfRun.SetTextPosition(20);//設置文本位置(設置兩行之間的行間)              xwpfRun.AddBreak();//設置換行(</br>)              xwpfRun.AddTab();//添加tab鍵              xwpfRun.AddCarriageReturn();//添加回車鍵              xwpfRun.IsImprinted = true;//印跡(懸浮陰影),效果和浮雕類似              xwpfRun.IsItalic=true;//是否設置斜體(字體傾斜)              xwpfRun.Subscript = VerticalAlign.SUBSCRIPT;//設置下標,枚舉類型  

NPOI中關於XWPFRun屬性的更多使用技巧,請閱讀源碼:

源碼地址:https://github.com/tonyqus/npoi/blob/master/ooxml/XWPF/Usermodel/XWPFRun.cs

四、NPOI生成Word完整程式碼:

using Microsoft.AspNetCore.Hosting;  using NPOI.OpenXmlFormats.Wordprocessing;  using NPOI.XWPF.UserModel;  using System;  using System.IO;    namespace Export.Services  {      public class NpoiWordExportService      {          private static IHostingEnvironment _environment;            public NpoiWordExportService(IHostingEnvironment iEnvironment)          {              _environment = iEnvironment;          }            #region 生成word            /// <summary>          ///  生成word文檔,並保存靜態資源文件夾(wwwroot)下的SaveWordFile文件夾中          /// </summary>          /// <param name="savePath">保存路徑</param>          public bool SaveWordFile(out string savePath)          {              savePath = "";              try              {                  string currentDate = DateTime.Now.ToString("yyyyMMdd");                  string checkTime = DateTime.Now.ToString("yyyy年MM月dd日");//檢查時間                  //保存文件到靜態資源wwwroot,使用絕對路徑路徑                  var uploadPath = _environment.WebRootPath + "/SaveWordFile/" + currentDate + "/";//>>>相當於HttpContext.Current.Server.MapPath("")                     string workFileName = checkTime + "追逐時光企業員工培訓考核統計記錄表";                  string fileName = string.Format("{0}.docx", workFileName, System.Text.Encoding.UTF8);                    if (!Directory.Exists(uploadPath))                  {                      Directory.CreateDirectory(uploadPath);                  }                    //通過使用文件流,創建文件流對象,向文件流中寫入內容,並保存為Word文檔格式                  using (var stream = new FileStream(Path.Combine(uploadPath, fileName), FileMode.Create, FileAccess.Write))                  {                      //創建document文檔對象對象實例                      XWPFDocument document = new XWPFDocument();                        /**                       *這裡我通過設置公共的Word文檔中SetParagraph(段落)實例創建和段落樣式格式設置,大大減少了程式碼的冗餘,                       * 避免每使用一個段落而去創建一次段落實例和設置段落的基本樣式                       *(如下,ParagraphInstanceSetting為段落實例創建和樣式設置,後面索引表示為當前是第幾行段落,索引從0開始)                       */                      //文本標題                      document.SetParagraph(ParagraphInstanceSetting(document, workFileName, true, 19, "宋體", ParagraphAlignment.CENTER), 0);                        //TODO:這裡一行需要顯示兩個文本                      document.SetParagraph(ParagraphInstanceSetting(document, $"編號:20190927101120445887", false, 14, "宋體", ParagraphAlignment.CENTER, true, $"    檢查時間:{checkTime}"), 1);                          document.SetParagraph(ParagraphInstanceSetting(document, "登記機關:企業員工監督檢查機構", false, 14, "宋體", ParagraphAlignment.LEFT), 2);                          #region 文檔第一個表格對象實例                      //創建文檔中的表格對象實例                      XWPFTable firstXwpfTable = document.CreateTable(4, 4);//顯示的行列數rows:3行,cols:4列                      firstXwpfTable.Width = 5200;//總寬度                      firstXwpfTable.SetColumnWidth(0, 1300); /* 設置列寬 */                      firstXwpfTable.SetColumnWidth(1, 1100); /* 設置列寬 */                      firstXwpfTable.SetColumnWidth(2, 1400); /* 設置列寬 */                      firstXwpfTable.SetColumnWidth(3, 1400); /* 設置列寬 */                        //Table 表格第一行展示...後面的都是一樣,只改變GetRow中的行數                      firstXwpfTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "企業名稱", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(0).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "追逐時光", ParagraphAlignment.CENTER, 40, false));                      firstXwpfTable.GetRow(0).GetCell(2).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "企業地址", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(0).GetCell(3).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "湖南省-長沙市-嶽麓區", ParagraphAlignment.CENTER, 40, false));                        //Table 表格第二行                      firstXwpfTable.GetRow(1).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "聯繫人", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(1).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "小明同學", ParagraphAlignment.CENTER, 40, false));                      firstXwpfTable.GetRow(1).GetCell(2).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "聯繫方式", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(1).GetCell(3).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "151****0456", ParagraphAlignment.CENTER, 40, false));                          //Table 表格第三行                      firstXwpfTable.GetRow(2).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "企業許可證號", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(2).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "XXXXX-66666666", ParagraphAlignment.CENTER, 40, false));                      firstXwpfTable.GetRow(2).GetCell(2).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "檢查次數", ParagraphAlignment.CENTER, 40, true));                      firstXwpfTable.GetRow(2).GetCell(3).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, $"本年度檢查8次", ParagraphAlignment.CENTER, 40, false));                          firstXwpfTable.GetRow(3).MergeCells(0, 3);//合併3列                      firstXwpfTable.GetRow(3).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "", ParagraphAlignment.LEFT, 10, false));                        #endregion                        var checkPeopleNum = 0;//檢查人數                      var totalScore = 0;//總得分                        #region 文檔第二個表格對象實例(遍歷表格項)                      //創建文檔中的表格對象實例                      XWPFTable secoedXwpfTable = document.CreateTable(5, 4);//顯示的行列數rows:8行,cols:4列                      secoedXwpfTable.Width = 5200;//總寬度                      secoedXwpfTable.SetColumnWidth(0, 1300); /* 設置列寬 */                      secoedXwpfTable.SetColumnWidth(1, 1100); /* 設置列寬 */                      secoedXwpfTable.SetColumnWidth(2, 1400); /* 設置列寬 */                      secoedXwpfTable.SetColumnWidth(3, 1400); /* 設置列寬 */                        //遍歷表格標題                      secoedXwpfTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "員工姓名", ParagraphAlignment.CENTER, 40, true));                      secoedXwpfTable.GetRow(0).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "性別", ParagraphAlignment.CENTER, 40, true));                      secoedXwpfTable.GetRow(0).GetCell(2).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "年齡", ParagraphAlignment.CENTER, 40, true));                      secoedXwpfTable.GetRow(0).GetCell(3).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "綜合評分", ParagraphAlignment.CENTER, 40, true));                        //遍歷四條數據                      for (var i = 1; i < 5; i++)                      {                          secoedXwpfTable.GetRow(i).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "小明" + i + "", ParagraphAlignment.CENTER, 40, false));                          secoedXwpfTable.GetRow(i).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, "", ParagraphAlignment.CENTER, 40, false));                          secoedXwpfTable.GetRow(i).GetCell(2).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, 20 + i + "", ParagraphAlignment.CENTER, 40, false));                          secoedXwpfTable.GetRow(i).GetCell(3).SetParagraph(SetTableParagraphInstanceSetting(document, firstXwpfTable, 90 + i + "", ParagraphAlignment.CENTER, 40, false));                            checkPeopleNum++;                          totalScore += 90 + i;                      }                        #endregion                        #region 文檔第三個表格對象實例                      //創建文檔中的表格對象實例                      XWPFTable thirdXwpfTable = document.CreateTable(5, 4);//顯示的行列數rows:5行,cols:4列                      thirdXwpfTable.Width = 5200;//總寬度                      thirdXwpfTable.SetColumnWidth(0, 1300); /* 設置列寬 */                      thirdXwpfTable.SetColumnWidth(1, 1100); /* 設置列寬 */                      thirdXwpfTable.SetColumnWidth(2, 1400); /* 設置列寬 */                      thirdXwpfTable.SetColumnWidth(3, 1400); /* 設置列寬 */                      //Table 表格第一行,後面的合併3列(注意關於表格中行合併問題,先合併,後填充內容)                      thirdXwpfTable.GetRow(0).MergeCells(0, 3);//從第一列起,合併3列                      thirdXwpfTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "檢查內容: " +                          $"於{checkTime}下午檢查了追逐時光企業員工培訓考核並對員工的相關資訊進行了相關統計,統計結果如下:                                                                                                                                                                                                                " +                          "-------------------------------------------------------------------------------------" +                          $"共對該企業({checkPeopleNum})人進行了培訓考核,培訓考核總得分為({totalScore})分。 " + "", ParagraphAlignment.LEFT, 30, false));                          //Table 表格第二行                      thirdXwpfTable.GetRow(1).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "檢查結果: ", ParagraphAlignment.CENTER, 40, true));                      thirdXwpfTable.GetRow(1).MergeCells(1, 3);//從第二列起,合併三列                      thirdXwpfTable.GetRow(1).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "該企業非常優秀,堅持每天學習打卡,具有蓬勃向上的活力。", ParagraphAlignment.LEFT, 40, false));                        //Table 表格第三行                      thirdXwpfTable.GetRow(2).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "處理結果: ", ParagraphAlignment.CENTER, 40, true));                      thirdXwpfTable.GetRow(2).MergeCells(1, 3);                      thirdXwpfTable.GetRow(2).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "通過檢查,評分為優秀!", ParagraphAlignment.LEFT, 40, false));                        //Table 表格第四行,後面的合併3列(注意關於表格中行合併問題,先合併,後填充內容),額外說明                      thirdXwpfTable.GetRow(3).MergeCells(0, 3);//合併3列                      thirdXwpfTable.GetRow(3).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "備註說明: 記住,堅持就是勝利,永遠保持一種求知,好問的心理!", ParagraphAlignment.LEFT, 30, false));                        //Table 表格第五行                      thirdXwpfTable.GetRow(4).MergeCells(0, 1);                      thirdXwpfTable.GetRow(4).GetCell(0).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "                                                                                                                                                                                                 檢查人員簽名:              年 月 日", ParagraphAlignment.LEFT, 40, false));                      thirdXwpfTable.GetRow(4).MergeCells(1, 2);                        thirdXwpfTable.GetRow(4).GetCell(1).SetParagraph(SetTableParagraphInstanceSetting(document, thirdXwpfTable, "                                                                                                                                                                                                 企業法人簽名:              年 月 日", ParagraphAlignment.LEFT, 40, false));                          #endregion                        //向文檔流中寫入內容,生成word                      document.Write(stream);                        savePath = "/SaveWordFile/" + currentDate + "/" + fileName;                        return true;                  }              }              catch (Exception ex)              {                  //ignore                  savePath = ex.Message;                  return false;              }          }              /// <summary>          /// 創建word文檔中的段落對象和設置段落文本的基本樣式(字體大小,字體,字體顏色,字體對齊位置)          /// </summary>          /// <param name="document">document文檔對象</param>          /// <param name="fillContent">段落第一個文本對象填充的內容</param>          /// <param name="isBold">是否加粗</param>          /// <param name="fontSize">字體大小</param>          /// <param name="fontFamily">字體</param>          /// <param name="paragraphAlign">段落排列(左對齊,居中,右對齊)</param>          /// <param name="isStatement">是否在同一段落創建第二個文本對象(解決同一段落裡面需要填充兩個或者多個文本值的情況,多個文本需要自己拓展,現在最多支援兩個)</param>          /// <param name="secondFillContent">第二次聲明的文本對象填充的內容,樣式與第一次的一致</param>          /// <returns></returns>          private static XWPFParagraph ParagraphInstanceSetting(XWPFDocument document, string fillContent, bool isBold, int fontSize, string fontFamily, ParagraphAlignment paragraphAlign, bool isStatement = false, string secondFillContent = "")          {              XWPFParagraph paragraph = document.CreateParagraph();//創建段落對象              paragraph.Alignment = paragraphAlign;//文字顯示位置,段落排列(左對齊,居中,右對齊)                XWPFRun xwpfRun = paragraph.CreateRun();//創建段落文本對象              xwpfRun.IsBold = isBold;//文字加粗              xwpfRun.SetText(fillContent);//填充內容              xwpfRun.FontSize = fontSize;//設置文字大小              xwpfRun.SetFontFamily(fontFamily, FontCharRange.None); //設置標題樣式如:(微軟雅黑,隸書,楷體)根據自己的需求而定                if (isStatement)              {                  XWPFRun secondxwpfRun = paragraph.CreateRun();//創建段落文本對象                  secondxwpfRun.IsBold = isBold;//文字加粗                  secondxwpfRun.SetText(secondFillContent);//填充內容                  secondxwpfRun.FontSize = fontSize;//設置文字大小                  secondxwpfRun.SetFontFamily(fontFamily, FontCharRange.None); //設置標題樣式如:(微軟雅黑,隸書,楷體)根據自己的需求而定              }                  return paragraph;          }            /// <summary>          /// 創建Word文檔中表格段落實例和設置表格段落文本的基本樣式(字體大小,字體,字體顏色,字體對齊位置)          /// </summary>          /// <param name="document">document文檔對象</param>          /// <param name="table">表格對象</param>          /// <param name="fillContent">要填充的文字</param>          /// <param name="paragraphAlign">段落排列(左對齊,居中,右對齊)</param>          /// <param name="rowsHeight">設置文本位置(設置兩行之間的行間),從而實現table的高度設置效果  </param>          /// <param name="isBold">是否加粗(true加粗,false不加粗)</param>          /// <param name="fontSize">字體大小</param>          /// <returns></returns>          private static XWPFParagraph SetTableParagraphInstanceSetting(XWPFDocument document, XWPFTable table, string fillContent, ParagraphAlignment paragraphAlign, int rowsHeight, bool isBold, int fontSize = 10)          {              var para = new CT_P();              XWPFParagraph paragraph = new XWPFParagraph(para, table.Body);//創建表格中的段落對象              paragraph.Alignment = paragraphAlign;//文字顯示位置,段落排列(左對齊,居中,右對齊)                XWPFRun xwpfRun = paragraph.CreateRun();//創建段落文本對象              xwpfRun.SetText(fillContent);              xwpfRun.FontSize = fontSize;//字體大小              xwpfRun.IsBold = isBold;//是否加粗              xwpfRun.SetFontFamily("宋體", FontCharRange.None);//設置字體(如:微軟雅黑,華文楷體,宋體)              xwpfRun.SetTextPosition(rowsHeight);//設置文本位置(設置兩行之間的行間),從而實現table的高度設置效果               return paragraph;          }            #endregion          }  }

總結:

  寫到最後我只想說我太難了,為了實現我想要的這個樣式,通過不斷的查閱資料,理解NPOI中的段落,文本基本屬性。最終在我生成第22個word時,總算是達到了我要的效果。這裡我將自己在網上查閱的一些資料和自己的實踐心得分享給大家,希望能夠幫到大家,別忘了給我star喲。

GitHub完整示例地址:https://github.com/YSGStudyHards/NPOI-Export-Word