以前咱們曾寫過一篇文章 FineUI小技巧(3)表格導出與文件下載,對於在 FineUI 中導出表格數據進行了詳細描述。今天咱們要更進一步,介紹下如何導出多表頭表格。 javascript
在 ASPX 中,咱們經過 GroupField 列來定義多表頭,以下所示: html
<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 集合來定義子列,從而實現多表頭的效果: java
若是照搬以前的邏輯,咱們和容易寫出以下的導出代碼(處理數組很簡單,循環搞定): 數組
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(); }
打開導出的文件,咱們會發現全部子列都不見了: app
這樣很容易理解,由於在後臺,FineUI 也是按照樹狀的結構存儲 Grid1.Columns 屬性的: 函數
[{ "text": "分組一", "columns": [{ "text": "姓名" }, { "text": "性別" }, { "text": "考試成績", "columns": [{ "text": "語文成績" }, { "text": "數學成績" }, { "text": "總成績" }] }, ] }, { "text": "所學專業" }, { "text": "註冊日期" }]
這個還真很差辦,由於 table 標籤不像它看起來那麼簡單,每一個單元格均可能要設置 rowspan 和 colspan,來看下咱們最終須要的結構: ui
最終生成的 table 標籤以下所示: spa
<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 的參數也不相同,看來這個轉換要本身手工作了。 調試
實現轉換以前,咱們先來總結兩個關鍵的邏輯: excel
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