一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

这是本文的第四部分也是最后部分,适合新人初步学习泛型、反射,提供了有限的业务场景分析、若干的NPOI接口使用示范,前三部分链接如下:

接第三部分,由于单元格设置语句仅简单的一句话row.CreateCell(i).SetCellValue(value.ToString()),生成的Excel仍然粗陋。

一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

Excel导出通常会遇到若干问题:

  • 整数变字符串问题:整型值比如ID列,单元格左上角出现绿色三角符号,单元格变成了字符串类型,不够友好;
  • 日期显示问题:有时候需要显示到日期如“2013-5-17”而不要带上“16:50:13”,有时候只希望显示“16:50”,而不是告诉使用人自己拿生成的工作薄设置格式;
  • 科学计数法问题:手机号13912345678只是列宽过短时看起来像科学计数法,但超过15位的整数像1234567890123456就真变成1.23457E+15而且最后一位6变成了零。

ICell的实现者HSSFCell有数量不少的属性和方法帮助我们完成单元格格式设置,考虑到应用场景,创建指定类型的单元格并配合格式化选项已经能满足要求,下文使用若干强制转型和控制语句完成功能。

首先扩充Header类,添加一个Format参数。当使用者传入空Format参数时由我们推断单元格类型与值,而当使用者传入非空的Format参数时,我们调用ToString(String format)方法进行格式化。

public class Header

{

    public String Name { get; set; }

    public String PrintName { get; set; }

    public String Format { get; set; }



    public Header(String name)

        : this(name, name)

    {

    }



    public Header(String name, String printName)

        : this(name, printName, null)

    {

    }



    public Header(String name, String printName, String format)

    {

        Name = name;

        PrintName = printName;

        Format = format;

    }

}

我们把权力交给调用者时,便认为他的参数传递是经过判断且有道理的,比如纯数字的条形码显示可能需要调用Decimal.ToString("F0")方法,,但使用者对薪水调用ToString("P2")显示百分号就没办法了;另一部分是设计之初的问题,如双精度Double型精确度,后面再说。

由于ICell.CreateCell(int column, CellType type)创建的单元格类型最终由ICell.SetCellValue()重载版本中参数类型(可以是Boolean、DateTime、Double、String及IRichTextString)决定,所以统一只使用了ICell.CreateCell(int column),必须注意的是用于存放数值的单元格类型枚举只有CellType.NUMERIC,下文还要讲到。新的Export改动点如刚才描述,旧的Person类及Export重载请见前文,实现如下:

public IWorkbook Export<T>(IList<T> records, IList<Header> headers)

{

    if (records == null)

        throw new ArgumentNullException("records");

    if (headers == null)

        throw new ArgumentNullException("headers"); //这里对headers集合非空、重复性、子元素非空引用等检查视具体需求



    PropertyInfo[] props = new PropertyInfo[headers.Count];

    for (Int32 i = 0; i < headers.Count; i++)

    {

        props[i] = typeof(T).GetProperty(headers[i].Name); //属性数组可能产生空元素

    }



    IWorkbook workbook = new HSSFWorkbook();

    ISheet sheet = null;

    IRow row = null;



    for (Int32 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 (Int32 i = 0; i < headers.Count; i++)

            {

                row.CreateCell(i).SetCellValue(headers[i].PrintName);

            }

        }



        row = sheet.CreateRow((r % RowPerSheet) + 1); //因为表头存在,行标需要下移1位

        for (Int32 i = 0; i < props.Length; i++)

        {

            if (props[i] != null) //空引用检查十分必要

            {

                Object value = props[i].GetValue(records[r], null);

                if (value != null) //空引用检查十分必要

                {

                    ICell cell = row.CreateCell(i);

                    if (props[i].PropertyType == typeof(Int32))

                    {

                        if (String.IsNullOrWhiteSpace(headers[i].Format))

                        {

                            cell.SetCellValue(((Int32)value));

                        }

                        else

                        {

                            cell.SetCellValue(((Int32)value).ToString(headers[i].Format));

                        }

                    }

                    else if (props[i].PropertyType == typeof(Double))

                    {

                        if (String.IsNullOrWhiteSpace(headers[i].Format))

                        {

                            cell.SetCellValue(((Double)value));

                        }

                        else

                        {

                            cell.SetCellValue(((Double)value).ToString(headers[i].Format));

                        }

                    }

                    else if (props[i].PropertyType == typeof(Decimal))

                    {

                        if (String.IsNullOrWhiteSpace(headers[i].Format))

                        {

                            cell.SetCellValue(Convert.ToDouble(value));

                        }

                        else

                        {

                            cell.SetCellValue(((Decimal)value).ToString(headers[i].Format));

                        }

                    }

                    else if (props[i].PropertyType == typeof(DateTime))

                    {

                        if (String.IsNullOrWhiteSpace(headers[i].Format))

                        {

                            cell.SetCellValue((DateTime)value);

                        }

                        else

                        {

                            cell.SetCellValue(((DateTime)value).ToString(headers[i].Format));

                        }

                    }

                    else

                    {

                        cell.SetCellValue(value.ToString());

                    }

                }

            }

        }

    }



    //调整每张Sheet列宽自适应

    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;

}

生日类数据显示到年月日即可,我们使用"yyyy-MM-dd"作为格式化参数,薪水未提供格式化参数,客户端调用代码如下:

static void Main(string[] args)

{

    Int32 records = 100;

    List<Person> persons = new List<Person>(records);

    for (Int32 i = 0; i < records; i++)

    {

        Person person = new Person();

        person.ID = i + 1;

        person.Name = "name" + (i + 1);

        person.Salary =  Math.Abs(Guid.NewGuid().GetHashCode() % 10000) + 5000;

        person.Birth = DateTime.Now;

        persons.Add(person);

    }



    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", "生日", "yyyy-MM-dd"));

    headers.Add(new ExcelHelper.Header("Salary", "薪水"));



    ExcelHelper excelHelper = new ExcelHelper();

    excelHelper.RowPerSheet = 100;

    String path = @"d:\1.xls";

    IWorkbook workbook = excelHelper.Export<Person>(persons, headers);

    using (FileStream stream = File.Open(path, FileMode.OpenOrCreate))

    {

        workbook.Write(stream);

    }

}

一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

 
  

之前提到过双精度的问题,对某个员工的薪酬设置为1234567890123456并输出Excel会怎么样,先做个测试:

Person person = new Person();

person.Salary = 1234567890123456;

Console.WriteLine(person.Salary);

输出结果是1.23456789012346E+15也就是说Excel上不仅显示的是科学计数法,而且数值已经变成悲了个剧的1234567890123460。粗暴地讲,这是因为双精度的Double型的有效位数造成的,并不是ToString方法或者Excel写入时产生了变化。

一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

要处理类似问题,仅将Person.Salary声明为Decimal类型还不够,因为单元格类型CellType枚举中用来存数字的只有CellType.NUMERIC,没办法我们需要配合ToString("F0")(或者F2等视具体要求)把值当作字符串写入单元格,改动部分的部分代码如下:

...

public Decimal Salary { get; set; }

...

person.Salary = 1234567890123456M;

....

headers.Add(new ExcelHelper.Header("Salary", "薪水", "F0"));

...

于是能看到Excel上显示的1234567890123456,虽然有绿色小三角,好歹没变成科学计数法和四舍五入。如要求更完备请自行参考NPOI相关API,代码文件已提供,本文完。

一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

你可能感兴趣的:(Excel)