代码之家  ›  专栏  ›  技术社区  ›  Nathan Taylor

ASP.NET-显示透视表的理想控件

  •  1
  • Nathan Taylor  · 技术社区  · 15 年前

    我正试图在数据绑定控件中显示一组表格数据,但我需要透视表,使各个记录都是表列而不是表行。最终结果是一个具有固定列数和可变行数的表,每个行显示所有记录的单个字段。 like this . 由于必须为每个字段而不是每个记录定义<tr/>标记,因此中继器不是解决此问题的合适方法。我想知道的是,是否有任何内置的ASP.NET控件可以实现我想要的。我在看ListView控件,但是我不确定它是否真的能够描述我所描述的内容。

    实际上,假设记录如下:

           Number Yardage Par  ...
    (Hole)   1     300     4   ...
    (Hole)   2     275     4   ...
    (Hole)   3     390     5   ...
    (Hole)  ...    ...    ...  ...
    

    我需要显示:

               1   2   3   ...
    Yardage:  300 275 390  ...
        Par:   4   4   5   ...
        ...:  ... ... ...  ...
    

    与<tr/>标签作斗争的一个可行的选择当然是使用display:inline<divs>和一些优雅的css,但如果我能保留<table>结构,那将是理想的。

    谢谢!

    4 回复  |  直到 13 年前
        1
  •  5
  •   joboy    15 年前

    我最近遇到了同样的问题,当寻找答案时,发现大多数解决方案都是围绕源数据进行工作的。

    我突然想到问题不在于源数据,而在于我们希望以何种方式呈现它。虽然上面的Chris的回答确实希望在渲染点改变数据,但我发现如果我需要模板化的网格视图,它就不够灵活。当时,可能更好的解决方案是捕获网格的表HTML标记并更改该标记—这实际上意味着该解决方案可以应用于任何呈现表标记的控件,并且其中包含的任何模板控件都将继续工作。

    由于时间压力,我只使用网格视图实现了解决方案;我最初希望生成一个模板服务器控件,如果在其中放置了任何控件,则会检查其标记输出并透视其中包含的任何表)

    无论如何,这里是实现网格视图的解决方案所需的代码。

    自定义服务器控制代码

    [ToolboxData("<{0}:PivotGridView runat=server></{0}:PivotGridView>")]
    public class PivotGridView : GridView
    {
        bool _pivotGrid = true;
    
        [Browsable(true)]
        public bool PivotGrid
        {
            get 
            { 
                return _pivotGrid; 
            }
            set 
            { 
                _pivotGrid = value; 
                EnsureChildControls(); 
            }
        }
    
        protected override void RenderContents(HtmlTextWriter output)
        {
            if (_pivotGrid)
            {
                System.IO.TextWriter baseOutputTextWriter = new System.IO.StringWriter();
                HtmlTextWriter baseOutputHtmlWriter = new HtmlTextWriter(baseOutputTextWriter);
    
                base.RenderContents(baseOutputHtmlWriter);
    
                output.Write(HtmlParserService.PivotHtmlTableMarkup(baseOutputTextWriter.ToString()));
            }
            else
            {
                base.RenderContents(output);
            }
        }
    }
    

    HTML解析器服务代码,分开以便于在其他地方实现。

    //... using System.Text.RegularExpressions;
    
    public static class HtmlParserService
    {        
        /// <summary>
        /// Takes HTML markup locates any table definition held within it and returns that
        /// markup with the table HTML pivotted
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public static string PivotHtmlTableMarkup(string html)
        {
            html = ReplaceShorthandTableTags(html);
    
            int openingTableTagIndex;
            string openingTableTagText;
            int closingTableTagIndex;
            string tableContentText;
    
            tableContentText = GetTagContentText("table", html, out openingTableTagIndex, out openingTableTagText, out closingTableTagIndex);
    
            MatchCollection rows = GetTagMatches("tr", tableContentText);
            if (rows.Count > 0)
            {
                MatchCollection columns = GetTagMatches("(td|th)", rows[0].Value);
    
                StringBuilder pivottedTableMarkup = new StringBuilder();
    
                for (int i = 0; i < columns.Count; i++)
                {
                    pivottedTableMarkup.Append("<tr>");
                    foreach (Match row in rows)
                    {
                        if (row.Value.Length > 0)
                        {
                            columns = GetTagMatches("(td|th)", row.Value);
    
                            if (columns.Count>i)
                            {
                                pivottedTableMarkup.Append(columns[i].Value);
                            }
                        }
                    }
                    pivottedTableMarkup.Append("</tr>");
                }
    
                string preTableText = "";
                if (openingTableTagIndex > 1)
                {
                    preTableText = html.Substring(1, openingTableTagIndex);
                }
    
                string postTableText;
                postTableText = html.Substring(closingTableTagIndex, html.Length - closingTableTagIndex);
    
                string newHtmlWithPivottedTable;
                newHtmlWithPivottedTable = preTableText + openingTableTagText + pivottedTableMarkup.ToString() + postTableText;
    
                return newHtmlWithPivottedTable;
            }
            else
            {
                return html;
            }
    
        }
    
        /// <summary>
        /// Gets the content between the specified tag.
        /// </summary>
        /// <param name="tag">The tag excluding any markup (e.g. "table" not "<table>"</param>
        /// <param name="text">The xml text string to extract content from</param>
        /// <param name="openingTagIndex">Outputs the indexed position of the opening tag</param>
        /// <param name="openingTagText">Outputs the definition of the tag, e.g. it's attributes etc</param>
        /// <param name="closingTagIndex">Outputs the indexed position of the closing tag</param>
        /// <returns></returns>
        public static string GetTagContentText(string tag, string text, out int openingTagIndex, out string openingTagText, out int closingTagIndex)
        {
            string contentText;
    
            openingTagIndex = text.ToLower().IndexOf("<" + tag);
            openingTagText = text.Substring(openingTagIndex, text.IndexOf(">", openingTagIndex) - openingTagIndex+1);
            closingTagIndex = text.ToLower().LastIndexOf("</" + tag + ">");
    
            contentText = text.Substring(
                openingTagIndex + openingTagText.Length,
                closingTagIndex - (openingTagIndex + openingTagText.Length) );
    
            return contentText;
        }
    
        /// <summary>
        /// Returns a collection of matches containing the content of each matched tag
        /// </summary>
        /// <param name="tag">HTML tag to match.  Exclude opening and close angled braces.
        /// Multiple tags can be matched by specifying them in the following format (tag1|tag2),
        /// e.g. (td|th)</param>
        /// <param name="html"></param>
        /// <returns></returns>
        public static MatchCollection GetTagMatches(string tag, string html)
        {
            Regex regexp = new Regex(@"<" + tag + @"\b[^>]*>(.*?)</" + tag + @">", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            return regexp.Matches(html);
        }
    
        /// <summary>
        /// Ensures any shorthand XML tags are full expressed, e.g.
        /// <td/> is converted to <td></td>
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static string ReplaceShorthandTableTags(string value)
        {
            value=value.Replace("<tr/>", "<tr></tr>");
            value=value.Replace("<td/>", "<td></td>");
            value=value.Replace("<th/>", "<th></th>");
    
            return value;
        }
    
    
    }
    
        2
  •  2
  •   Chris    15 年前

    您最好的选择可能是创建自己的服务器控件。

    我通过创建一个扩展了GridView的自定义服务器控件来完成类似的工作。然后我以数据源为轴心,在databind上动态地向GridView添加列。

    我使用了网格视图方法来保持与我的其他站点相似的外观和行为。或者,可以创建一个更简单的自定义控件,该控件在呈现方法中为表创建HTML。

    祝你好运。

    编辑: 下面是一个如何使服务器控件基于表的示例(这并不难,但请注意我没有测试这个…):

    public class PivotTable : Control
    {
        protected Table pivotTable = new Table();
        private DataTable _datasource;
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
         Category("Data"), Browsable(true),
         Description("Gets and sets the DataSource for the Control.  Needs to be a DataTable")]
        public DataTable DataSource
        {
            get { return _datasource; }
            set
            {
                if (value.GetType() == typeof(DataTable))
                {
                    throw new Exception("DataSource must be a DataTable.");
                }
                _datasource = value;
            }
        }
    
        public override ControlCollection Controls
        {
            get
            {
                EnsureChildControls();
                return base.Controls;
            }
        }
    
        protected override void CreateChildControls()
        {
            Table tbl = new Table();
            foreach (DataColumn dc in DataSource.Columns)
            {
                TableRow tr = new TableRow();
                TableHeaderCell header = new TableHeaderCell();
                header.Text = dc.ColumnName;
                tr.Controls.Add(header);
    
                foreach (DataRow dr in DataSource.Rows)
                {
                    TableCell tc = new TableCell();
                    tc.Text = dr[dc.ColumnName].ToString();
                    tr.Controls.Add(tc);
                }
    
                tbl.Controls.Add(tr);
            }
    
            Controls.Add(tbl);
        }
    }
    
        3
  •  0
  •   Semih Yagcioglu    15 年前

    您可以使用表组件,但是您需要努力工作才能得到您想要的。我的最佳解决方案是使用GridView。您可以将透视结果集填充到DataTable,并动态创建DataTable列和行。获得所需格式后,可以将数据表绑定到GridView。

    我以前也做过类似的事情,它更容易实现,而不是试图创建自己的控件。

        4
  •  0
  •   mika    15 年前

    第三方交叉表控件可用。德沃斯出版社 ASPxPivotGrid 首先想到。

    如果您只需要将交叉表用于独立的非交互式报表,那么可以考虑使用单独的报表工具,并将报表嵌入到ASP.NET报表查看器控件中。SQL Server Reporting Services 2008具有良好的 tablix region 用于表格和矩阵数据。 Telerik Reporting 也有交叉标签。

    但是,如果您的需求简单且定义良好,那么按建议部署HTML表控件可能是最快的解决方案。