NPOI 是 Apache POI 專案的 .NET 移植版,純 managed code 實現,無需安裝 Office 即可操作 Excel、Word、PowerPoint。優點:
NPOI 使用 HSSFWorkbook (xls) 與 XSSFWorkbook (xlsx),兩者皆實作 IWorkbook 介面,可透過副檔名動態選擇:
IWorkbook workbook;
string ext = Path.GetExtension(filePath).ToLower();
using (var fs = File.OpenRead(filePath))
{
workbook = ext == ".xls" ? new HSSFWorkbook(fs) : new XSSFWorkbook(fs);
}
public DataTable ReadExcel(string filePath)
{
IWorkbook wk;
string ext = Path.GetExtension(filePath).ToLower();
using (var fs = File.OpenRead(filePath))
{
wk = ext == ".xls" ? new HSSFWorkbook(fs) : new XSSFWorkbook(fs);
}
var sheet = wk.GetSheetAt(0); // 取第一個工作表
var table = new DataTable();
// 表頭
var headerRow = sheet.GetRow(0);
if (headerRow != null)
{
for (int c = 0; c < headerRow.LastCellNum; c++)
table.Columns.Add(headerRow.GetCell(c)?.ToString() ?? $"Column{c}");
}
// 資料列
for (int r = (headerRow != null ? 1 : 0); r <= sheet.LastRowNum; r++)
{
var row = sheet.GetRow(r);
if (row == null) continue;
var dataRow = table.NewRow();
for (int c = 0; c < row.LastCellNum; c++)
{
var cell = row.GetCell(c);
dataRow[c] = GetCellValue(cell);
}
table.Rows.Add(dataRow);
}
return table;
}
// 根據儲存格型別取得強型別值
private object GetCellValue(ICell cell)
{
if (cell == null || cell.CellType == CellType.Blank) return null;
switch (cell.CellType)
{
case CellType.Numeric:
return DateUtil.IsCellDateFormatted(cell)
? (object)cell.DateCellValue
: cell.NumericCellValue;
case CellType.Boolean:
return cell.BooleanCellValue;
case CellType.Formula:
// 公式儲存格需依公式結果型別處理,這裡簡單回傳字串
return cell.StringCellValue;
default:
return cell.StringCellValue;
}
}
public void WriteExcel(string filePath)
{
IWorkbook wb;
string ext = Path.GetExtension(filePath).ToLower();
wb = ext == ".xls" ? new HSSFWorkbook() : new XSSFWorkbook();
// 樣式 1:基本框線、置中、自動換行
var style1 = wb.CreateCellStyle();
style1.BorderBottom = style1.BorderLeft = style1.BorderRight = style1.BorderTop = BorderStyle.Thin;
style1.Alignment = HorizontalAlignment.Center;
style1.VerticalAlignment = VerticalAlignment.Center;
style1.WrapText = true;
// 樣式 2:紅色楷體字、黃底
var style2 = wb.CreateCellStyle();
var font = wb.CreateFont();
font.FontName = "楷體";
font.Color = HSSFColor.Red.Index;
font.Boldweight = (short)FontBoldWeight.Bold;
style2.SetFont(font);
style2.FillForegroundColor = HSSFColor.Yellow.Index;
style2.FillPattern = FillPattern.SolidForeground;
style2.Alignment = HorizontalAlignment.Center;
style2.VerticalAlignment = VerticalAlignment.Center;
style2.BorderBottom = style2.BorderLeft = style2.BorderRight = style2.BorderTop = BorderStyle.Thin;
var sheet = wb.CreateSheet("Sheet0");
// 設定欄寬(單位:1/256 字元寬度)
int[] colWidths = { 10, 10, 10, 20 };
for (int i = 0; i < colWidths.Length; i++)
sheet.SetColumnWidth(i, 256 * colWidths[i]);
// 測試資料
object[,] data = {
{ "列0", "列1", "列2", "列3" },
{ "", 400, 5.2, 6.01 },
{ "", DateTime.Today, true, "2014-07-02" }
};
for (int r = 0; r < 3; r++)
{
var row = sheet.CreateRow(r);
for (int c = 0; c < 4; c++)
{
var cell = row.CreateCell(c);
cell.CellStyle = (c % 2 == 0) ? style1 : style2;
SetCellValue(cell, data[r, c]);
}
}
// 合併儲存格:第 0-2 列、第 0 欄
sheet.AddMergedRegion(new CellRangeAddress(0, 2, 0, 0));
using (var fs = File.Create(filePath))
wb.Write(fs);
}
// 依物件型別寫入儲存格
private void SetCellValue(ICell cell, object value)
{
if (value == null) { cell.SetCellValue(""); return; }
switch (value)
{
case int i: cell.SetCellValue(i); break;
case double d: cell.SetCellValue(d); break;
case DateTime dt: cell.SetCellValue(dt); break;
case bool b: cell.SetCellValue(b); break;
case IRichTextString rt: cell.SetCellValue(rt); break;
default: cell.SetCellValue(value.ToString()); break;
}
}
// 解鎖特定儲存格(預設皆為鎖定)
cell.CellStyle.IsLocked = false;
// 保護工作表(需提供密碼)
sheet.ProtectSheet("password");
DateUtil.IsCellDateFormatted 判斷。LastRowNum 是「最後一列索引」(以 0 為基底),而非列數;迴圈條件用 r <= sheet.LastRowNum。LastCellNum 是「最後一欄索引 + 1」,即該列實際欄數。SXSSFWorkbook (xlsx 串流模式) 避免記憶體暴增。原文連結:https://www.bianceng.cn/Programming/csharp/201410/45750.htm