[原创]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打开后可见:

image

 

 

将GridView导出为Excel文件

首先来看下如何将Asp.Net的GridView导出为Excel文件,网上已经有很好的参考资料,这是博客园中的中文译本

概括说来有如下几个技巧:

  1. 必须重载VerifyRenderingInServerForm函数,函数体留空,否则会报错;
  2. 如果GridView中包含CheckBox,LinkButton等控件或者分页时,需要设置页面属性EnableEventValidation="false",否则会报错;
  3. 可以在导出数据之前将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:  }

这里做了如下几件事情:

    1. 在ASPX标签中设置页面的EnableEventValidation属性为false;
    2. 重载VerifyRenderingInServerForm函数并留空;
    3. 在导出Excel的按钮事件中,首先设置响应头content-disposition,这样浏览器才会将此响应作为文件下载;
    4. 将GridView控件重新渲染到HtmlTextWriter流中;
    5. 读出HtmlTextWriter流的内容,并作为响应体的正文。

 

我们来看下页面的UI显示和导出的Excel的内容:

image

 

image

 

优化GridView导出的Excel文件

在上面导出的Excel表格中,有两个地方需要进一步优化:

  1. “是否在校”列不应该使用复选框,而应该是静态文本;
  2. “分组”列的图片没有显示出来,因为表格中的图片路径是相对路径,这里需要转换为绝对路径。

 

实现起来也很简单,只需要遍历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:  }

最终的结果:

image

 

 

将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的属性:

  1. EnableAjax=false,由于在按钮的点击事件中手工修改了响应头和响应正文,就不能使用FineUI默认的Ajax回发;
  2. 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就隐藏在页面的底部,如果你观察生成的页面源代码的话,就能看到类似的代码:

image 

 

而这些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:  }

正如你看到的,这里面也对复选框和图片进行了处理:

  1. 因为FineUI最终将复选框渲染成类似 <div class="box-grid-static-checkbox"></div>的标签,所以我们要将其替换成静态文本;
  2. 对图片的处理则是在图片路径前面加载前缀,以生成图片的绝对路径。

 

来看最终的效果:

image

 

 

小结

将表格内容导出为Excel文件在实际项目中经常会遇到,本篇文章从导出Asp.Net的GridView开始,循序渐进地讲解如何导出FineUI的Grid控件,最终得到我们满意的结果。

下一篇文章我们会开始新的征程,接着讲解树控件、选项卡控件、手风琴控件以及窗体控件。

 

注:《FineUI秘密花园》系列文章由三生石上原创,博客园首发,转载请注明出处。文章目录 官方论坛

posted @ 2012-11-20 08:13 三生石上(FineUI控件) 阅读(...) 评论(...) 编辑 收藏