將表格內容導出為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控件,最終得到我們滿意的結果。
下一篇文章我們會開始新的征程,接着講解樹控件、選項卡控件、手風琴控件以及窗體控件。