继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

FineUI多表头表格导出小技巧

翻翻过去那场雪
关注TA
已关注
手记 60
粉丝 7
获赞 25

在 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
    考试成绩,           // 当前列对象
    分组一     // 父列对象
]

 

逻辑点到为止,剩下的就来看代码了,我们把逻辑封装到自定义类中:

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 标签结构的转换,虽然实现稍微复杂了点,但只要思路清晰,最终还是能否完整呈现的。

 

打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP