• [原创]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秘密花园》系列文章由三生石上原创,博客园首发,转载请注明出处。文章目录 官方论坛

  • 相关阅读:
    Oracle建立表空间和用户
    fscanf()函数具体解释
    三层架构(我的理解及具体分析)
    ListView嵌套ListView优化
    Android xml 解析
    玩转Web之servlet(三)---一张图看懂B/S架构
    jquery.scrollTo-min.js
    C#中MessageBox使用方法大全(附效果图)
    hdu 1882 Strange Billboard(位运算+枚举)
    MySQL 通配符学习小结
  • 原文地址:https://www.cnblogs.com/sanshi/p/2778332.html
Copyright © 2020-2023  润新知