[原创]FineUI秘密花园(二十二) — 表格之导出Excel文件
将表格内容导出为Excel文件是实际项目中的常见需求,怎么来实现呢?
导出文件的格式
首先我们需要理解的一点是,导出的文件其实一个HTML片段,只不过Excel会按照自身的格式自动格式化而已。
来看一个导出文件的典型示例:
1: <table border="1">
2: <tr><th>姓名</th><th>性别</th></tr>
3: <tr><th>张三</th><th>男</th></tr>
4: <tr><th>李四</th><th>男</th></tr>
5: <tr><th>春花</th><th>女</th></tr>
6: </table>
将此文件后缀改成xls,并用Excel打开后可见:
将GridView导出为Excel文件
首先来看下如何将Asp.Net的GridView导出为Excel文件,网上已经有很好的参考资料,这是博客园中的中文译本。
概括说来有如下几个技巧:
- 必须重载VerifyRenderingInServerForm函数,函数体留空,否则会报错;
- 如果GridView中包含CheckBox,LinkButton等控件或者分页时,需要设置页面属性EnableEventValidation="false",否则会报错;
- 可以在导出数据之前将GridView中的CheckBox等控件用Literal控件代替。
下面来看一个示例,示例的ASPX标签结构如下:
1: <asp:GridView ID="GridView1" Width="900px" DataKeyNames="Id,Name" AutoGenerateColumns="False"
2: runat="server">
3: <Columns>
4: <asp:TemplateField>
5: <ItemTemplate>
6: <asp:Label ID="Label1" runat="server" Text='<%# Container.DataItemIndex + 1 %>'></asp:Label>
7: </ItemTemplate>
8: </asp:TemplateField>
9: <asp:BoundField DataField="Name" HeaderText="姓名" />
10: <asp:TemplateField HeaderText="性别">
11: <ItemTemplate>
12: <asp:Label ID="Label2" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label>
13: </ItemTemplate>
14: </asp:TemplateField>
15: <asp:BoundField DataField="EntranceYear" HeaderText="入学年份" />
16: <asp:CheckBoxField DataField="AtSchool" HeaderText="是否在校" />
17: <asp:HyperLinkField HeaderText="所学专业" DataTextField="Major" DataTextFormatString="{0}"
18: DataNavigateUrlFields="Major" DataNavigateUrlFormatString="http://gsa.ustc.edu.cn/search?q={0}"
19: Target="_blank" />
20: <asp:ImageField DataImageUrlField="Group" DataImageUrlFormatString="~/images/16/{0}.png"
21: HeaderText="分组">
22: </asp:ImageField>
23: </Columns>
24: </asp:GridView>
表格初始化代码省略,我们来看下和导出相关的代码:
1: public override void VerifyRenderingInServerForm(Control control)
2: {
3:
4: }
5:
6: protected void Button2_Click(object sender, EventArgs e)
7: {
8: // ResolveGridView(GridView1);
9:
10: Response.ClearContent();
11: Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
12: Response.ContentType = "application/excel";
13:
14: StringWriter sw = new StringWriter();
15: HtmlTextWriter htw = new HtmlTextWriter(sw);
16: GridView1.RenderControl(htw);
17:
18: Response.Write(sw.ToString());
19: Response.End();
20: }
这里做了如下几件事情:
- 在ASPX标签中设置页面的EnableEventValidation属性为false;
- 重载VerifyRenderingInServerForm函数并留空;
- 在导出Excel的按钮事件中,首先设置响应头content-disposition,这样浏览器才会将此响应作为文件下载;
- 将GridView控件重新渲染到HtmlTextWriter流中;
- 读出HtmlTextWriter流的内容,并作为响应体的正文。
我们来看下页面的UI显示和导出的Excel的内容:
优化GridView导出的Excel文件
在上面导出的Excel表格中,有两个地方需要进一步优化:
- “是否在校”列不应该使用复选框,而应该是静态文本;
- “分组”列的图片没有显示出来,因为表格中的图片路径是相对路径,这里需要转换为绝对路径。
实现起来也很简单,只需要遍历GridView实例,修改其中的复选框控件和图片控件即可,如下所示:
1: private void ResolveGridView(Control ctrl)
2: {
3: for (int i = 0; i < ctrl.Controls.Count; i++)
4: {
5: // 图片的完整URL
6: if (ctrl.Controls[i].GetType() == typeof(AspNet.Image))
7: {
8: AspNet.Image img = ctrl.Controls[i] as AspNet.Image;
9: img.ImageUrl = Request.Url.AbsoluteUri.Replace(Request.Url.AbsolutePath, Page.ResolveUrl(img.ImageUrl));
10: }
11:
12: // 将CheckBox控件转化为静态文本
13: if (ctrl.Controls[i].GetType() == typeof(AspNet.CheckBox))
14: {
15: Literal lit = new Literal();
16: lit.Text = (ctrl.Controls[i] as AspNet.CheckBox).Checked ? "√" : "×";
17: ctrl.Controls.RemoveAt(i);
18: ctrl.Controls.AddAt(i, lit);
19: }
20:
21: if (ctrl.Controls[i].HasControls())
22: {
23: ResolveGridView(ctrl.Controls[i]);
24: }
25: }
26: }
最终的结果:
将Grid导出为Excel文件
前面介绍了如何将Asp.Net的GridView控件导出为Excel,简单来说就是将GridView重新渲染到HtmlTextWriter流中,然后将流内容输入为响应正文。
那么我们是否也可以照葫芦画瓢来实现将Grid导出为Excel文件呢?
下面就来试一试,首先来看ASPX标签结构:
1: <ext:Grid ID="Grid1" Title="表格" ShowBorder="true" ShowHeader="true" Width="900px"
2: AutoHeight="true" runat="server" DataKeyNames="Id,Name">
3: <Columns>
4: <ext:TemplateField Width="60px">
5: <ItemTemplate>
6: <asp:Label ID="Label1" runat="server" Text='<%# Container.DataItemIndex + 1 %>'></asp:Label>
7: </ItemTemplate>
8: </ext:TemplateField>
9: <ext:BoundField Width="100px" DataField="Name" DataFormatString="{0}" HeaderText="姓名" />
10: <ext:TemplateField Width="60px" HeaderText="性别">
11: <ItemTemplate>
12: <asp:Label ID="Label3" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label>
13: </ItemTemplate>
14: </ext:TemplateField>
15: <ext:BoundField Width="60px" DataField="EntranceYear" HeaderText="入学年份" />
16: <ext:CheckBoxField Width="60px" RenderAsStaticField="true" DataField="AtSchool" HeaderText="是否在校" />
17: <ext:HyperLinkField HeaderText="所学专业" DataToolTipField="Major" DataTextField="Major"
18: DataTextFormatString="{0}" DataNavigateUrlFields="Major" DataNavigateUrlFormatString="http://gsa.ustc.edu.cn/search?q={0}"
19: DataNavigateUrlFieldsEncode="true" Target="_blank" ExpandUnusedSpace="True" />
20: <ext:ImageField Width="60px" DataImageUrlField="Group" DataImageUrlFormatString="~/images/16/{0}.png"
21: HeaderText="分组"></ext:ImageField>
22: <ext:BoundField Width="100px" DataField="LogTime" DataFormatString="{0:yy-MM-dd}"
23: HeaderText="注册日期" />
24: </Columns>
25: </ext:Grid>
26: <ext:Button ID="Button1" EnableAjax="false" DisableControlBeforePostBack="false"
27: runat="server" Text="将Grid导出为Excel文件" OnClick="Button1_Click">
28: </ext:Button>
请注意这里ext:Button的属性:
- EnableAjax=false,由于在按钮的点击事件中手工修改了响应头和响应正文,就不能使用FineUI默认的Ajax回发;
- DisableControlBeforePostBack=false,这个属性本来是让按钮在点击后立即变灰,然后在Ajax响应后再次启用,放置多次点击,这里就不需要了。
来看下后台按钮事件处理函数:
1: protected void Button1_Click(object sender, EventArgs e)
2: {
3: Response.ClearContent();
4: Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
5: Response.ContentType = "application/excel";
6:
7: StringWriter sw = new StringWriter();
8: HtmlTextWriter htw = new HtmlTextWriter(sw);
9: Grid1.RenderControl(htw);
10:
11: Response.Write(sw.ToString());
12: Response.End();
13: }
点击导出按钮,用记事本打开生成的Excel文件:
1: <div id="Grid1_wrapper">
2: <div id="Grid1_tpls" class="x-grid-tpls x-hide-display">
3: <div class="x-grid-tpl" id="Grid1_c0r0">
4: <span id="Grid1_c0r0_Label1">1</span>
5: </div>
6: <div class="x-grid-tpl" id="Grid1_c2r0">
7: <span id="Grid1_c2r0_Label3">女</span>
8: </div>
9: <div class="x-grid-tpl" id="Grid1_c0r1">
10: <span id="Grid1_c0r1_Label1">2</span>
11: </div>
12: // 省略类似的部分...
13: </div>
14: </div>
结果只看到一些DIV,而非Table结构。仔细观察这些DIV,你会发现它们是模板列渲染后的值,其他列的值哪去了?
如果你理解extjs的工作原理,这个结果并不奇怪。
Grid渲染到页面中的只有一些简单的DIV标签,至于内部的内容则是通过JavaScript来生成的,这个JavaScript就隐藏在页面的底部,如果你观察生成的页面源代码的话,就能看到类似的代码:
而这些Values值正是渲染后的HTML片段,并且我们可以通过行GridRow的Values属性拿到这些值!
如此一来就好办了,拿到这些值后手工拼装成一个表格不就可以了,最终的代码如下所示:
1: protected void Button1_Click(object sender, EventArgs e)
2: {
3: Response.ClearContent();
4: Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
5: Response.ContentType = "application/excel";
6: Response.Write(GetGridTableHtml(Grid1));
7: Response.End();
8: }
9:
10: private string GetGridTableHtml(Grid grid)
11: {
12: StringBuilder sb = new StringBuilder();
13:
14: sb.Append("<table cellspacing=\"0\" rules=\"all\" border=\"1\" style=\"border-collapse:collapse;\">");
15:
16: sb.Append("<tr>");
17: foreach (GridColumn column in grid.Columns)
18: {
19: sb.AppendFormat("<td>{0}</td>", column.HeaderText);
20: }
21: sb.Append("</tr>");
22:
23:
24: foreach (GridRow row in grid.Rows)
25: {
26: sb.Append("<tr>");
27: foreach (object value in row.Values)
28: {
29: string html = value.ToString();
30: // 处理CheckBox
31: if (html.Contains("box-grid-static-checkbox"))
32: {
33: if (html.Contains("box-grid-static-checkbox-uncheck"))
34: {
35: html = "×";
36: }
37: else
38: {
39: html = "√";
40: }
41: }
42:
43: // 处理图片
44: if (html.Contains("<img"))
45: {
46: string prefix = Request.Url.AbsoluteUri.Replace(Request.Url.AbsolutePath, "");
47: html = html.Replace("src=\"", "src=\"" + prefix);
48: }
49:
50: sb.AppendFormat("<td>{0}</td>", html);
51: }
52: sb.Append("</tr>");
53: }
54:
55: sb.Append("</table>");
56:
57: return sb.ToString();
58: }
正如你看到的,这里面也对复选框和图片进行了处理:
- 因为FineUI最终将复选框渲染成类似 <div class="box-grid-static-checkbox"></div>的标签,所以我们要将其替换成静态文本;
- 对图片的处理则是在图片路径前面加载前缀,以生成图片的绝对路径。
来看最终的效果:
小结
将表格内容导出为Excel文件在实际项目中经常会遇到,本篇文章从导出Asp.Net的GridView开始,循序渐进地讲解如何导出FineUI的Grid控件,最终得到我们满意的结果。
下一篇文章我们会开始新的征程,接着讲解树控件、选项卡控件、手风琴控件以及窗体控件。