前言

之前我们曾写过一篇文章 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

如果本文对你有所启发或者帮助,请猛击“好文要顶”,支持原创,支持三石!

FineUI小技巧(7)多表头表格导出的更多相关文章

  1. FineUI小技巧(3)表格导出与文件下载

    需求描述 实际应用中,我们可能需要导出表格内容,或者在页面回发时根据用户权限下载文件(注意,这里的导出与下载,都是在后台进行的,和普通的一个链接下载文件不同). 点击按钮导出表格 由于FineUI 默 ...

  2. FineUI小技巧(1)简单的购物车页面

    起因 最初是一位 FineUI 网友对购物车功能的需求,需要根据产品单价和数量来计算所有选中商品的总价. 这个逻辑最好在前台使用JavaScript实现,如果把这个逻辑移动到后台C#实现,则会导致过多 ...

  3. FineUI小技巧(5)向子窗口传值,向父窗口传值

    前言 FineUI中经常会用到启用IFrame的Window控件,这样有助于从物理上进行代码解耦和.IFrame的引入就会涉及传值问题,如何在父窗口和子窗口之间相互传值呢? 向子窗口传值 向子窗口传值 ...

  4. FineUI小技巧(4)关闭窗体那些事

    前言 FineUI中的Window控件常用作选择.新增或编辑内容.而关闭Window控件却有很多技巧,了解这些技巧有助于项目的快速开发. 如何关闭Window控件 第一个问题就是如何关闭Window控 ...

  5. FineUI小技巧(2)将表单内全部字段禁用、只读、设置无效标识

    需求描述 对表单内的所有字段进行操作也是常见需求,这些操作有: 禁用:表单字段变灰,不响应用户动作. 只读:表单字段不变灰,但不接受用户输入(实际上是设置DOM节点的readonly属性),有触发器的 ...

  6. FineUI小技巧(6)自定义页面回发

    前言 FineUI中的绝大部分回发事件都是由控件触发了,比如按钮的点击事件,下拉列表的改变事件,表格的排序分页事件.但有时我们可能会要自己触发页面回发,这时就要知道怎么使用 JavaScript 来做 ...

  7. sql server 小技巧(7) 导出完整sql server 数据库成一个sql文件,包含表结构及数据

    1. 右健数据库 –> Tasks –> Generate Scripts   2. 选择所有的表   3. 下一步,选择Advanded, Types of data to script ...

  8. 你想的到想不到的 javascript 应用小技巧方法

    javascript 在前端应用体验小技巧继续积累. 事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElemen ...

  9. SmartForms 小技巧

    1.添加空行,保证每一页有固定的打印的表格行数 上图,每页最多打印13行,数据只有11行,自动添加两个空行补齐 代码如下“ "定义变量: data: l_blank type i. &quo ...

随机推荐

  1. js中的位运算

    按位运算符是把操作数看作一系列单独的位,而不是一个数字值.所以在这之前,不得不提到什么是"位": 数值或字符在内存内都是被存储为0和 1的序列,每个0和1被称之为1个位,比如说10 ...

  2. 中文字体font-family常用列表

    Windows的一些: 黑体:SimHei 宋体:SimSun 新宋体:NSimSun 仿宋:FangSong 楷体:KaiTi 仿宋_GB2312:FangSong_GB2312 楷体_GB2312 ...

  3. js 模块化的一些理解和es6模块化学习

    模块化 1 IIFE 2 commonjs 3 浏览器中js的模块化 4 简单理解模块加载器的原理  5 es6 之前在参加百度前端技术学院做的小题目的时候,自己写模块的时候 都是写成立即调用表达式( ...

  4. Vi (Unix及Linux系统下标准的编辑器)VIM (Unix及类Unix系统文本编辑器)

    Vi是Unix及Linux系统下标准的编辑器.学会它后,您将在Linux的世界里畅行无阻.基本上vi可以分为三种状态,分别是命令模式.插入模式,和底行模式. vi编辑器是所有Unix及Linux系统下 ...

  5. SharePoint 2013 图文开发系列之计时器任务

    SharePoint的计时器任务,又称TimerJob,由服务里的Timer服务执行,在管理中心管理,是一个类似于Windows任务计划的功能,方便定时执行一些需要的功能,以免影响服务器性能. 在Sh ...

  6. MyEclipse 2016 CI 3发布

    JSjet 语法高亮 受够了Eclipse中的JavaScript编码体验?那就来试试MyEclipse 2016 CI 3.JSjet改进了JavaScript编码的语法高亮功能,大大提升了代码的可 ...

  7. Upgrade from SharePoint 2010 to SharePoint 2016

    [转]http://nikcharlebois.com/upgrade-from-sharepoint-2010-to-sharepoint-2016/ In this blog, I will go ...

  8. Android—android与js交互以及相互传参

    Android中可以通过WebView来实现与js的交互,让用户可以在android客户端看到js写的页面,接下来为大家介绍的就是怎样实现此功能: 首先android项目目录下有“assets”文件夹 ...

  9. Activity详解三 启动activity并返回结果

    首先看演示: 1 简介 .如果想在Activity中得到新打开Activity 关闭后返回的数据,需要使用系统提供的startActivityForResult(Intent intent, int ...

  10. 你真的了解UITabBarController吗?

    一:首先查看一下关于UITabBarController的定义 NS_CLASS_AVAILABLE_IOS(2_0) @interface UITabBarController : UIViewCo ...