FineUI小技巧(7)多表头表格导出
前言
之前我们曾写过一篇文章 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)多表头表格导出的更多相关文章
- FineUI小技巧(3)表格导出与文件下载
需求描述 实际应用中,我们可能需要导出表格内容,或者在页面回发时根据用户权限下载文件(注意,这里的导出与下载,都是在后台进行的,和普通的一个链接下载文件不同). 点击按钮导出表格 由于FineUI 默 ...
- FineUI小技巧(1)简单的购物车页面
起因 最初是一位 FineUI 网友对购物车功能的需求,需要根据产品单价和数量来计算所有选中商品的总价. 这个逻辑最好在前台使用JavaScript实现,如果把这个逻辑移动到后台C#实现,则会导致过多 ...
- FineUI小技巧(5)向子窗口传值,向父窗口传值
前言 FineUI中经常会用到启用IFrame的Window控件,这样有助于从物理上进行代码解耦和.IFrame的引入就会涉及传值问题,如何在父窗口和子窗口之间相互传值呢? 向子窗口传值 向子窗口传值 ...
- FineUI小技巧(4)关闭窗体那些事
前言 FineUI中的Window控件常用作选择.新增或编辑内容.而关闭Window控件却有很多技巧,了解这些技巧有助于项目的快速开发. 如何关闭Window控件 第一个问题就是如何关闭Window控 ...
- FineUI小技巧(2)将表单内全部字段禁用、只读、设置无效标识
需求描述 对表单内的所有字段进行操作也是常见需求,这些操作有: 禁用:表单字段变灰,不响应用户动作. 只读:表单字段不变灰,但不接受用户输入(实际上是设置DOM节点的readonly属性),有触发器的 ...
- FineUI小技巧(6)自定义页面回发
前言 FineUI中的绝大部分回发事件都是由控件触发了,比如按钮的点击事件,下拉列表的改变事件,表格的排序分页事件.但有时我们可能会要自己触发页面回发,这时就要知道怎么使用 JavaScript 来做 ...
- sql server 小技巧(7) 导出完整sql server 数据库成一个sql文件,包含表结构及数据
1. 右健数据库 –> Tasks –> Generate Scripts 2. 选择所有的表 3. 下一步,选择Advanded, Types of data to script ...
- 你想的到想不到的 javascript 应用小技巧方法
javascript 在前端应用体验小技巧继续积累. 事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElemen ...
- SmartForms 小技巧
1.添加空行,保证每一页有固定的打印的表格行数 上图,每页最多打印13行,数据只有11行,自动添加两个空行补齐 代码如下“ "定义变量: data: l_blank type i. &quo ...
随机推荐
- js正则表达式图形化工具-rline
github地址:https://github.com/finance-sh/rline 在线demo: http://lihuazhai.com/demo/test.html 这是一个js正则表达式 ...
- Ajax表单异步上传(包括文件域)
起因 做前台页面时,需要调用WebAPI的Post请求,发送一些字段和文件(相当于把表单通过ajax异步发送出去,得到返回结果),然后得到返回值判断是否成功. 尝试 先是尝试了一下 "jQu ...
- Android高效计算——RenderScript(二)
3 RenderScript运行时层与反射层 3.1 RenderScript运行时层 RenderScript运行时层是指.rs代码运行时所在的层级.当对安卓项目进行编译的时候,.rs或者.rsh中 ...
- Tableview中Dynamic Prototypes动态表的使用
Tableview时IOS中应用非常广泛的控件,当需要动态的添加多条不同的数据时,需要用动态表来实现,下面给出一个小例子,适用于不确定Section的数目,并且每个Section中的行数也不同的情况, ...
- Lucene 时间排序
在Lucene4.4中,想要实现搜索结果按照时间倒序的效果:如果两个文档得分相同,那么就按照发布时间倒序排列:否则就按照分数排列.这种效果在Lucene4.6中实现起来极其简单,直接利用search接 ...
- sublime text 乱码生成.dump问题的解决方法
title: sublime text 乱码生成.dump问题的解决方法 tags: sublime text,sublime text 3,.dump,乱码 grammar_cjkRuby: tru ...
- 比Ansible更吊的自动化运维工具,自动化统一安装部署自动化部署udeploy 1.0 版本发布
新增功能: 逻辑与业务分离,完美实现逻辑与业务分离,业务实现统一shell脚本开发,由框架统一调用. 并发多线程部署,不管多少台服务器,多少个服务,同时发起线程进行更新.部署.启动. 提高list规则 ...
- 学习tensorflow之mac上安装tensorflow
背景 听说谷歌的第二代机器学习的框架tensorflow开源了,我也心血来潮去探探大牛的产品.怎奈安装就折腾了一天,现在整理出来备忘. tensorflow官方网站给出的安装步骤很简单: # Only ...
- ASP.NET获取真正的客户端IP地址的6种方法
Request.ServerVariables("REMOTE_ADDR") 来取得客户端的IP地址,但如果客户端是使用代理服务器来访问,那取到的就是代理服务器的IP地址,而不是真 ...
- java实现文件变化监控
一. spring配置文件:application.xml <?xml version="1.0" encoding="UTF-8"?> <b ...