第04章-Excel单元格操作与数据类型
第四章:Excel单元格操作与数据类型
4.1 单元格基础操作
4.1.1 创建和获取单元格
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("数据");
IRow row = sheet.CreateRow(0);
// 创建单元格(索引从0开始)
ICell cell0 = row.CreateCell(0); // A列
ICell cell1 = row.CreateCell(1); // B列
ICell cell2 = row.CreateCell(2); // C列
// 获取已存在的单元格
ICell existingCell = row.GetCell(0); // 如果不存在返回null
// 获取单元格,不存在时返回空白单元格
ICell cell = row.GetCell(0, MissingCellPolicy.CREATE_NULL_AS_BLANK);
// 获取或创建单元格
ICell cellOrNew = row.GetCell(5) ?? row.CreateCell(5);
4.1.2 MissingCellPolicy策略
// 获取单元格时的策略
row.GetCell(0, MissingCellPolicy.RETURN_NULL_AND_BLANK); // 默认,返回null或空白单元格
row.GetCell(0, MissingCellPolicy.RETURN_BLANK_AS_NULL); // 空白单元格也返回null
row.GetCell(0, MissingCellPolicy.CREATE_NULL_AS_BLANK); // 不存在时创建空白单元格
4.1.3 单元格地址与引用
using NPOI.SS.Util;
// 单元格地址对象
CellAddress address = new CellAddress(0, 0); // A1
Console.WriteLine(address.FormatAsString()); // 输出: A1
// 行列索引转换为单元格地址
string cellRef = CellReference.ConvertNumToColString(0); // 输出: A
CellReference reference = new CellReference(0, 0); // A1
Console.WriteLine(reference.FormatAsString());
// 从字符串解析单元格地址
CellReference parsed = new CellReference("B5");
int row = parsed.Row; // 4 (0-based)
int col = parsed.Col; // 1 (0-based)
string colStr = parsed.CellRefParts[1]; // B
// 获取单元格的地址信息
ICell cell = row.GetCell(0);
int rowIndex = cell.RowIndex;
int colIndex = cell.ColumnIndex;
CellAddress cellAddress = cell.Address;
4.2 数据类型处理
4.2.1 单元格类型枚举
// NPOI支持的单元格类型
public enum CellType
{
Unknown = -1, // 未知类型
Numeric = 0, // 数值(包括日期)
String = 1, // 字符串
Formula = 2, // 公式
Blank = 3, // 空白
Boolean = 4, // 布尔值
Error = 5 // 错误
}
4.2.2 字符串类型
// 设置字符串值
cell.SetCellValue("Hello World");
// 读取字符串值
string value = cell.StringCellValue;
// 设置富文本字符串
IWorkbook workbook = new XSSFWorkbook();
ICreationHelper helper = workbook.GetCreationHelper();
IRichTextString richText = helper.CreateRichTextString("加粗和普通文本");
IFont boldFont = workbook.CreateFont();
boldFont.IsBold = true;
richText.ApplyFont(0, 2, boldFont); // 对"加粗"应用粗体
cell.SetCellValue(richText);
// 读取富文本
IRichTextString rtValue = cell.RichStringCellValue;
string plainText = rtValue.String;
4.2.3 数值类型
// 设置整数
cell.SetCellValue(100);
// 设置小数
cell.SetCellValue(3.14159);
// 设置长整数
cell.SetCellValue(9999999999L);
// 设置货币值(需要配合数字格式)
cell.SetCellValue(1234.56);
ICellStyle currencyStyle = workbook.CreateCellStyle();
IDataFormat format = workbook.CreateDataFormat();
currencyStyle.DataFormat = format.GetFormat("¥#,##0.00");
cell.CellStyle = currencyStyle;
// 读取数值
double numValue = cell.NumericCellValue;
// 安全读取数值
public static double? GetNumericValue(ICell cell)
{
if (cell == null) return null;
return cell.CellType switch
{
CellType.Numeric => cell.NumericCellValue,
CellType.String when double.TryParse(cell.StringCellValue, out double v) => v,
CellType.Formula when cell.CachedFormulaResultType == CellType.Numeric
=> cell.NumericCellValue,
_ => null
};
}
4.2.4 日期时间类型
// 设置日期
cell.SetCellValue(DateTime.Now);
// 设置日期格式
ICellStyle dateStyle = workbook.CreateCellStyle();
IDataFormat dateFormat = workbook.CreateDataFormat();
dateStyle.DataFormat = dateFormat.GetFormat("yyyy-MM-dd");
cell.CellStyle = dateStyle;
// 设置日期时间格式
ICellStyle dateTimeStyle = workbook.CreateCellStyle();
dateTimeStyle.DataFormat = dateFormat.GetFormat("yyyy-MM-dd HH:mm:ss");
cell.CellStyle = dateTimeStyle;
// 判断单元格是否为日期
bool isDate = DateUtil.IsCellDateFormatted(cell);
// 读取日期值
DateTime? dateValue = cell.DateCellValue;
// 安全读取日期
public static DateTime? GetDateValue(ICell cell)
{
if (cell == null) return null;
if (cell.CellType == CellType.Numeric)
{
if (DateUtil.IsCellDateFormatted(cell))
{
return cell.DateCellValue;
}
// 尝试将数值转换为日期
return DateUtil.GetJavaDate(cell.NumericCellValue);
}
if (cell.CellType == CellType.String)
{
if (DateTime.TryParse(cell.StringCellValue, out DateTime date))
{
return date;
}
}
return null;
}
// 常用日期格式
var dateFormats = new Dictionary<string, string>
{
{ "年月日", "yyyy-MM-dd" },
{ "年月日时分秒", "yyyy-MM-dd HH:mm:ss" },
{ "中文日期", "yyyy年MM月dd日" },
{ "短日期", "yyyy/M/d" },
{ "仅时间", "HH:mm:ss" },
{ "年月", "yyyy-MM" }
};
4.2.5 布尔类型
// 设置布尔值
cell.SetCellValue(true);
cell.SetCellValue(false);
// 读取布尔值
bool boolValue = cell.BooleanCellValue;
// 安全读取布尔值
public static bool? GetBooleanValue(ICell cell)
{
if (cell == null) return null;
return cell.CellType switch
{
CellType.Boolean => cell.BooleanCellValue,
CellType.String => bool.TryParse(cell.StringCellValue, out bool v) ? v : null,
CellType.Numeric => cell.NumericCellValue != 0,
_ => null
};
}
4.2.6 错误类型
// 设置错误值
cell.SetCellErrorValue(FormulaError.DIV0.Code); // #DIV/0!
cell.SetCellErrorValue(FormulaError.NA.Code); // #N/A
cell.SetCellErrorValue(FormulaError.NAME.Code); // #NAME?
cell.SetCellErrorValue(FormulaError.NULL.Code); // #NULL!
cell.SetCellErrorValue(FormulaError.NUM.Code); // #NUM!
cell.SetCellErrorValue(FormulaError.REF.Code); // #REF!
cell.SetCellErrorValue(FormulaError.VALUE.Code); // #VALUE!
// 读取错误值
byte errorCode = cell.ErrorCellValue;
FormulaError error = FormulaError.ForInt(errorCode);
4.3 通用单元格值处理
4.3.1 获取任意类型单元格值
/// <summary>
/// 通用单元格值读取器
/// </summary>
public static class CellValueReader
{
/// <summary>
/// 获取单元格的字符串表示
/// </summary>
public static string GetStringValue(ICell cell)
{
if (cell == null) return string.Empty;
return cell.CellType switch
{
CellType.String => cell.StringCellValue ?? string.Empty,
CellType.Numeric => DateUtil.IsCellDateFormatted(cell)
? cell.DateCellValue?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty
: cell.NumericCellValue.ToString(),
CellType.Boolean => cell.BooleanCellValue.ToString(),
CellType.Formula => GetFormulaCellValue(cell),
CellType.Error => FormulaError.ForInt(cell.ErrorCellValue).String,
CellType.Blank => string.Empty,
_ => string.Empty
};
}
private static string GetFormulaCellValue(ICell cell)
{
try
{
return cell.CachedFormulaResultType switch
{
CellType.String => cell.StringCellValue,
CellType.Numeric => DateUtil.IsCellDateFormatted(cell)
? cell.DateCellValue?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""
: cell.NumericCellValue.ToString(),
CellType.Boolean => cell.BooleanCellValue.ToString(),
CellType.Error => FormulaError.ForInt(cell.ErrorCellValue).String,
_ => cell.CellFormula
};
}
catch
{
return cell.CellFormula;
}
}
/// <summary>
/// 获取单元格值为指定类型
/// </summary>
public static T GetValue<T>(ICell cell, T defaultValue = default)
{
if (cell == null) return defaultValue;
Type targetType = typeof(T);
object value = GetRawValue(cell);
if (value == null) return defaultValue;
try
{
if (targetType == typeof(string))
{
return (T)(object)value.ToString();
}
if (targetType == typeof(DateTime) || targetType == typeof(DateTime?))
{
if (value is DateTime dt) return (T)(object)dt;
if (value is double d && DateUtil.IsValidExcelDate(d))
{
return (T)(object)DateUtil.GetJavaDate(d);
}
if (DateTime.TryParse(value.ToString(), out DateTime parsed))
{
return (T)(object)parsed;
}
}
return (T)Convert.ChangeType(value,
Nullable.GetUnderlyingType(targetType) ?? targetType);
}
catch
{
return defaultValue;
}
}
private static object GetRawValue(ICell cell)
{
return cell.CellType switch
{
CellType.String => cell.StringCellValue,
CellType.Numeric => cell.NumericCellValue,
CellType.Boolean => cell.BooleanCellValue,
CellType.Formula => GetFormulaRawValue(cell),
_ => null
};
}
private static object GetFormulaRawValue(ICell cell)
{
return cell.CachedFormulaResultType switch
{
CellType.String => cell.StringCellValue,
CellType.Numeric => cell.NumericCellValue,
CellType.Boolean => cell.BooleanCellValue,
_ => null
};
}
}
4.3.2 设置任意类型单元格值
/// <summary>
/// 通用单元格值设置器
/// </summary>
public static class CellValueWriter
{
/// <summary>
/// 设置单元格值(自动识别类型)
/// </summary>
public static void SetValue(ICell cell, object value, IWorkbook workbook = null)
{
if (value == null)
{
cell.SetCellType(CellType.Blank);
return;
}
switch (value)
{
case string s:
cell.SetCellValue(s);
break;
case int i:
cell.SetCellValue(i);
break;
case long l:
cell.SetCellValue(l);
break;
case float f:
cell.SetCellValue(f);
break;
case double d:
cell.SetCellValue(d);
break;
case decimal dec:
cell.SetCellValue(Convert.ToDouble(dec));
break;
case DateTime dt:
cell.SetCellValue(dt);
if (workbook != null)
{
SetDateFormat(cell, workbook, "yyyy-MM-dd");
}
break;
case DateTimeOffset dto:
cell.SetCellValue(dto.DateTime);
if (workbook != null)
{
SetDateFormat(cell, workbook, "yyyy-MM-dd HH:mm:ss");
}
break;
case bool b:
cell.SetCellValue(b);
break;
case byte[] bytes:
// 图片数据需要特殊处理
cell.SetCellValue($"[Binary:{bytes.Length} bytes]");
break;
default:
cell.SetCellValue(value.ToString());
break;
}
}
private static void SetDateFormat(ICell cell, IWorkbook workbook, string format)
{
ICellStyle style = workbook.CreateCellStyle();
IDataFormat dataFormat = workbook.CreateDataFormat();
style.DataFormat = dataFormat.GetFormat(format);
cell.CellStyle = style;
}
}
4.4 单元格区域操作
4.4.1 单元格区域定义
using NPOI.SS.Util;
// 创建单元格区域(A1:D10)
CellRangeAddress range = new CellRangeAddress(0, 9, 0, 3);
// 参数:起始行, 结束行, 起始列, 结束列
// 从字符串创建
CellRangeAddress range2 = CellRangeAddress.ValueOf("A1:D10");
// 获取区域信息
int firstRow = range.FirstRow;
int lastRow = range.LastRow;
int firstCol = range.FirstColumn;
int lastCol = range.LastColumn;
// 格式化为字符串
string rangeStr = range.FormatAsString(); // "A1:D10"
// 判断单元格是否在区域内
bool isInRange = range.IsInRange(5, 2); // 行索引5, 列索引2
4.4.2 遍历单元格区域
/// <summary>
/// 遍历单元格区域
/// </summary>
public static void IterateRange(ISheet sheet, CellRangeAddress range,
Action<ICell> action)
{
for (int rowIdx = range.FirstRow; rowIdx <= range.LastRow; rowIdx++)
{
IRow row = sheet.GetRow(rowIdx);
if (row == null) continue;
for (int colIdx = range.FirstColumn; colIdx <= range.LastColumn; colIdx++)
{
ICell cell = row.GetCell(colIdx);
if (cell != null)
{
action(cell);
}
}
}
}
/// <summary>
/// 遍历区域并收集值
/// </summary>
public static List<List<object>> GetRangeValues(ISheet sheet, CellRangeAddress range)
{
var result = new List<List<object>>();
for (int rowIdx = range.FirstRow; rowIdx <= range.LastRow; rowIdx++)
{
var rowValues = new List<object>();
IRow row = sheet.GetRow(rowIdx);
for (int colIdx = range.FirstColumn; colIdx <= range.LastColumn; colIdx++)
{
ICell cell = row?.GetCell(colIdx);
rowValues.Add(CellValueReader.GetStringValue(cell));
}
result.Add(rowValues);
}
return result;
}
// 使用示例
var range = CellRangeAddress.ValueOf("A1:C10");
var values = GetRangeValues(sheet, range);
4.4.3 批量设置区域值
/// <summary>
/// 批量设置区域值
/// </summary>
public static void SetRangeValues(ISheet sheet, int startRow, int startCol,
object[,] values, IWorkbook workbook)
{
int rows = values.GetLength(0);
int cols = values.GetLength(1);
for (int i = 0; i < rows; i++)
{
IRow row = sheet.GetRow(startRow + i) ?? sheet.CreateRow(startRow + i);
for (int j = 0; j < cols; j++)
{
ICell cell = row.GetCell(startCol + j) ?? row.CreateCell(startCol + j);
CellValueWriter.SetValue(cell, values[i, j], workbook);
}
}
}
// 使用示例
object[,] data = new object[,]
{
{ "姓名", "年龄", "部门" },
{ "张三", 28, "技术部" },
{ "李四", 32, "市场部" },
{ "王五", 25, "人事部" }
};
SetRangeValues(sheet, 0, 0, data, workbook);
4.5 特殊单元格处理
4.5.1 超链接
// 创建Web超链接
ICell cell = row.CreateCell(0);
cell.SetCellValue("访问百度");
ICreationHelper helper = workbook.GetCreationHelper();
IHyperlink link = helper.CreateHyperlink(HyperlinkType.Url);
link.Address = "https://www.baidu.com";
cell.Hyperlink = link;
// 设置超链接样式
ICellStyle linkStyle = workbook.CreateCellStyle();
IFont linkFont = workbook.CreateFont();
linkFont.Color = IndexedColors.Blue.Index;
linkFont.Underline = FontUnderlineType.Single;
linkStyle.SetFont(linkFont);
cell.CellStyle = linkStyle;
// 创建文件超链接
IHyperlink fileLink = helper.CreateHyperlink(HyperlinkType.File);
fileLink.Address = "C:\\Documents\\report.pdf";
cell.Hyperlink = fileLink;
// 创建邮件超链接
IHyperlink emailLink = helper.CreateHyperlink(HyperlinkType.Email);
emailLink.Address = "mailto:example@email.com";
cell.Hyperlink = emailLink;
// 创建工作表内部链接
IHyperlink docLink = helper.CreateHyperlink(HyperlinkType.Document);
docLink.Address = "'Sheet2'!A1"; // 链接到Sheet2的A1单元格
cell.Hyperlink = docLink;
4.5.2 注释/批注
// 创建绘图对象
IDrawing drawing = sheet.CreateDrawingPatriarch();
// 创建注释锚点
IClientAnchor anchor = drawing.CreateAnchor(0, 0, 0, 0, 2, 1, 4, 4);
// 参数:dx1, dy1, dx2, dy2, col1, row1, col2, row2
// 创建注释
IComment comment = drawing.CreateCellComment(anchor);
comment.String = new XSSFRichTextString("这是一条注释");
comment.Author = "张三";
// 将注释附加到单元格
ICell cell = row.GetCell(0) ?? row.CreateCell(0);
cell.CellComment = comment;
// 读取注释
IComment existingComment = cell.CellComment;
if (existingComment != null)
{
string commentText = existingComment.String.String;
string author = existingComment.Author;
}
// 删除注释
cell.RemoveCellComment();
4.5.3 数据有效性(下拉列表)
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
// 创建数据有效性帮助类
IDataValidationHelper validationHelper = sheet.GetDataValidationHelper();
// 创建下拉列表选项
string[] options = { "选项A", "选项B", "选项C", "选项D" };
IDataValidationConstraint constraint = validationHelper
.CreateExplicitListConstraint(options);
// 指定应用范围(A2:A100)
CellRangeAddressList addressList = new CellRangeAddressList(1, 99, 0, 0);
// 创建数据有效性
IDataValidation validation = validationHelper
.CreateValidation(constraint, addressList);
// 设置错误提示
validation.ShowErrorBox = true;
validation.CreateErrorBox("输入错误", "请从下拉列表中选择");
// 设置输入提示
validation.ShowPromptBox = true;
validation.CreatePromptBox("提示", "请选择一个选项");
// 应用数据有效性
sheet.AddValidationData(validation);
// 创建数值范围验证
IDataValidationConstraint numConstraint = validationHelper
.CreateNumericConstraint(
ValidationType.INTEGER,
OperatorType.BETWEEN,
"1", "100");
CellRangeAddressList numAddressList = new CellRangeAddressList(1, 99, 1, 1);
IDataValidation numValidation = validationHelper
.CreateValidation(numConstraint, numAddressList);
numValidation.ShowErrorBox = true;
numValidation.CreateErrorBox("数值错误", "请输入1-100之间的整数");
sheet.AddValidationData(numValidation);
// 创建日期范围验证
IDataValidationConstraint dateConstraint = validationHelper
.CreateDateConstraint(
OperatorType.BETWEEN,
"2024-01-01", "2024-12-31",
"yyyy-MM-dd");
CellRangeAddressList dateAddressList = new CellRangeAddressList(1, 99, 2, 2);
IDataValidation dateValidation = validationHelper
.CreateValidation(dateConstraint, dateAddressList);
sheet.AddValidationData(dateValidation);
4.6 单元格复制与移动
4.6.1 复制单元格
/// <summary>
/// 复制单元格(包括值、样式、公式)
/// </summary>
public static void CopyCell(ICell sourceCell, ICell targetCell,
IWorkbook targetWorkbook, bool copyStyle = true)
{
if (sourceCell == null) return;
// 复制单元格类型和值
switch (sourceCell.CellType)
{
case CellType.Blank:
targetCell.SetCellType(CellType.Blank);
break;
case CellType.Boolean:
targetCell.SetCellValue(sourceCell.BooleanCellValue);
break;
case CellType.Numeric:
targetCell.SetCellValue(sourceCell.NumericCellValue);
break;
case CellType.String:
targetCell.SetCellValue(sourceCell.StringCellValue);
break;
case CellType.Formula:
targetCell.SetCellFormula(sourceCell.CellFormula);
break;
case CellType.Error:
targetCell.SetCellErrorValue(sourceCell.ErrorCellValue);
break;
}
// 复制样式
if (copyStyle && sourceCell.CellStyle != null)
{
ICellStyle newStyle = targetWorkbook.CreateCellStyle();
newStyle.CloneStyleFrom(sourceCell.CellStyle);
targetCell.CellStyle = newStyle;
}
// 复制超链接
if (sourceCell.Hyperlink != null)
{
ICreationHelper helper = targetWorkbook.GetCreationHelper();
IHyperlink newLink = helper.CreateHyperlink(sourceCell.Hyperlink.Type);
newLink.Address = sourceCell.Hyperlink.Address;
targetCell.Hyperlink = newLink;
}
// 复制注释
if (sourceCell.CellComment != null)
{
// 注释复制需要创建新的绘图对象
// 这里简化处理,只复制文本
ISheet targetSheet = targetCell.Sheet;
IDrawing drawing = targetSheet.CreateDrawingPatriarch();
IClientAnchor anchor = drawing.CreateAnchor(
0, 0, 0, 0,
targetCell.ColumnIndex, targetCell.RowIndex,
targetCell.ColumnIndex + 2, targetCell.RowIndex + 2);
IComment comment = drawing.CreateCellComment(anchor);
comment.String = new XSSFRichTextString(sourceCell.CellComment.String.String);
comment.Author = sourceCell.CellComment.Author;
targetCell.CellComment = comment;
}
}
/// <summary>
/// 复制单元格区域
/// </summary>
public static void CopyRange(ISheet sourceSheet, CellRangeAddress sourceRange,
ISheet targetSheet, int targetStartRow, int targetStartCol,
IWorkbook targetWorkbook)
{
for (int rowOffset = 0; rowOffset <= sourceRange.LastRow - sourceRange.FirstRow; rowOffset++)
{
IRow sourceRow = sourceSheet.GetRow(sourceRange.FirstRow + rowOffset);
if (sourceRow == null) continue;
IRow targetRow = targetSheet.GetRow(targetStartRow + rowOffset)
?? targetSheet.CreateRow(targetStartRow + rowOffset);
for (int colOffset = 0; colOffset <= sourceRange.LastColumn - sourceRange.FirstColumn; colOffset++)
{
ICell sourceCell = sourceRow.GetCell(sourceRange.FirstColumn + colOffset);
if (sourceCell == null) continue;
ICell targetCell = targetRow.CreateCell(targetStartCol + colOffset);
CopyCell(sourceCell, targetCell, targetWorkbook, true);
}
}
}
4.6.2 移动单元格
/// <summary>
/// 移动单元格
/// </summary>
public static void MoveCell(ICell sourceCell, IRow targetRow, int targetColIndex,
IWorkbook workbook)
{
ICell targetCell = targetRow.CreateCell(targetColIndex);
CopyCell(sourceCell, targetCell, workbook);
// 清空源单元格
sourceCell.SetCellType(CellType.Blank);
sourceCell.CellStyle = null;
if (sourceCell.Hyperlink != null)
{
sourceCell.Hyperlink = null;
}
if (sourceCell.CellComment != null)
{
sourceCell.RemoveCellComment();
}
}
/// <summary>
/// 移动行
/// </summary>
public static void MoveRow(ISheet sheet, int sourceRowIndex, int targetRowIndex)
{
if (sourceRowIndex == targetRowIndex) return;
// 使用ShiftRows移动行
if (targetRowIndex > sourceRowIndex)
{
// 向下移动
sheet.ShiftRows(sourceRowIndex, sourceRowIndex,
targetRowIndex - sourceRowIndex);
}
else
{
// 向上移动
sheet.ShiftRows(sourceRowIndex, sourceRowIndex,
targetRowIndex - sourceRowIndex);
}
}
4.7 数据类型转换工具类
/// <summary>
/// Excel数据类型转换工具
/// </summary>
public static class ExcelDataConverter
{
/// <summary>
/// 将DataTable转换为Excel
/// </summary>
public static IWorkbook DataTableToExcel(DataTable dt, bool includeHeader = true)
{
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet(dt.TableName ?? "Sheet1");
int rowIndex = 0;
// 创建表头
if (includeHeader)
{
IRow headerRow = sheet.CreateRow(rowIndex++);
ICellStyle headerStyle = CreateHeaderStyle(workbook);
for (int i = 0; i < dt.Columns.Count; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue(dt.Columns[i].ColumnName);
cell.CellStyle = headerStyle;
}
}
// 创建数据行
foreach (DataRow dr in dt.Rows)
{
IRow row = sheet.CreateRow(rowIndex++);
for (int i = 0; i < dt.Columns.Count; i++)
{
ICell cell = row.CreateCell(i);
object value = dr[i];
CellValueWriter.SetValue(cell, value == DBNull.Value ? null : value, workbook);
}
}
// 自动调整列宽
for (int i = 0; i < dt.Columns.Count; i++)
{
sheet.AutoSizeColumn(i);
}
return workbook;
}
/// <summary>
/// 将Excel转换为DataTable
/// </summary>
public static DataTable ExcelToDataTable(ISheet sheet, bool hasHeader = true)
{
DataTable dt = new DataTable(sheet.SheetName);
if (sheet.PhysicalNumberOfRows == 0) return dt;
IRow firstRow = sheet.GetRow(sheet.FirstRowNum);
int columnCount = firstRow.LastCellNum;
// 创建列
if (hasHeader)
{
for (int i = 0; i < columnCount; i++)
{
ICell cell = firstRow.GetCell(i);
string columnName = CellValueReader.GetStringValue(cell);
if (string.IsNullOrEmpty(columnName))
{
columnName = $"Column{i}";
}
dt.Columns.Add(columnName);
}
}
else
{
for (int i = 0; i < columnCount; i++)
{
dt.Columns.Add($"Column{i}");
}
}
// 创建行数据
int startRow = hasHeader ? sheet.FirstRowNum + 1 : sheet.FirstRowNum;
for (int rowIdx = startRow; rowIdx <= sheet.LastRowNum; rowIdx++)
{
IRow row = sheet.GetRow(rowIdx);
if (row == null) continue;
DataRow dr = dt.NewRow();
bool hasData = false;
for (int colIdx = 0; colIdx < columnCount; colIdx++)
{
ICell cell = row.GetCell(colIdx);
string value = CellValueReader.GetStringValue(cell);
dr[colIdx] = value;
if (!string.IsNullOrEmpty(value)) hasData = true;
}
if (hasData) dt.Rows.Add(dr);
}
return dt;
}
/// <summary>
/// 将集合转换为Excel
/// </summary>
public static IWorkbook ListToExcel<T>(IEnumerable<T> list,
string sheetName = "Sheet1") where T : class
{
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet(sheetName);
var properties = typeof(T).GetProperties();
// 创建表头
IRow headerRow = sheet.CreateRow(0);
ICellStyle headerStyle = CreateHeaderStyle(workbook);
for (int i = 0; i < properties.Length; i++)
{
ICell cell = headerRow.CreateCell(i);
// 使用DisplayName特性或属性名
string displayName = properties[i]
.GetCustomAttributes(typeof(System.ComponentModel.DisplayNameAttribute), false)
.FirstOrDefault() is System.ComponentModel.DisplayNameAttribute attr
? attr.DisplayName
: properties[i].Name;
cell.SetCellValue(displayName);
cell.CellStyle = headerStyle;
}
// 创建数据行
int rowIndex = 1;
foreach (var item in list)
{
IRow row = sheet.CreateRow(rowIndex++);
for (int i = 0; i < properties.Length; i++)
{
ICell cell = row.CreateCell(i);
object value = properties[i].GetValue(item);
CellValueWriter.SetValue(cell, value, workbook);
}
}
// 自动调整列宽
for (int i = 0; i < properties.Length; i++)
{
sheet.AutoSizeColumn(i);
}
return workbook;
}
/// <summary>
/// 将Excel转换为集合
/// </summary>
public static List<T> ExcelToList<T>(ISheet sheet, bool hasHeader = true)
where T : class, new()
{
var list = new List<T>();
var properties = typeof(T).GetProperties();
if (sheet.PhysicalNumberOfRows == 0) return list;
// 建立列名到属性的映射
var columnMap = new Dictionary<int, PropertyInfo>();
IRow firstRow = sheet.GetRow(sheet.FirstRowNum);
if (hasHeader)
{
for (int i = 0; i < firstRow.LastCellNum; i++)
{
string columnName = CellValueReader.GetStringValue(firstRow.GetCell(i));
var prop = properties.FirstOrDefault(p =>
p.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase) ||
(p.GetCustomAttributes(typeof(System.ComponentModel.DisplayNameAttribute), false)
.FirstOrDefault() is System.ComponentModel.DisplayNameAttribute attr &&
attr.DisplayName.Equals(columnName, StringComparison.OrdinalIgnoreCase)));
if (prop != null)
{
columnMap[i] = prop;
}
}
}
else
{
for (int i = 0; i < Math.Min(firstRow.LastCellNum, properties.Length); i++)
{
columnMap[i] = properties[i];
}
}
// 读取数据
int startRow = hasHeader ? sheet.FirstRowNum + 1 : sheet.FirstRowNum;
for (int rowIdx = startRow; rowIdx <= sheet.LastRowNum; rowIdx++)
{
IRow row = sheet.GetRow(rowIdx);
if (row == null) continue;
T item = new T();
bool hasData = false;
foreach (var kvp in columnMap)
{
ICell cell = row.GetCell(kvp.Key);
if (cell == null) continue;
try
{
object value = ConvertCellValue(cell, kvp.Value.PropertyType);
if (value != null)
{
kvp.Value.SetValue(item, value);
hasData = true;
}
}
catch
{
// 忽略转换错误
}
}
if (hasData) list.Add(item);
}
return list;
}
private static object ConvertCellValue(ICell cell, Type targetType)
{
if (cell == null || cell.CellType == CellType.Blank) return null;
Type underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (underlyingType == typeof(string))
{
return CellValueReader.GetStringValue(cell);
}
if (underlyingType == typeof(DateTime))
{
if (cell.CellType == CellType.Numeric && DateUtil.IsCellDateFormatted(cell))
{
return cell.DateCellValue;
}
if (DateTime.TryParse(CellValueReader.GetStringValue(cell), out DateTime dt))
{
return dt;
}
return null;
}
if (underlyingType == typeof(bool))
{
if (cell.CellType == CellType.Boolean) return cell.BooleanCellValue;
string strValue = CellValueReader.GetStringValue(cell).ToLower();
return strValue == "true" || strValue == "1" || strValue == "是";
}
// 数值类型
double numValue = 0;
if (cell.CellType == CellType.Numeric)
{
numValue = cell.NumericCellValue;
}
else if (!double.TryParse(CellValueReader.GetStringValue(cell), out numValue))
{
return null;
}
return Convert.ChangeType(numValue, underlyingType);
}
private static ICellStyle CreateHeaderStyle(IWorkbook workbook)
{
ICellStyle style = workbook.CreateCellStyle();
IFont font = workbook.CreateFont();
font.IsBold = true;
style.SetFont(font);
style.FillForegroundColor = IndexedColors.Grey25Percent.Index;
style.FillPattern = FillPattern.SolidForeground;
style.Alignment = HorizontalAlignment.Center;
style.BorderBottom = BorderStyle.Thin;
style.BorderTop = BorderStyle.Thin;
style.BorderLeft = BorderStyle.Thin;
style.BorderRight = BorderStyle.Thin;
return style;
}
}
4.8 本章小结
本章详细介绍了NPOI中单元格的操作和各种数据类型的处理。通过本章学习,你应该掌握:
- 单元格的创建、获取和地址引用
- 各种数据类型(字符串、数值、日期、布尔、公式、错误)的读写
- 通用单元格值处理工具的封装
- 单元格区域的操作和批量数据处理
- 特殊单元格功能(超链接、注释、数据验证)
- 单元格的复制和移动
- DataTable和集合与Excel的相互转换
这些单元格操作是Excel处理的核心,掌握后可以处理大部分Excel数据操作需求。
下一章预告:第五章将介绍Excel样式与格式化,包括字体、边框、背景色、对齐方式和数字格式等详细设置。

浙公网安备 33010602011771号