zoukankan      html  css  js  c++  java
  • 一个Excel导出类的实现过程(四):格式化与若干坑 已补图和代码zip

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

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

    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);
        }
    }

    
    

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

    Person person = new Person();
    person.Salary = 1234567890123456;
    Console.WriteLine(person.Salary);

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

    要处理类似问题,仅将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,代码文件已提供,本文完。

  • 相关阅读:
    ::before和::after伪元素的用法
    JS中map、some、every、filter方法
    C++多线程,互斥,同步
    RAII
    Proxy 代理
    Decorator 装饰
    TCP和UDP的9个区别是什么
    谈谈自己对面向对象的理解
    C++11多线程
    std::move
  • 原文地址:https://www.cnblogs.com/Jusfr/p/3083723.html
Copyright © 2011-2022 走看看