• FineUI小技巧(7)多表头表格导出


    前言

    之前我们曾写过一篇文章 FineUI小技巧(3)表格导出与文件下载,对于在 FineUI 中导出表格数据进行了详细描述。今天我们要更进一步,介绍下如何导出多表头表格。

    多表头表格的标签定义

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

     

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

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

    源代码与在线示例

    本系列所有文章的源代码均可自行下载:http://fineui.codeplex.com/

    在线示例:http://fineui.com/demo/#/demo/grid/grid_excel_groupfield.aspx

    如果本文对你有所启发或者帮助,请猛击“好文要顶”,支持原创,支持三石!

  • 相关阅读:
    一卦,测一年运气
    测一下我心中想的事
    一卦,测一下我心里想的事
    这一卦,学到了不少东西
    癸山丁向下卦(七运)
    起卦测我心里想的事
    现在的卦,越来越看不懂了
    luogu P2759 奇怪的函数 |二分答案
    luogu P2515 [HAOI2010]软件安装 |Tarjan+树上背包
    luogu P2343 宝石管理系统 |分块+堆
  • 原文地址:https://www.cnblogs.com/sanshi/p/4104411.html
Copyright © 2020-2023  润新知