一个Excel导出类的实现过程(三):NPOI组件
NPOI组件强大高效,这里只使用它完成我们的Excel写入功能,需要更多内容则请自行搜索。
- IWorkbook:工作簿对象,可以由NPOI.HSSF.UserModel.HSSFWorkbook实例化得来,Write(Stream stream)如其意;
- ISheet:工作表对象,可以由IWorkbook实例的CreateSheet()方法创建;
- IRow:行对象,可以由ISheet实例的Create()方法创建;
- ICell:单元格对象,可以由IRow实例的CreateCell()方法创建。
先处理翻页问题,10版Excel最多支持1048576行,03版Excel最多支持65536行。只处理03版Excel,除去表头,翻页算法如下:
IWorkbook workbook = new HSSFWorkbook(); ISheet sheet = null; const Int32 RowPerSheet = 65535; for (Int32 r = 0; r < records.Count; r++) { if ((r % RowPerSheet) == 0) { Int32 sheetIndex = (Int32)((Double)r / RowPerSheet) + 1; sheet = workbook.CreateSheet("Sheet" + sheetIndex); for (Int32 i = 0; i < headers.Count; i++) { Console.Write(headers[i].PrintName); Console.Write("\t"); } Console.WriteLine(); } //注意CreateRow(Int32 rownum)中参数rownum虽然从第0行开始,但因为表头存在,每次得往下一行 IRow row = sheet.CreateRow(r % RowPerSheet + 1); for (Int32 i = 0; i < props.Length; i++) { //拿row干活儿了... } }
注意引用NPOI.SS.UserModel和NPOI.HSSF.UserModel命名空间,现在我们有Row,那么创建单元格写入内容就容易了。
考虑到导出一个65536行的工作表,看的人会抓狂,于是封装导出方法到ExcelHelper类,提供行数属性及导出方法,同时将Header类设置为其内部类(这个不是必须的,但重命名一下可能没错),实现如下:
public class ExcelHelper { public class Header { public String Name { get; private set; } public String PrintName { get; private set; } public Header(String name) : this(name, name) { } public Header(String name, String printName) { Name = name; PrintName = printName; } } private const Int32 MaxRowPerSheet = 65535; private Int32 rowPerSheet = 1000; public Int32 RowPerSheet { get { return rowPerSheet; } set { if (value < 0 || value > MaxRowPerSheet) { throw new ArgumentOutOfRangeException("RowPerSheet"); } else { rowPerSheet = value; } } } public IWorkbook Export<T>(IList<T> records) { if (records == null) throw new ArgumentNullException("records"); String[] headers = typeof(T).GetProperties().Select(p => p.Name).ToArray(); return Export<T>(records, headers); } public IWorkbook Export<T>(IList<T> records, IList<String> headers) { if (records == null) throw new ArgumentNullException("records"); if (headers == null || headers.Count == 0) throw new ArgumentNullException("headers"); Header[] newHeaders = typeof(T).GetProperties().Select(p => new Header(p.Name)).ToArray(); return Export<T>(records, newHeaders); } public IWorkbook Export<T>(IList<T> records, IList<Header> headers) { if (records == null) throw new ArgumentNullException("records"); if (headers == null || headers.Count == 0) throw new ArgumentNullException("headers"); PropertyInfo[] props = new PropertyInfo[headers.Count]; for (int i = 0; i < headers.Count; i++) { props[i] = typeof(T).GetProperty(headers[i].Name); //注意属性数组仍然可以有元素为null } IWorkbook workbook = new HSSFWorkbook(); ISheet sheet = null; IRow row = null; for (int r = 0; r < records.Count; r++) { if ((r % RowPerSheet) == 0) { Int32 sheetIndex = (Int32)((Double)r / RowPerSheet) + 1; sheet = workbook.CreateSheet("Sheet" + sheetIndex); row = sheet.CreateRow(0); for (int i = 0; i < headers.Count; i++) { row.CreateCell(i).SetCellValue(headers[i].PrintName); } Console.WriteLine(); } //注意CreateRow(Int32 rownum)中参数rownum虽然从第0行开始,但因为表头存在,每次得往下一行 row = sheet.CreateRow(r % RowPerSheet + 1); for (int i = 0; i < props.Length; i++) { if (props[i] != null) //注意null检查 { Object value = props[i].GetValue(records[r], null); if (value != null) { row.CreateCell(i).SetCellValue(value.ToString()); } } } } for (Int32 i = 0; i < workbook.NumberOfSheets; i++) { sheet = workbook.GetSheetAt(i); for (Int32 h = 0; h < headers.Count; h++) { sheet.AutoSizeColumn(h); //每列宽度自适应 } } return workbook; } }
客户端调用如下:
static void Main(string[] args) { List<Person> persons = new List<Person>(); //persons.Add(new Person { ID = 1, Name = "Rattz", Birth = new DateTime(1980, 10, 1), Salary = 20.2D }); //persons.Add(new Person { ID = 2, Name = "Mike", Birth = new DateTime(1988, 2, 15), Salary = 20.2D }); Int32 records = 201; for (Int32 i = 0; i < records; i++) { persons.Add(new Person { ID = i, Name = "name" + i, Birth = new DateTime(1980, 10, 1), Salary = 20.2D }); } List<ExcelHelper.Header> headers = new List<ExcelHelper.Header>(); headers.Add(new ExcelHelper.Header("ID")); headers.Add(new ExcelHelper.Header("Name", "名称")); headers.Add(new ExcelHelper.Header("Birth", "生日")); headers.Add(new ExcelHelper.Header("Salary", "薪水")); ExcelHelper excelHelper = new ExcelHelper(); excelHelper.RowPerSheet = 100; IWorkbook workbook = excelHelper.Export<Person>(persons, headers); String path = @"d:\1.xls"; using (FileStream stream = File.Open(path, FileMode.OpenOrCreate)) { workbook.Write(stream); } }
至此功能已完成,但细节待完善,观察“生日”列所有值都是1980-10-01 00:00:00,这是单元格值设置语句row.CreateCell(i).SetCellValue(value.ToString())过于简单的缘故,后续一篇继续,读者也可以参考NPOI组件的ICell属性与方法自行完成。