目 錄:基於NPOI的報表引擎——ExcelReport
上一篇:使用ExcelReport導出Excel
下一篇:擴展元素格式化器
針對上一篇隨筆收到的反饋,在展開對ExcelReport源碼解析之前,我認為把編寫該組件時的想法分享給大家是有必要的。
編寫該組件時,思考如下:
為什麼要將樣式、格式與數據分離呢?恩,你不妨想一想在生成報表時,那些是變的而那些又是不變的。我的結論是:變的是數據。
有了這個想法,很自然的想到用模板去承載不變的部分(常量內容的樣式、格式、數據及變量內容的樣式、格式),在程序中控制變的部分(變量內容的數據)。
這裡以上一篇中的例子標識:
變量內容已使用粉紅色邊框標出,其余為常量內容。好了,相信“內容的數據”大家都知道那個是那個的。下面截圖,內容的樣式和格式。
現在我們回到上篇中使用的模板,相信你應該知道它承載那些東西了。
啰嗦了這麼多,總結一下樣式、格式與數據分離的好處:它讓我們編寫程序時關注更少(只需關心“變量內容的數據”)。
為什麼要抽象一個“元素格式化器”的概念呢?我們看數據源,我們有可能要將某個類型的數據填充到某個單元格、也可能將一個集合填充到多行、有可能將一張圖片填充到某個位置、也有可能就將某個字符串合並到某個單元格的內容中......如此種種。那麼它們有什麼共同點呢?它們都是填充“變量內容的數據”到模板的。
有了上面的背景,這張UML想必不難理解了。
當然,如果你還是覺得復雜, 沒關系。我會先介紹一下這裡面幾個重點關系。
這是一個靜態類,非常簡單。只有兩個靜態方法:ExportToLocal()、ExportToWeb()分別將生成的文件導出到本地和Web。這多半是廢話了,下面是重點:
這便引出了SheetFormatterContainer類,SheetFormatterContainer類是何許也?
說到這,順便說下:ElementFormatter類和SheetFormatterContext類。
回到Export:
public static byte[] ExportToBuffer(string templateFile, params SheetFormatterContainer[] containers)
{
var workbook = LoadTemplateWorkbook(templateFile);
foreach (var container in containers)
{
var sheet = workbook.GetSheet(container.SheetName);
var context = new SheetFormatterContext(sheet, container.Formatters);
context.Format();
}
return workbook.SaveToBuffer();
}
如上代碼,在執行導出的過程中,將每一個SheetFormatterContainer對象轉換成了SheetFormatterContext對象。然後SheetFormatterContext對象調用自身的Format()方法格式化Sheet。
public void Format()
{
if (null == Sheet || null == Formatters)
{
return;
}
foreach (var formatter in Formatters)
{
formatter.Format(this);
}
}
Formatters就是從SheetFormatterContainer傳過來的“元素格式化器”集合。
抽象的“元素格式化器”:
至此,ExcelReport組件的核心部分已經介紹完成了,下面附上代碼(如下代碼僅供參考,了解ExcelReport組件最新動態,請到GitHub下載最新源碼)。
SheetFormatterContainer.cs
/*
類:SheetFormatterContainer
描述:Sheet中元素的格式化器集合
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.Collections.Generic;
namespace ExcelReport
{
public class SheetFormatterContainer
{
#region 成員字段及屬性
private string sheetName;
public string SheetName
{
get { return sheetName; }
}
private IEnumerable<ElementFormatter> formatters;
public IEnumerable<ElementFormatter> Formatters
{
get { return formatters; }
}
#endregion 成員字段及屬性
#region 構造函數
public SheetFormatterContainer(string sheetName, IEnumerable<ElementFormatter> formatters)
{
this.sheetName = sheetName;
this.formatters = formatters;
}
#endregion 構造函數
}
}SheetFormatterContext.cs
/*
類:SheetFormatterContext
描述:Sheet格式化的上下文
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.Collections.Generic;
using NPOI.SS.UserModel;
namespace ExcelReport
{
public class SheetFormatterContext
{
#region 成員字段及屬性
private int _increaseRowsCount = 0;
public ISheet Sheet { get; set; }
public IEnumerable<ElementFormatter> Formatters { get; set; }
#endregion 成員字段及屬性
#region 構造函數
public SheetFormatterContext()
{
}
public SheetFormatterContext(ISheet sheet, IEnumerable<ElementFormatter> formatters)
{
this.Sheet = sheet;
this.Formatters = formatters;
}
#endregion 構造函數
#region 獲取指定行當前行標
/// <summary>
/// 獲取指定行當前行標
/// </summary>
/// <param name="rowIndexInTemplate">指定行在模板中的行標</param>
/// <returns>當前行標</returns>
public int GetCurrentRowIndex(int rowIndexInTemplate)
{
return rowIndexInTemplate + _increaseRowsCount;
}
#endregion 獲取指定行當前行標
#region 在指定行後插入一行(並將指定行作為模板復制樣式)
/// <summary>
/// 在指定行後插入一行(並將指定行作為模板復制樣式)
/// </summary>
/// <param name="templateRowIndex">模板行在模板中的行標</param>
public void InsertEmptyRow(int templateRowIndex)
{
var templateRow = Sheet.GetRow(GetCurrentRowIndex(templateRowIndex));
var insertRowIndex = GetCurrentRowIndex(templateRowIndex + 1);
if (insertRowIndex < Sheet.LastRowNum)
{
Sheet.ShiftRows(insertRowIndex, Sheet.LastRowNum, 1, true, false);
}
var newRow = Sheet.CreateRow(GetCurrentRowIndex(templateRowIndex + 1));
_increaseRowsCount++;
foreach (var cell in templateRow.Cells)
{
newRow.CreateCell(cell.ColumnIndex).CellStyle = cell.CellStyle;
}
}
#endregion 在指定行後插入一行(並將指定行作為模板復制樣式)
#region 清除指定行單元格內容
/// <summary>
/// 清除指定行單元格內容
/// </summary>
/// <param name="rowIndex">指定行在模板中的行標</param>
public void ClearRowContent(int rowIndex)
{
var row = Sheet.GetRow(GetCurrentRowIndex(rowIndex));
foreach (var cell in row.Cells)
{
cell.SetCellValue(string.Empty);
}
}
#endregion 清除指定行單元格內容
#region 刪除指定行
/// <summary>
/// 刪除指定行
/// </summary>
/// <param name="rowIndex">指定行在模板中的行標</param>
public void RemoveRow(int rowIndex)
{
var row = Sheet.GetRow(GetCurrentRowIndex(rowIndex));
Sheet.RemoveRow(row);
}
#endregion 刪除指定行
#region 格式化Sheet
/// <summary>
/// 格式化Sheet
/// </summary>
public void Format()
{
if (null == Sheet || null == Formatters)
{
return;
}
foreach (var formatter in Formatters)
{
formatter.Format(this);
}
}
#endregion 格式化Sheet
}
}ElementFormatter.cs
/*
類:ElementFormatter
描述:(元素)格式化器(抽象)
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System;
using NPOI.SS.UserModel;
namespace ExcelReport
{
public abstract class ElementFormatter
{
#region 設置單元格值
protected virtual void SetCellValue(ICell cell, object value)
{
if (null == cell)
{
return;
}
if (null == value)
{
cell.SetCellValue(string.Empty);
}
else
{
var valueTypeCode = Type.GetTypeCode(value.GetType());
switch (valueTypeCode)
{
case TypeCode.String: //字符串類型
cell.SetCellValue(Convert.ToString(value));
break;
case TypeCode.DateTime: //日期類型
cell.SetCellValue(Convert.ToDateTime(value));
break;
case TypeCode.Boolean: //布爾型
cell.SetCellValue(Convert.ToBoolean(value));
break;
case TypeCode.Int16: //整型
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Byte:
case TypeCode.Single: //浮點型
case TypeCode.Double:
case TypeCode.UInt16: //無符號整型
case TypeCode.UInt32:
case TypeCode.UInt64:
cell.SetCellValue(Convert.ToDouble(value));
break;
default:
cell.SetCellValue(string.Empty);
break;
}
}
}
#endregion 設置單元格值
#region 格式化操作
public abstract void Format(SheetFormatterContext context);
#endregion 格式化操作
}
}CellFormatter.cs
/*
類:CellFormatter
描述:單元格(元素)格式化器
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.Drawing;
namespace ExcelReport
{
public class CellFormatter : ElementFormatter
{
#region 成員字段及屬性
private Point _cellPoint;
private object _value;
#endregion 成員字段及屬性
#region 構造函數
public CellFormatter(Point cellPoint, object value)
{
_cellPoint = cellPoint;
_value = value;
}
public CellFormatter(int rowIndex, int columnIndex, object value)
{
_cellPoint = new Point(rowIndex, columnIndex);
_value = value;
}
#endregion 構造函數
#region 格式化操作
public override void Format(SheetFormatterContext context)
{
var rowIndex = context.GetCurrentRowIndex(_cellPoint.X);
var row = context.Sheet.GetRow(rowIndex);
if (null == row)
{
row = context.Sheet.CreateRow(rowIndex);
}
var cell = row.GetCell(_cellPoint.Y);
if (null == cell)
{
cell = row.CreateCell(_cellPoint.Y);
}
SetCellValue(cell, _value);
}
#endregion 格式化操作
}
}TableFormatter.cs
/*
類:TableFormatter
描述:表格(元素)格式化器
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System;
using System.Collections.Generic;
namespace ExcelReport
{
public class TableFormatter<TSource> : ElementFormatter
{
#region 成員字段
private int _templateRowIndex;
private IEnumerable<TSource> _dataSource;
private List<TableColumnInfo<TSource>> _columnInfoList;
#endregion 成員字段
#region 構造函數
public TableFormatter(int templateRowIndex, IEnumerable<TSource> dataSource, params TableColumnInfo<TSource>[] columnInfos)
{
_templateRowIndex = templateRowIndex;
_dataSource = dataSource;
_columnInfoList = new List<TableColumnInfo<TSource>>();
if (null != columnInfos && columnInfos.Length > 0)
{
_columnInfoList.AddRange(columnInfos);
}
}
#endregion 構造函數
#region 格式化操作
public override void Format(SheetFormatterContext context)
{
context.ClearRowContent(_templateRowIndex); //清除模板行單元格內容
if (null == _columnInfoList || _columnInfoList.Count <= 0 || null == _dataSource)
{
return;
}
foreach (TSource rowSource in _dataSource)
{
var row = context.Sheet.GetRow(context.GetCurrentRowIndex(_templateRowIndex));
foreach (TableColumnInfo<TSource> colInfo in _columnInfoList)
{
var cell = row.GetCell(colInfo.ColumnIndex);
SetCellValue(cell, colInfo.DgSetValue(rowSource));
}
context.InsertEmptyRow(_templateRowIndex); //追加空行
}
context.RemoveRow(_templateRowIndex); //刪除空行
}
#endregion 格式化操作
#region 添加列信息
public void AddColumnInfo(TableColumnInfo<TSource> columnInfo)
{
_columnInfoList.Add(columnInfo);
}
public void AddColumnInfo(int columnIndex, Func<TSource, object> dgSetValue)
{
_columnInfoList.Add(new TableColumnInfo<TSource>(columnIndex, dgSetValue));
}
#endregion 添加列信息
}
}
ExportHelper.cs
/*
類:ExportHelper
描述:導出助手類
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.IO;
using NPOI.SS.UserModel;
namespace ExcelReport
{
internal static class ExportHelper
{
#region 加載模板,獲取IWorkbook對象
private static IWorkbook LoadTemplateWorkbook(string templateFile)
{
using (var fileStream = new FileStream(templateFile, FileMode.Open, FileAccess.Read)) //讀入excel模板
{
return WorkbookFactory.Create(fileStream);
}
}
#endregion 加載模板,獲取IWorkbook對象
#region 將IWorkBook對象轉換成二進制文件流
private static byte[] SaveToBuffer(this IWorkbook workbook)
{
using (var ms = new MemoryStream())
{
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
return ms.GetBuffer();
}
}
#endregion 將IWorkBook對象轉換成二進制文件流
#region 導出格式化處理後的文件到二進制文件流
public static byte[] ExportToBuffer(string templateFile, params SheetFormatterContainer[] containers)
{
var workbook = LoadTemplateWorkbook(templateFile);
foreach (var container in containers)
{
var sheet = workbook.GetSheet(container.SheetName);
var context = new SheetFormatterContext(sheet, container.Formatters);
context.Format();
}
return workbook.SaveToBuffer();
}
#endregion 導出格式化處理後的文件到二進制文件流
}
}
Export.cs
/*
類:Export
描述:導出
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System;
using System.IO;
using System.Web;
namespace ExcelReport
{
public static class Export
{
#region 導出到本地
public static void ExportToLocal(string templateFile, string targetFile, params SheetFormatterContainer[] containers)
{
#region 參數驗證
if (string.IsNullOrWhiteSpace(templateFile))
{
throw new ArgumentNullException("templateFile");
}
if (string.IsNullOrWhiteSpace(targetFile))
{
throw new ArgumentNullException("targetFile");
}
if (!File.Exists(templateFile))
{
throw new FileNotFoundException(templateFile + " 文件不存在!");
}
#endregion 參數驗證
using (FileStream fs = File.OpenWrite(targetFile))
{
var buffer = ExportHelper.ExportToBuffer(templateFile, containers);
fs.Write(buffer, 0, buffer.Length);
fs.Flush();
}
}
#endregion 導出到本地
#region 導出到Web
public static void ExportToWeb(string templateFile, string targetFile, params SheetFormatterContainer[] containers)
{
#region 參數驗證
if (string.IsNullOrWhiteSpace(templateFile))
{
throw new ArgumentNullException("templateFile");
}
if (string.IsNullOrWhiteSpace(targetFile))
{
throw new ArgumentNullException("targetFile");
}
if (!File.Exists(templateFile))
{
throw new FileNotFoundException(templateFile + " 文件不存在!");
}
#endregion 參數驗證
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(targetFile, System.Text.Encoding.UTF8));
HttpContext.Current.Response.BinaryWrite(ExportHelper.ExportToBuffer(templateFile, containers));
HttpContext.Current.Response.End();
}
#endregion 導出到Web
}
}
Parameter.cs
/*
類:Parameter
描述:參數信息
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.Drawing;
namespace ExcelReport
{
public class Parameter
{
public Parameter()
{
}
public Parameter(string sheetName, string parameterName, Point cellPoint)
{
this.SheetName = sheetName;
this.ParameterName = parameterName;
this.CellPoint = cellPoint;
}
public string SheetName { set; get; }
public string ParameterName { set; get; }
public Point CellPoint { set; get; }
}
}
ParameterCollection.cs
/*
類:ParameterCollection
描述:模板中參數信息的集合
編 碼 人:韓兆新 日期:2015年01月17日
修改記錄:
*/
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml.Serialization;
namespace ExcelReport
{
public class ParameterCollection
{
protected List<Parameter> parameterList = new List<Parameter>();
public Point this[string sheetName, string parameterName]
{
get
{
foreach (Parameter parameter in parameterList)
{
if (parameter.SheetName.Equals(sheetName) && parameter.ParameterName.Equals(parameterName))
{
return parameter.CellPoint;
}
}
return new Point();
}
set
{
bool isExist = false;
foreach (Parameter parameter in parameterList)
{
if (parameter.SheetName.Equals(sheetName) && parameter.ParameterName.Equals(parameterName))
{
isExist = true;
parameter.CellPoint = value;
}
}
if (!isExist)
{
parameterList.Add(new Parameter(sheetName, parameterName, value));
}
}
}
public void Load(string xmlPath)
{
string fileName = xmlPath;
if (File.Exists(fileName))
{
XmlSerializer xmlSerializer = new XmlSerializer(parameterList.GetType());
using (Stream reader = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
parameterList = xmlSerializer.Deserialize(reader) as List<Parameter>;
}
}
else
{
parameterList = new List<Parameter>();
}
}
public void Save(string xmlPath)
{
string fileName = xmlPath;
FileInfo fileInfo = new FileInfo(fileName);
DirectoryInfo directoryInfo = fileInfo.Directory;
if (!directoryInfo.Exists)
{
directoryInfo.Create();
}
XmlSerializer xmlSerializer = new XmlSerializer(parameterList.GetType());
using (Stream writer = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
xmlSerializer.Serialize(writer, parameterList);
}
}
}
}
下載地址:https://github.com/hanzhaoxin/ExcelReport