You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

899 lines
36 KiB

  1. using NPOI.HSSF.UserModel;
  2. using NPOI.SS.UserModel;
  3. using NPOI.SS.Util;
  4. using NPOI.XSSF.UserModel;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text;
  11. namespace WebMvcEF.Extensions
  12. {
  13. public static class NpoiExtension
  14. {
  15. /// <summary>
  16. /// 建出物件及名稱並傳回路徑
  17. /// </summary>
  18. /// <param name="templateName"></param>
  19. /// <param name="report"></param>
  20. /// <param name="timeout"></param>
  21. /// <returns></returns>
  22. public static string Report(string templateName, Action<ISheet> report)
  23. {
  24. string sNewTemplateName = null;
  25. if (report != null)
  26. {
  27. string template = string.Format(CultureInfo.CurrentCulture, "App_Data\\{0}.xlsx", templateName);
  28. string sPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, template);
  29. IWorkbook workbook = LoadWorkbook(sPath);
  30. sNewTemplateName = string.Format(CultureInfo.CurrentCulture, "temp\\uploads\\{0}{1}.xlsx", templateName, DateTime.Now.ToString("yyyyMMddhhmmss", CultureInfo.CurrentCulture));
  31. report.Invoke(workbook.GetSheetAt(0));
  32. using FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + sNewTemplateName, FileMode.Create, FileAccess.Write);
  33. workbook.Write(fs);
  34. }
  35. return sNewTemplateName;
  36. }
  37. #region 產生Excel
  38. /// <summary>
  39. /// 根據型別產生試算表
  40. /// </summary>
  41. /// <param name="type"></param>
  42. /// <returns></returns>
  43. public static IWorkbook CreateWorkbook(string type)
  44. {
  45. if (type != null)
  46. {
  47. type = type.TrimStart('.').ToLower(CultureInfo.CurrentCulture);
  48. }
  49. return type switch
  50. {
  51. "xls" => new HSSFWorkbook(),
  52. "xlsx" => new XSSFWorkbook(),
  53. _ => throw new ArgumentNullException("Undefined type: " + type),
  54. };
  55. }
  56. /// <summary>
  57. /// 根據傳進來的路徑載入試算表
  58. /// </summary>
  59. /// <param name="path"></param>
  60. /// <returns></returns>
  61. public static IWorkbook LoadWorkbook(string path)
  62. {
  63. var type = Path.GetExtension(path);
  64. using var fs = new FileStream(path, FileMode.Open);
  65. if (type != null)
  66. {
  67. type = type.TrimStart('.').ToLower(CultureInfo.CurrentCulture);
  68. }
  69. return type switch
  70. {
  71. "xls" => new HSSFWorkbook(fs),
  72. "xlsx" => new XSSFWorkbook(fs),
  73. _ => throw new ArgumentNullException("Undefined type: " + type),
  74. };
  75. }
  76. /// <summary>
  77. /// 判斷型態是否為xlsx
  78. /// </summary>
  79. /// <param name="type"></param>
  80. /// <returns></returns>
  81. public static bool IsXlsx(string type)
  82. {
  83. return "xlsx".Equals(type, StringComparison.OrdinalIgnoreCase);
  84. }
  85. #endregion
  86. /// <summary>
  87. /// 取得目標儲存格
  88. /// </summary>
  89. /// <param name="sheet">工作表</param>
  90. /// <param name="rowNumber">行號</param>
  91. /// <param name="cellNumber">欄號</param>
  92. /// <returns></returns>
  93. public static ICell Cell(this ISheet sheet, int rowNumber, int cellNumber)
  94. {
  95. return sheet?.GetRow(rowNumber).GetCell(cellNumber);
  96. }
  97. /// <summary>
  98. /// 取得目標儲存格 (特別注意: 本方法行號等同顯示行號,例如第1行請輸入1而非0)
  99. /// </summary>
  100. /// <param name="sheet">工作表</param>
  101. /// <param name="rowNumber">行號 (第1行請輸入0)</param>
  102. /// <param name="column">用字元指定欄位 (第一欄請輸入'A')</param>
  103. /// <returns></returns>
  104. public static ICell Cell(this ISheet sheet, int rowNumber, char column)
  105. {
  106. var cellNumber = char.ToUpper(column, CultureInfo.CurrentCulture) - 'A';
  107. return sheet?.GetRow(rowNumber - 1).GetCell(cellNumber);
  108. }
  109. #region 擴充儲存格輸入格式
  110. /// <summary>
  111. /// 設定儲存格數字 (decimal)
  112. /// </summary>
  113. /// <param name="cell">儲存格</param>
  114. /// <param name="value">填入值</param>
  115. public static void SetCellValue(this ICell cell, decimal value)
  116. {
  117. if (cell != null)
  118. {
  119. cell.SetCellValue(Convert.ToDouble(value));
  120. }
  121. }
  122. /// <summary>
  123. /// 設定儲存格數字 (decimal?)
  124. /// 如果傳入null,不寫值進去
  125. /// </summary>
  126. /// <param name="cell">儲存格</param>
  127. /// <param name="value">填入值</param>
  128. public static void SetCellValue(this ICell cell, decimal? value)
  129. {
  130. if (cell != null && value.HasValue)
  131. {
  132. cell.SetCellValue(Convert.ToDouble(value, CultureInfo.CurrentCulture));
  133. }
  134. }
  135. /// <summary>
  136. /// 設定儲存格數字 (int?)
  137. /// 如果傳入null,不寫值進去
  138. /// </summary>
  139. /// <param name="cell">儲存格</param>
  140. /// <param name="value">填入值</param>
  141. public static void SetCellValue(this ICell cell, int? value)
  142. {
  143. if (cell != null && value.HasValue)
  144. {
  145. cell.SetCellValue(Convert.ToDouble(value, CultureInfo.CurrentCulture));
  146. }
  147. }
  148. /// <summary>
  149. /// 設定儲存格數字 (double?)
  150. /// 如果傳入null,不寫值進去
  151. /// </summary>
  152. /// <param name="cell">儲存格</param>
  153. /// <param name="value">填入值</param>
  154. public static void SetCellValue(this ICell cell, double? value)
  155. {
  156. if (cell != null && value.HasValue)
  157. {
  158. cell.SetCellValue(Convert.ToDouble(value, CultureInfo.CurrentCulture));
  159. }
  160. }
  161. /// <summary>
  162. /// 設定儲存格日期 (DateTime?)
  163. /// 如果傳入null,不寫值進去
  164. /// </summary>
  165. /// <param name="cell">儲存格</param>
  166. /// <param name="value">填入值</param>
  167. public static void SetCellValue(this ICell cell, DateTime? value)
  168. {
  169. if (cell != null && value.HasValue)
  170. {
  171. cell.SetCellValue(value.Value);
  172. }
  173. }
  174. /// <summary>
  175. /// 設定文字,簡寫string.Format方法
  176. /// </summary>
  177. /// <param name="cell">儲存格</param>
  178. /// <param name="format">文字格式</param>
  179. /// <param name="args">參數</param>
  180. public static void SetCellValue(this ICell cell, string format, params object[] args)
  181. {
  182. if (cell != null)
  183. {
  184. cell.SetCellValue(string.Format(CultureInfo.CurrentCulture, format, args));
  185. }
  186. }
  187. /// <summary>
  188. /// 將該格原始文字當作format參數,填入指定項目
  189. /// </summary>
  190. /// <param name="cell"></param>
  191. /// <param name="args">format參數</param>
  192. public static void SetFormatValue(this ICell cell, params object[] args)
  193. {
  194. if (cell != null)
  195. {
  196. cell.SetCellValue(string.Format(CultureInfo.CurrentCulture, cell.StringCellValue, args));
  197. }
  198. }
  199. #endregion
  200. #region 產生Excel公式
  201. /// <summary>
  202. /// 取得Excel column的名稱
  203. /// </summary>
  204. /// <param name="columnNumber"></param>
  205. /// <returns></returns>
  206. private static string GetColumnName(int columnNumber)
  207. {
  208. var dividend = columnNumber;
  209. var columnName = string.Empty;
  210. int modulo;
  211. while (dividend > 0)
  212. {
  213. modulo = (dividend - 1) % 26;
  214. columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
  215. dividend = (dividend - modulo) / 26;
  216. }
  217. return columnName;
  218. }
  219. /// <summary>
  220. /// 取得Excel欄位位置
  221. /// </summary>
  222. /// <param name="rowNumber"></param>
  223. /// <param name="columnNumber"></param>
  224. /// <returns></returns>
  225. public static string GetColumnAddress(int rowNumber, int columnNumber)
  226. {
  227. return GetColumnName(columnNumber + 1) + (rowNumber + 1).ToString();
  228. }
  229. /// <summary>
  230. /// 產生Sum公式,請用SetCellFormula填入
  231. /// </summary>
  232. /// <param name="startRow">起始列</param>
  233. /// <param name="startColumn">起始儲存格</param>
  234. /// <param name="endRow">結束列</param>
  235. /// <param name="endColumn">結束儲存格</param>
  236. /// <returns></returns>
  237. public static string GenerateSumFormula(int startRow, int startColumn, int endRow, int endColumn)
  238. {
  239. return string.Format("SUM({0}:{1})", GetColumnAddress(startRow, startColumn), GetColumnAddress(endRow, endColumn));
  240. }
  241. /// <summary>
  242. /// 產生計算公式,請用SetCellFormula填入
  243. /// </summary>
  244. /// <param name="formula"></param>
  245. /// <param name="addresses"></param>
  246. /// <returns></returns>
  247. public static string GenerateCalcFormula(string formula, ExcelAddress[] addresses)
  248. {
  249. var list = new List<string>();
  250. foreach (var address in addresses)
  251. {
  252. list.Add(GetColumnAddress(address.Row, address.Column));
  253. }
  254. return string.Format(formula, list.ToArray());
  255. }
  256. #endregion
  257. /// <summary>
  258. /// 插入資料欄(複製被插入的那欄Style),可以插入一個範圍的欄,目前不包含合併儲存格
  259. /// </summary>
  260. /// <param name="sheet">工作表</param>
  261. /// <param name="rowNumber">行號</param>
  262. /// <param name="columnNumber">欄號</param>
  263. /// <param name="rowRange">行範圍</param>
  264. /// <param name="columnRange">欄範圍</param>
  265. /// <param name="columnCount">次數,丟幾次產幾次</param>
  266. /// <param name="targetColumnNumber">指定插入位置</param>
  267. public static void InsertColumn(this ISheet sheet, int rowNumber, int columnNumber, int rowRange = 1, int columnRange = 1, int columnCount = 1, int targetColumnNumber = 0)
  268. {
  269. // 範圍和次數大於0才會繼續執行
  270. if (rowRange > 0 && columnCount > 0 && columnRange > 0)
  271. {
  272. // 起始位置
  273. var startNumber = targetColumnNumber > 0 ? targetColumnNumber : columnNumber + columnRange;
  274. var addColumnCount = columnCount * columnRange;
  275. // 紀錄已經修改過style的column
  276. var columnList = new List<int>();
  277. // 每個資料列去處理
  278. for (var r = 0; r < rowRange; r++)
  279. {
  280. // 取得原始資料列
  281. var sourceRow = sheet.GetRow(rowNumber + r);
  282. // 將需要往後推的Cell向後推
  283. for (var c = sourceRow.LastCellNum; c >= startNumber; c--)
  284. {
  285. // 目標Cell
  286. var targetCellNum = c + addColumnCount;
  287. // 如果Cell不等於空就移過去
  288. var sourceCell = sourceRow.GetCell(c);
  289. if (sourceCell != null)
  290. {
  291. // 確保目標的Cell可以使用
  292. if (sourceRow.GetCell(targetCellNum) == null)
  293. {
  294. sourceRow.CreateCell(targetCellNum);
  295. }
  296. // 複製Cell
  297. sourceRow.CopyCell(c, targetCellNum);
  298. // 移除原本的Cell
  299. sourceRow.RemoveCell(sourceCell);
  300. // 複製Column的width
  301. if (!columnList.Contains(targetCellNum))
  302. {
  303. columnList.Add(targetCellNum);
  304. sheet.SetColumnWidth(targetCellNum, sheet.GetColumnWidth(c));
  305. sheet.SetColumnWidth(c, sheet.DefaultColumnWidth);
  306. }
  307. }
  308. }
  309. // 從目標位置開始複製
  310. for (var c = columnNumber; c < columnNumber + columnRange; c++)
  311. {
  312. // 如果Cell不等於空就移過去
  313. var sourceCell = sourceRow.GetCell(c);
  314. if (sourceCell != null)
  315. {
  316. for (var x = 1; x <= columnCount; x++)
  317. {
  318. // 目標Cell
  319. var targetCellNum = c + (x * columnRange);
  320. // 確保目標的Cell可以使用
  321. if (sourceRow.GetCell(targetCellNum) == null)
  322. {
  323. sourceRow.CreateCell(targetCellNum);
  324. }
  325. // 複製Cell
  326. sourceRow.CopyCell(c, targetCellNum);
  327. // 複製Column的width
  328. if (!columnList.Contains(targetCellNum))
  329. {
  330. columnList.Add(targetCellNum);
  331. sheet.SetColumnWidth(targetCellNum, sheet.GetColumnWidth(c));
  332. }
  333. }
  334. }
  335. }
  336. }
  337. }
  338. }
  339. /// <summary>
  340. /// 插入資料列(複製被插入的那列Style),可以插入一個範圍的列,公式請自行輸入
  341. /// </summary>
  342. /// <param name="sheet">工作表</param>
  343. /// <param name="rowNumber">行號</param>
  344. /// <param name="range">範圍</param>
  345. /// <param name="count">次數,丟幾次產幾次</param>
  346. /// <param name="targetRowNumber">指定插入位置</param>
  347. public static void InsertRow(this ISheet sheet, int rowNumber, int range = 1, int count = 1, int targetRowNumber = 0)
  348. {
  349. // 範圍和次數大於0才會繼續執行
  350. if (range > 0 && count > 0)
  351. {
  352. // 從選取範圍後往下移動幾行
  353. var addRowCount = range * count;
  354. var startNumber = targetRowNumber > 0 ? targetRowNumber : rowNumber + range;
  355. if (startNumber <= sheet.LastRowNum)
  356. {
  357. sheet.ShiftRows(startNumber, sheet.LastRowNum, addRowCount, true, false);
  358. }
  359. // 產生資料列
  360. for (var r = 0; r < range; r++)
  361. {
  362. // 取得原始資料列
  363. var sourceRow = sheet.GetRow(rowNumber + r);
  364. for (var t = 0; t < count; t++)
  365. {
  366. // 產生新資料列
  367. var newRow = sheet.CreateRow(startNumber + r + t * range);
  368. // 沒有來源則跳過
  369. if (sourceRow == null) continue;
  370. // 產生新儲存格並填入資料
  371. for (var c = 0; c < sourceRow.LastCellNum; c++)
  372. {
  373. var newCell = newRow.CreateCell(c);
  374. var oldCell = sourceRow.Cells[c];
  375. // 如果該儲存格為Null則跳過這格
  376. if (oldCell == null) continue;
  377. // 複製內容
  378. switch (oldCell.CellType)
  379. {
  380. case CellType.Blank:
  381. newCell.SetCellValue(oldCell.StringCellValue);
  382. break;
  383. case CellType.Boolean:
  384. newCell.SetCellValue(oldCell.BooleanCellValue);
  385. break;
  386. case CellType.Error:
  387. newCell.SetCellErrorValue(oldCell.ErrorCellValue);
  388. break;
  389. case CellType.Numeric:
  390. newCell.SetCellValue(oldCell.NumericCellValue);
  391. break;
  392. case CellType.String:
  393. newCell.SetCellValue(oldCell.RichStringCellValue);
  394. break;
  395. case CellType.Unknown:
  396. newCell.SetCellValue(oldCell.StringCellValue);
  397. break;
  398. case CellType.Formula:
  399. newCell.CellFormula = oldCell.CellFormula;
  400. break;
  401. default:
  402. break;
  403. }
  404. // 複製Cell Style
  405. newCell.CellStyle = oldCell.CellStyle;
  406. }
  407. // 複製Row Style
  408. if (sourceRow.RowStyle != null)
  409. {
  410. newRow.RowStyle = sourceRow.RowStyle;
  411. }
  412. // 複製Row 高度
  413. newRow.HeightInPoints = sourceRow.HeightInPoints;
  414. }
  415. }
  416. #region 分頁符號處理
  417. if (sheet.RowBreaks.Length > 0)
  418. {
  419. startNumber = targetRowNumber > 0 ? targetRowNumber : rowNumber + range;
  420. var cloneBreaks = sheet.RowBreaks.Where(r => startNumber > r && r >= rowNumber);
  421. var resetBreaks = sheet.RowBreaks.Where(r => r >= startNumber);
  422. var newRowBreaks = new List<int>();
  423. // 產生新的分頁符號
  424. foreach (var cloneBreak in cloneBreaks)
  425. {
  426. for (var t = 1; t <= count; t++)
  427. {
  428. var newRowBreak = cloneBreak + (range * t);
  429. newRowBreaks.Add(newRowBreak);
  430. }
  431. }
  432. // 重新設定舊的分頁符號
  433. foreach (var resetBreak in resetBreaks)
  434. {
  435. // 產生新的分頁符號
  436. var newRowBreak = resetBreak + (range * count);
  437. newRowBreaks.Add(newRowBreak);
  438. // 移除舊的分頁符號
  439. sheet.RemoveRowBreak(resetBreak);
  440. }
  441. // 不按照順序插入會無法正常顯示分頁符號
  442. newRowBreaks = newRowBreaks.OrderBy(r => r).ToList();
  443. // 將產生的分頁符號新增進資料表
  444. foreach (var newRowBreak in newRowBreaks)
  445. {
  446. sheet.SetRowBreak(newRowBreak);
  447. }
  448. }
  449. #endregion 分頁符號處理
  450. #region 合併儲存格處理
  451. if (sheet.NumMergedRegions > 0)
  452. {
  453. startNumber = targetRowNumber > 0 ? targetRowNumber : rowNumber + range;
  454. for (var m = sheet.NumMergedRegions - 1; 0 <= m; m--)
  455. {
  456. var address = sheet.GetMergedRegion(m);
  457. // 清除沒用處的合併儲存格資訊
  458. if (address == null)
  459. {
  460. sheet.RemoveMergedRegion(m);
  461. continue;
  462. }
  463. // 產生新增範圍內應有的合併儲存格
  464. var rowRange = address.LastRow - address.FirstRow;
  465. if (address.FirstRow >= rowNumber && rowNumber + range > address.FirstRow)
  466. {
  467. for (var t = 0; t < count; t++)
  468. {
  469. var startRow = startNumber + range * t + address.FirstRow - rowNumber;
  470. var endRow = startRow + rowRange;
  471. sheet.AddMergedRegion(new CellRangeAddress(startRow, endRow, address.FirstColumn, address.LastColumn));
  472. }
  473. }
  474. }
  475. }
  476. #endregion 合併儲存格處理
  477. }
  478. }
  479. /// <summary>
  480. /// 清除資料列,可選範圍
  481. /// </summary>
  482. /// <param name="sheet">工作表</param>
  483. /// <param name="rowNumber">行號</param>
  484. /// <param name="range"></param>
  485. /// <param name="shift">是否將向下所有資料往上移</param>
  486. public static void ClearRow(this ISheet sheet, int rowNumber, int range = 1, bool shift = false)
  487. {
  488. if (sheet != null)
  489. {
  490. for (var i = 0; i < range; i++)
  491. {
  492. var removeRow = sheet.GetRow(rowNumber + i);
  493. if (removeRow != null)
  494. {
  495. sheet.RemoveRow(removeRow);
  496. }
  497. }
  498. // 如果資料要往上移則
  499. if (shift)
  500. {
  501. #region 分頁符號處理
  502. // 如果有分頁符號則重新設定分頁符號
  503. if (sheet.RowBreaks.Length > 0)
  504. {
  505. var rowBreaks = sheet.RowBreaks;
  506. var newRowBreaks = new List<int>();
  507. // 儲存需要的分頁符號並刪除資料表中不需要的分頁符號
  508. foreach (var rowBreak in rowBreaks)
  509. {
  510. if (rowBreak >= rowNumber + range)
  511. {
  512. var newRowBreak = rowBreak - range;
  513. newRowBreaks.Add(newRowBreak);
  514. }
  515. if (rowBreak > rowNumber)
  516. {
  517. sheet.RemoveRowBreak(rowBreak);
  518. }
  519. }
  520. // 不按照順序插入會無法正常顯示分頁符號
  521. newRowBreaks = newRowBreaks.OrderBy(r => r).ToList();
  522. // 將需要的分頁符號新增進資料表
  523. foreach (var newRowBreak in newRowBreaks)
  524. {
  525. sheet.SetRowBreak(newRowBreak);
  526. }
  527. }
  528. #endregion 分頁符號處理
  529. #region 合併儲存格處理
  530. // 因為shiftRow會將合併儲存格往上往下移但不會判斷是否要移除多的合併儲存格,故自行重新設定合併儲存格
  531. var newMergedRegions = new List<CellRangeAddress>();
  532. var rangeLastRow = rowNumber + range - 1;
  533. for (var i = sheet.NumMergedRegions - 1; 0 <= i; i--)
  534. {
  535. var address = sheet.GetMergedRegion(i);
  536. // 清空因shiftRows而產出的垃圾
  537. if (address == null)
  538. {
  539. sheet.RemoveMergedRegion(i);
  540. continue;
  541. }
  542. // 若該合併儲存格的row在應被刪除的row底下則重新設定該合併儲存格
  543. if (rowNumber <= address.LastRow)
  544. {
  545. CellRangeAddress newAddress = null;
  546. // 根據情形重新設定合併儲存格(拆開比較容易理解)
  547. if (rowNumber < address.FirstRow && rangeLastRow < address.FirstRow)
  548. {
  549. // 若該刪除的row沒有覆蓋到合併儲存格的row時,則單純將該合併儲存格往上拉回
  550. newAddress = new CellRangeAddress(address.FirstRow - range, address.LastRow - range, address.FirstColumn, address.LastColumn);
  551. }
  552. else
  553. {
  554. // 算應該刪除的row數量
  555. var removeStartRow = rowNumber < address.FirstRow ? address.FirstRow : rowNumber;
  556. var removeEndRow = rangeLastRow > address.LastRow ? address.LastRow : rangeLastRow;
  557. var removeRowCount = removeEndRow - removeStartRow + 1;
  558. // 若該合併儲存格的row總數比應該刪除的row數量還多才進行新增
  559. var totalRow = address.LastRow - address.FirstRow + 1;
  560. if (totalRow > removeRowCount)
  561. {
  562. var addressFirstRow = address.FirstRow;
  563. var addressLastRow = address.LastRow - removeRowCount;
  564. // 若起始row比合併儲存格的row還小的時候則往上拉回
  565. if (rowNumber < address.FirstRow)
  566. {
  567. var shiftRowCount = address.FirstRow - rowNumber;
  568. addressFirstRow -= shiftRowCount;
  569. addressLastRow -= shiftRowCount;
  570. }
  571. newAddress = new CellRangeAddress(addressFirstRow, addressLastRow, address.FirstColumn, address.LastColumn);
  572. }
  573. }
  574. // 刪除原本的合併儲存格
  575. sheet.RemoveMergedRegion(i);
  576. // 新增新的合併儲存格
  577. if (newAddress != null)
  578. {
  579. newMergedRegions.Add(newAddress);
  580. }
  581. }
  582. }
  583. #endregion 合併儲存格處理
  584. // 將向下所有資料往上移
  585. if (rowNumber < sheet.LastRowNum)
  586. {
  587. sheet.ShiftRows(rowNumber + range, sheet.LastRowNum, -range, true, false);
  588. }
  589. // 因為Shift往上移時候位置會偏移所以在這邊才將合併儲存格新增回去
  590. foreach (var newMergedRegion in newMergedRegions)
  591. {
  592. sheet.AddMergedRegion(newMergedRegion);
  593. }
  594. }
  595. }
  596. }
  597. /// <summary>
  598. /// 重新設定列高功能
  599. /// </summary>
  600. /// <param name="sheet">工作表</param>
  601. /// <param name="rowNumber">行號</param>
  602. public static void AutoHeight(this ISheet sheet, int rowNumber)
  603. {
  604. short height = -1;
  605. if (sheet != null)
  606. {
  607. var row = sheet.GetRow(rowNumber);
  608. var lastCellNum = row.LastCellNum;
  609. for (var i = 0; i < lastCellNum; i++)
  610. {
  611. var columnWidth = sheet.GetColumnWidth(i); // 儲存格可以容納的最大長度
  612. // 如果有被合併的儲存格則會重新設定最大長度
  613. for (var j = 0; j < sheet.NumMergedRegions; j++)
  614. {
  615. var address = sheet.GetMergedRegion(j);
  616. // 過濾空值
  617. if (address == null)
  618. {
  619. continue;
  620. }
  621. if (address.FirstRow == rowNumber)
  622. {
  623. for (var k = i + 1; k <= address.LastColumn; k++)
  624. {
  625. columnWidth += sheet.GetColumnWidth(k);
  626. }
  627. }
  628. }
  629. // 計算新的高度,目前缺少LineBreak的判斷
  630. var texts = row.GetCell(i).ToString().Split(new string[] { "\r\n" }, StringSplitOptions.None);
  631. var fontHeight = row.Cells[i].CellStyle.GetFont(sheet.Workbook).FontHeight;
  632. var byteCount = 0;
  633. foreach (var text in texts)
  634. {
  635. byteCount += Encoding.Default.GetByteCount(text);
  636. }
  637. var textWidth = byteCount * fontHeight / 2;
  638. var scalingRatio = textWidth > columnWidth ? textWidth / columnWidth * fontHeight : fontHeight;
  639. var newHeight = row.Height - fontHeight + ((texts.Length - 1) * fontHeight) + scalingRatio * 3;
  640. if (newHeight > height)
  641. {
  642. height = (short)newHeight;
  643. }
  644. }
  645. row.Height = height;
  646. }
  647. }
  648. /// <summary>
  649. /// 平均設定列高功能
  650. /// </summary>
  651. /// <param name="sheet">工作表</param>
  652. /// <param name="rowNumber">行號</param>
  653. /// <param name="range">範圍</param>
  654. public static void AverageHeight(this ISheet sheet, int rowNumber, int range)
  655. {
  656. var totalHeight = 0f;
  657. if (sheet != null)
  658. {
  659. // 第一次迴圈先計算總高度
  660. for (var i = 0; i < range; i++)
  661. {
  662. totalHeight += sheet.GetRow(rowNumber + i).HeightInPoints;
  663. }
  664. // 第二次迴圈將回傳平均高度回去
  665. var averageHeight = totalHeight / range;
  666. for (var i = 0; i < range; i++)
  667. {
  668. sheet.GetRow(rowNumber + i).HeightInPoints = averageHeight;
  669. }
  670. }
  671. }
  672. /// <summary>
  673. /// 將資料填到Excel資料表
  674. /// </summary>
  675. /// <typeparam name="T"></typeparam>
  676. /// <param name="sheet"></param>
  677. /// <param name="data">要塞入的資料</param>
  678. /// <param name="sourceRowNum">要當作範本的行數(從0開始算)</param>
  679. /// <param name="rowHeight">行高設定</param>
  680. /// <param name="action"></param>
  681. public static void Fill<T>(this ISheet sheet, IEnumerable<T> data, int sourceRowNum, RowHeight rowHeight, Action<IRow, T> action)
  682. {
  683. if (sheet != null && data != null)
  684. {
  685. var sourceRow = sheet.GetRow(sourceRowNum);
  686. var cellStyles = sourceRow.Cells.Select(x => x.CellStyle).ToArray();
  687. var cellNum = sourceRow.LastCellNum;
  688. var endRowNum = sourceRowNum + data.Count();
  689. var sourceRowHeight = sourceRow.HeightInPoints;
  690. var dataCount = data.Count();
  691. var rowNum = sourceRowNum;
  692. if (sourceRowNum < sheet.LastRowNum && dataCount >= 2)
  693. {
  694. sheet.ShiftRows(sourceRowNum + 1, sheet.LastRowNum, dataCount - 1); // 把範本後的內容往下推,避免被蓋過
  695. }
  696. foreach (var item in data)
  697. {
  698. IRow row = null;
  699. if (rowNum < endRowNum)
  700. {
  701. row = sheet.CreateRow(rowNum);
  702. for (var c = 0; c < cellNum; c++)
  703. {
  704. var cell = row.CreateCell(c);
  705. cell.CellStyle = cellStyles[c];
  706. }
  707. }
  708. else
  709. {
  710. row = sheet.GetRow(rowNum);
  711. }
  712. action.Invoke(row, item);
  713. if (rowHeight == RowHeight.Auto)
  714. {
  715. sheet.AutoHeight(rowNum);
  716. }
  717. else if (rowHeight == RowHeight.Fixed)
  718. {
  719. row.HeightInPoints = sourceRowHeight;
  720. }
  721. rowNum++;
  722. }
  723. }
  724. }
  725. public static void AutoWidth(this ISheet sheet)
  726. {
  727. if (sheet != null)
  728. {
  729. for (var cell = 0; cell < sheet.GetRow(0).LastCellNum; cell++)
  730. {
  731. sheet.AutoSizeColumn(cell);
  732. }
  733. }
  734. }
  735. /// <summary>
  736. /// 設定自訂屬性
  737. /// </summary>
  738. /// <param name="workbook"></param>
  739. /// <param name="name"></param>
  740. /// <param name="value"></param>
  741. public static void SetCustomProperty(this IWorkbook workbook, string name, bool value)
  742. {
  743. if (workbook is HSSFWorkbook)
  744. {
  745. (workbook as HSSFWorkbook).DocumentSummaryInformation.CustomProperties.Put(name, value);
  746. }
  747. else if (workbook is XSSFWorkbook)
  748. {
  749. (workbook as XSSFWorkbook).GetProperties().CustomProperties.AddProperty(name, value);
  750. }
  751. }
  752. /// <summary>
  753. /// 取得自訂屬性
  754. /// </summary>
  755. /// <param name="workbook"></param>
  756. /// <param name="name"></param>
  757. /// <returns></returns>
  758. public static bool GetCustomProperty(this IWorkbook workbook, string name)
  759. {
  760. if (workbook is HSSFWorkbook)
  761. {
  762. var props = (workbook as HSSFWorkbook).DocumentSummaryInformation.CustomProperties;
  763. if (props.ContainsKey(name))
  764. {
  765. return (bool)props[name];
  766. }
  767. }
  768. else if (workbook is XSSFWorkbook)
  769. {
  770. var props = (workbook as XSSFWorkbook).GetProperties().CustomProperties;
  771. if (props.Contains(name))
  772. {
  773. return (bool)props.GetProperty(name).Item;
  774. }
  775. }
  776. return false;
  777. }
  778. }
  779. /// <summary>
  780. /// 每行高度要如何調整 (LibreOffice無法自動換行,但Excel可)
  781. /// </summary>
  782. public enum RowHeight
  783. {
  784. /// <summary>
  785. /// 不處理
  786. /// (文字過長時轉成PDF有可能超出格子)
  787. /// </summary>
  788. None,
  789. /// <summary>
  790. /// 使用自製方法判斷高度
  791. /// (有誤差)
  792. /// </summary>
  793. Auto,
  794. /// <summary>
  795. /// 使用範本高度固定設定每一行
  796. /// (文字過長時轉成PDF有可能超出格子,可設高點)
  797. /// </summary>
  798. Fixed
  799. }
  800. /// <summary>
  801. /// Excel位置
  802. /// </summary>
  803. public class ExcelAddress
  804. {
  805. public ExcelAddress(int row, int column)
  806. {
  807. Row = row;
  808. Column = column;
  809. }
  810. public int Row { get; set; }
  811. public int Column { get; set; }
  812. }
  813. }