一個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,代碼文件已提供,本文完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM