前言
之前我们曾写过一篇文章 FineUI小技巧(3)表格导出与文件下载,对于在 FineUI 中导出表格数据进行了详细描述。今天我们要更进一步,介绍下如何导出多表头表格。
多表头表格的标签定义
在 ASPX 中,我们通过 GroupField 列来定义多表头,如下所示:
<f:Grid ID="Grid1" Title="表格" EnableCollapse="true" ShowBorder="true" ShowHeader="true" Width="800px" runat="server" DataKeyNames="Id,Name"> <Columns> <f:TemplateField ColumnID="tfNumber" Width="60px"> <ItemTemplate> <span id="spanNumber" runat="server"><%# Container.DataItemIndex + 1 %></span> </ItemTemplate> </f:TemplateField> <f:GroupField EnableLock="true" HeaderText="分组一" TextAlign="Center"> <Columns> <f:BoundField Width="100px" DataField="Name" DataFormatString="{0}" HeaderText="姓名" /> <f:TemplateField ColumnID="tfGender" Width="80px" HeaderText="性别" TextAlign="Center"> <ItemTemplate> <asp:Label ID="labGender" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label> </ItemTemplate> </f:TemplateField> <f:GroupField EnableLock="true" HeaderText="考试成绩" TextAlign="Center"> <Columns> <f:BoundField EnableLock="true" Width="80px" DataField="ChineseScore" SortField="ChineseScore" HeaderText="语文成绩" TextAlign="Center" /> <f:BoundField EnableLock="true" Width="80px" DataField="MathScore" SortField="MathScore" HeaderText="数学成绩" TextAlign="Center" /> <f:BoundField EnableLock="true" Width="80px" DataField="TotalScore" SortField="TotalScore" HeaderText="总成绩" TextAlign="Center" /> </Columns> </f:GroupField> </Columns> </f:GroupField> <f:BoundField ExpandUnusedSpace="True" DataField="Major" HeaderText="所学专业" /> <f:BoundField Width="100px" DataField="LogTime" DataFormatString="{0:yy-MM-dd}" HeaderText="注册日期" /> </Columns> </f:Grid>
这是一个树状的结构,通过 GroupField 的 Columns 集合来定义子列,从而实现多表头的效果:
老方法已经不再奏效
如果照搬之前的逻辑,我们和容易写出如下的导出代码(处理数组很简单,循环搞定):
protected void Button1_Click(object sender, EventArgs e) { Response.ClearContent(); Response.AddHeader("content-disposition", "attachment; filename=myexcel.xls"); Response.ContentType = "application/excel"; Response.ContentEncoding = System.Text.Encoding.UTF8; Response.Write(GetGridTableHtml(Grid1)); Response.End(); } private string GetGridTableHtml(Grid grid) { StringBuilder sb = new StringBuilder(); sb.Append("<meta http-equiv="content-type" content="application/excel; charset=UTF-8"/>"); sb.Append("<table cellspacing="0" rules="all" border="1" style="border-collapse:collapse;">"); sb.Append("<tr>"); foreach (GridColumn column in grid.Columns) { sb.AppendFormat("<td>{0}</td>", column.HeaderText); } sb.Append("</tr>"); foreach (GridRow row in grid.Rows) { sb.Append("<tr>"); foreach (GridColumn column in grid.Columns) { string html = row.Values[column.ColumnIndex].ToString(); if (column.ColumnID == "tfNumber") { html = (row.FindControl("spanNumber") as System.Web.UI.HtmlControls.HtmlGenericControl).InnerText; } else if (column.ColumnID == "tfGender") { html = (row.FindControl("labGender") as AspNet.Label).Text; } sb.AppendFormat("<td>{0}</td>", html); } sb.Append("</tr>"); } sb.Append("</table>"); return sb.ToString(); }
打开导出的文件,我们会发现所有子列都不见了:
这样很容易理解,因为在后台,FineUI 也是按照树状的结构存储 Grid1.Columns 属性的:
[{ "text": "分组一", "columns": [{ "text": "姓名" }, { "text": "性别" }, { "text": "考试成绩", "columns": [{ "text": "语文成绩" }, { "text": "数学成绩" }, { "text": "总成绩" }] }, ] }, { "text": "所学专业" }, { "text": "注册日期" }]
树状结构转换为 table 标签
这个还真不好办,因为 table 标签不像它看起来那么简单,每个单元格都可能要设置 rowspan 和 colspan,来看下我们最终需要的结构:
最终生成的 table 标签如下所示:
<table cellspacing="0" rules="all" border="1" style="border-collapse:collapse;"> <tr> <th rowspan="3"></th> <th colspan="5" style="text-align:center;">分组一</th> <th rowspan="3">所学专业</th> <th rowspan="3">注册日期</th> </tr> <tr> <th rowspan="2">姓名</th> <th rowspan="2">性别</th> <th colspan="3" style="text-align:center;">考试成绩</th> </tr> <tr> <th>语文成绩</th> <th>数学成绩</th> <th>总成绩</th> </tr> </table>
最终生成了 3 行数据,每一行中 th 的个数不尽相同,每个 th 的参数也不相同,看来这个转换要自己手工做了。
实现转换之前,我们先来总结两个关键的逻辑:
1. 如果某列有子列,则更改本列的 colspan,并且增加所有父列(向上追溯)的 colspan
2. 如果下一行有数据,则增加上一行(向上追溯)中没有子项的列的 rowspan
每个 th 在 C# 代码中通过 object[] 来表达,比如 [考试成绩] 这一列最终的结构是:
[ 1, // rowspan 3, // colspan 考试成绩, // 当前列对象 分组一 // 父列对象 ]
逻辑点到为止,剩下的就来看代码了,我们把逻辑封装到自定义类中:
/// <summary> /// 处理多表头的类 /// </summary> public class MultiHeaderTable { // 包含 rowspan,colspan 的多表头,方便生成 HTML 的 table 标签 public List<List<object[]>> MultiTable = new List<List<object[]>>(); // 最终渲染的列数组 public List<GridColumn> Columns = new List<GridColumn>(); public void ResolveMultiHeaderTable(GridColumnCollection columns) { List<object[]> row = new List<object[]>(); foreach (GridColumn column in columns) { object[] cell = new object[4]; cell[0] = 1; // rowspan cell[1] = 1; // colspan cell[2] = column; cell[3] = null; row.Add(cell); } ResolveMultiTable(row, 0); ResolveColumns(row); } private void ResolveColumns(List<object[]> row) { foreach (object[] cell in row) { GroupField groupField = cell[2] as GroupField; if (groupField != null && groupField.Columns.Count > 0) { List<object[]> subrow = new List<object[]>(); foreach (GridColumn column in groupField.Columns) { subrow.Add(new object[] { 1, 1, column, groupField }); } ResolveColumns(subrow); } else { Columns.Add(cell[2] as GridColumn); } } } private void ResolveMultiTable(List<object[]> row, int level) { List<object[]> nextrow = new List<object[]>(); foreach (object[] cell in row) { GroupField groupField = cell[2] as GroupField; if (groupField != null && groupField.Columns.Count > 0) { // 如果当前列包含子列,则更改当前列的 colspan,以及增加父列(向上递归)的colspan cell[1] = Convert.ToInt32(groupField.Columns.Count); PlusColspan(level - 1, cell[3] as GridColumn,groupField.Columns.Count - 1); foreach (GridColumn column in groupField.Columns) { nextrow.Add(new object[] { 1, 1, column, groupField }); } } } MultiTable.Add(row); // 如果当前下一行,则增加上一行(向上递归)中没有子列的列的 rowspan if (nextrow.Count > 0) { PlusRowspan(level); ResolveMultiTable(nextrow, level + 1); } } private void PlusRowspan(int level) { if (level < 0) { return; } foreach (object[] cells in MultiTable[level]) { GroupField groupField = cells[2] as GroupField; if (groupField != null && groupField.Columns.Count > 0) { // ... } else { cells[0] = Convert.ToInt32(cells[0]) + 1; } } PlusRowspan(level - 1); } private void PlusColspan(int level, GridColumn parent, int plusCount) { if (level < 0) { return; } foreach (object[] cells in MultiTable[level]) { GridColumn column = cells[2] as GridColumn; if (column == parent) { cells[1] = Convert.ToInt32(cells[1]) + plusCount; PlusColspan(level - 1, cells[3] as GridColumn, plusCount); } } } }
其实主要的逻辑就上面提到的两点,然后需要好几个递归函数来一块完成任务。
导出的代码调用如下:
protected void Button1_Click(object sender, EventArgs e) { Response.ClearContent(); Response.AddHeader("content-disposition", "attachment; filename=myexcel.xls"); Response.ContentType = "application/excel"; Response.ContentEncoding = System.Text.Encoding.UTF8; Response.Write(GetGridTableHtml(Grid1)); Response.End(); } private string GetGridTableHtml(Grid grid) { StringBuilder sb = new StringBuilder(); MultiHeaderTable mht = new MultiHeaderTable(); mht.ResolveMultiHeaderTable(Grid1.Columns); sb.Append("<meta http-equiv="content-type" content="application/excel; charset=UTF-8"/>"); sb.Append("<table cellspacing="0" rules="all" border="1" style="border-collapse:collapse;">"); foreach (List<object[]> rows in mht.MultiTable) { sb.Append("<tr>"); foreach (object[] cell in rows) { int rowspan = Convert.ToInt32(cell[0]); int colspan = Convert.ToInt32(cell[1]); GridColumn column = cell[2] as GridColumn; sb.AppendFormat("<th{0}{1}{2}>{3}</th>", rowspan != 1 ? " rowspan="" + rowspan + """ : "", colspan != 1 ? " colspan="" + colspan + """ : "", colspan != 1 ? " style="text-align:center;"" : "", column.HeaderText); } sb.Append("</tr>"); } foreach (GridRow row in grid.Rows) { sb.Append("<tr>"); foreach (GridColumn column in mht.Columns) { string html = row.Values[column.ColumnIndex].ToString(); if (column.ColumnID == "tfNumber") { html = (row.FindControl("spanNumber") as System.Web.UI.HtmlControls.HtmlGenericControl).InnerText; } else if (column.ColumnID == "tfGender") { html = (row.FindControl("labGender") as AspNet.Label).Text; } sb.AppendFormat("<td>{0}</td>", html); } sb.Append("</tr>"); } sb.Append("</table>"); return sb.ToString(); }
最终导出的文件结构如下所示:
原创不易,请点赞
短短一篇文章,几行代码,看似轻描淡写,实则是花了很大功夫调试。你觉得作者在整个过程中做了多少次导出文件的动作?才最终实现了这个效果!
。
。
10?
。
。
。
20?
。
。
。
。
30?
。
。
。
。
。
。
40?
。
。
。
。
。
。
。
请恕作者愚钝,足足不下 50 次:
本章小结
本篇文章介绍了如何导出多表头表格,重点在于树状结构到 table 标签结构的转换,虽然实现稍微复杂了点,但只要思路清晰,最终还是能否完整呈现的。
源代码与在线示例
本系列所有文章的源代码均可自行下载:http://fineui.codeplex.com/
在线示例:http://fineui.com/demo/#/demo/grid/grid_excel_groupfield.aspx
如果本文对你有所启发或者帮助,请猛击“好文要顶”,支持原创,支持三石!