65.9K
CodeProject 正在变化。 阅读更多。
Home

一个ASP.NET DataGrid自定义控件,只需更改单个属性值即可冻结标题、行、列

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (22投票s)

2006年2月20日

CPOL

4分钟阅读

viewsIcon

298666

downloadIcon

3838

一个ASP.NET DataGrid自定义控件,允许通过更改单个属性值来冻结DataGrid的标题、列和行。它继承自.NET DataGrid,因此DataGrid的所有功能仍然可用。

Sample Image - FreezeHeader.jpg

引言

这是一个自定义控件,通过更改单个属性值,可以轻松地在ASP.NET DataGrid中实现冻结标题、行和列。它继承自System.Web.UI.WebControls.DataGrid,因此DataGrid的所有功能仍然可用。

必备组件

您的系统上需要安装.NET Framework 1.1 Service Pack 1,因为标题是以<TH>标签渲染的。

属性

  • FreezeHeader:是否冻结标题。默认值:false
  • FreezeRows:指定要冻结的行数。这也会自动冻结标题。默认值:0
  • FreezeColumns:指定要在网格中冻结的列数。默认值:0
  • AddEmptyHeaders:这将添加空的<tr><th></th>..</tr>行;当您希望在DataGrid的所有页面中保留一个静态的标题行时,这非常有用。
  • EmptyHeaderClass:当AddEmptyHeaders非零时,在此指定空行的CSS类。
  • GridHeight:网格高度;这实际上是<DIV>标签的高度,而不是<table>的高度(可以通过Height设置)。当FreezeHeadertrueFreezeRows非零时,请始终在此提供一个值。
  • GridWidth:网格宽度;这实际上是<DIV>标签的宽度,而不是<table>的宽度(可以通过Width设置)。当FreezeColumns非零时,请始终在此提供一个值。

Using the Code

仅冻结DataGrid中的标题

<Tittle:CustomDataGrid GridHeight="150" FreezeHeader=true    
 ..
/>

以上内容将呈现为

如下所示冻结DataGrid中的表格行。请注意,在这种情况下,标题会自动冻结。

<Tittle:CustomDataGrid GridHeight="110" FreezeRows=1    
 ..
/>

以上内容将呈现为

冻结DataGrid中的表格列

<Tittle:CustomDataGrid GridWidth="250" FreezeColumns=2    
 ..
/>

以上内容将呈现为

添加冻结的空标题

<Tittle:CustomDataGrid GridHeight="209" 
        AddEmptyHeaders=1 EmptyHeaderClass="greyed" 
        FreezeHeader="true"
 ..
/>

以上内容将呈现为

技术实现洞察

CustomDataGrid.cs
using System;
using System.Web.UI;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Reflection;

[assembly: TagPrefix ("Tittle.Controls" , "Tittle") ]
namespace Tittle.Controls
{    
    /// <summary>
    /// A Custom DataGrid which Allow freezing of Headers/Rwos/Columns
    /// https://codeproject.org.cn/script/articles/list_articles.asp?userid=1654009
    /// </summary>
    public class CustomDataGrid : DataGrid
    {    
        #region Private Variables
        private bool freezeHeader = false;
        private int addEmptyHeaders = 0;
        private int freezeRows = 0;
        private string emptyHeaderClass = "";
        private int freezeColumns = 0;
        private Unit gridHeight = Unit.Empty;
        private Unit gridWidth = Unit.Empty;
        #endregion
        
        #region Constructors
        /// <summary>
        /// default initialization with datagrid properties
        /// </summary>
        public CustomDataGrid(): base() 
        {
            //set default appearence style
            this.HeaderStyle.CssClass = "GridHeader";
            this.FooterStyle.CssClass = "GridHeader";
            this.ItemStyle.CssClass = "GridNormal";
            this.AlternatingItemStyle.CssClass = "GridAlternate";
            this.CssClass = "Grid";
            this.BorderColor =  
              System.Drawing.Color.FromArgb(47, 33, 98); //"#2f2162"
            this.BorderWidth = Unit.Parse("1");
            this.GridLines = GridLines.Both; 
            this.CellPadding = 2; 
            this.CellSpacing = 0;        
            //paging
            this.PagerStyle.Mode = PagerMode.NumericPages;
            this.PagerStyle.Position = PagerPosition.Bottom; 
            this.PagerStyle.HorizontalAlign = HorizontalAlign.Center; 
                
            this.PagerStyle.ForeColor = 
                System.Drawing.Color.FromArgb(0, 0, 255); //"#2f2162"
                
            this.PageSize = 10;
        }
        #endregion

        #region Exposed Attributes
        /// <summary>
        /// Freeze Header : True/False
        /// 
        /// Default: False
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        DefaultValue("false"),
        Description ("Freeze Header.") > 
        public bool FreezeHeader
        {
            get { return freezeHeader; }
            set 
            {                 
                freezeHeader = value;
                if ( freezeHeader == true )
                {
                    //this attribute requires .Net Framework 1.1 
                    //service pack 1 installed
                    //on system, it generates headers 
                    //in <th> tags instead of <td> tags.
                    this.UseAccessibleHeader = true;
                    //If user hasnt set height property set a default here.
                    if ( this.gridHeight == Unit.Empty )
                        this.gridHeight = 800;
                }
            }
        }

        /// <summary>
        /// Freeze Data Rows : Specify No. of Rows
        /// to Freeze from top excluding the header.
        /// 
        /// Default: 0
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        DefaultValue("0"),
        Description ("Freeze Data Rows.") > 
        public int FreezeRows
        {
            get { return freezeRows; }
            set 
            {                 
                freezeRows = value;    
                if ( freezeRows >= 1 )
                {
                    this.FreezeHeader = true;
                }
            }
        }

        /// <summary>
        /// Specify here how many columns you want to freeze in the grid, 
        /// freeze columns from the left.
        /// 
        /// Default: 0
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        DefaultValue("0"),
        Description ("Specify no. of columns to freeze
            in the grid, it freeze columns from the left.") >
        public int FreezeColumns
        {
            get { return freezeColumns; }
            set { freezeColumns = value; }
        }        

        /// <summary>
        /// this will add empty <tr><th></th>..</tr> rows
        /// the benefit of it is that later we can
        /// through client side add/modify any text to individual cell 
        /// it is done since through client side
        /// we can not add more than one row
        /// with <th> tags to table.
        /// 
        /// It is used when you set FreezeHeader to "true"
        /// and wants to have more than one such headers 
        /// to be freezed but data of all such headers
        /// is decided on client side.
        /// 
        /// This is useful you want to keep a static
        /// row in all of the pages of a datagrid. 
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        DefaultValue("0"),
        Description ("It will add empty rows.") > 
        public int AddEmptyHeaders
        {
            get { return addEmptyHeaders; }
            set { addEmptyHeaders = value;    }
        }                

        /// <summary>
        /// Class of Empty rows <tr><th></th>.. </tr>
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        Description ("Class of empty rows.") >
        public string EmptyHeaderClass
        {
            get { return emptyHeaderClass; }
            set { emptyHeaderClass = value; }
        }        

        /// <summary>
        /// Grid height, this is actually height of <DIV> tag not of 
       <table ID="Table1"> which can be set through "Height"
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        Description ("Grid height, this is actually height of 
          <DIV> tag not of <table ID="Table2">
          which can be set through Height.") >
        public Unit GridHeight
        {
            get { return gridHeight; }
            set { gridHeight = value; }
        }

        /// <summary>
        /// Grid width, this is actually width of <DIV> tag not of 
        /// <table ID="Table3"> which can be set through "Width"
        /// </summary>
        <Browsable(true),
        Category("Freeze"),
        Description ("Grid width, this is actually width 
          of <DIV> tag not of <table ID="Table4"> 
          which can be set through Width.") >
        public Unit GridWidth
        {
            get { return gridWidth; }
            set { gridWidth = value; }
        }
        #endregion

        #region Overriden events like OnPreRender / Render
        /// <summary>
        /// Before rendering to screen, check if freezing
        /// of column/header/row is required.
        /// Add <div> tag before <table ID="Table5"> tag then
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {            
            #region DataGrid freezing script
            //DataGrid functions/styles etc.
            if (!Page.IsClientScriptBlockRegistered("FreezeGridScript") )
            {
                string freezeGridScript = @"                
                     function FreezeGridColumns(dgTbl, colNo) 
                     {                                
                         var tbl = document.getElementById(dgTbl);
                         for ( var i=0; i<tbl.rows.length; i++)
                         {
                             for ( var j=0; j<colNo; j++) 
                             { 
                                 tbl.rows[i].cells[j].className = 'locked'; 
                             } 
                         }                 
                     } 
                ";
                Page.RegisterClientScriptBlock("FreezeGridScript", 
                     "<script language="javascript">\n" + 
                     freezeGridScript + "\n</script>");
            }

            if ( !Page.IsClientScriptBlockRegistered("GridStyle") )
            {
                string gridStyle = "";
                gridStyle = @"
                      <style TYPE='text/css'> 
                    /* Locks table header */
                    th 
                    {                     
                        /* border-right: 1px solid silver; */ 
                        position:relative;
                        cursor: default;
                        /*IE5+ only*/ 
                        top: expression(this.parentElement.parentElement.
                             parentElement.parentElement.scrollTop -2);
                        z-index: 10;    
                    }
                    TR.GridNormal TH, TR.GridAlternate TH
                    {
                        text-align:left;
                    }
                    TR.GridHeader TH
                    {
                        text-align:center;
                    }                    
                        
                    /* Locks the left column */ 
                    td.locked, th.locked 
                    {                     
                        /* border-right: 1px solid silver; */ 
                        position:relative; 
                        cursor: default; 
                        /*IE5+ only*/    
                        left: expression(this.parentElement.parentElement.
                              parentElement.parentElement.scrollLeft-2);    
                    } 
                    
                    /* Keeps the header as the top most item.
                       Important for top left item*/ 
                    th.locked {z-index: 99;} 
                    </style> 
                    <style>
                    /*Overriding Grid Styles*/
                    .Grid
                    {
                        border:0;
                        background-color: #808080;    
                    }
                    .GridHeader
                    {
                        background-color: #547095;
                        color:White;
                        font-weight:bold;
                        font-family:Tahoma;
                        text-align:center;    
                        padding : 1px 0px 1px 0px;
                        font-size:8pt;        
                    }
                    .GridHeader TD A, TH A
                    {
                        text-decoration:none;
                        color:White;
                    }
                    .GridNormal
                    {
                        background-color: #FFFFFF;
                        font-weight: normal;
                        font: x-small Verdana;
                    }
                    .GridAlternate
                    {
                        background-color: #EFF8FC;
                        font-weight: normal;
                        font: x-small Verdana;
                    }
                    .GridSelected
                    {
                        background-color: #FFC082;
                        font-weight: normal;
                        font: x-small Verdana;
                    }
                    .GridPager
                    {
                        background-color : White;
                        font-size : x-small;
                    }
                    </style>
                    "; 
                Page.RegisterClientScriptBlock("GridStyle",gridStyle);
            }

            //Trim space from the div and datagrid table.
            string gridOnLoad = "";
            gridOnLoad = @"
                // trims the height of the div surrounding the results table
                // so that if not a lot of results are returned,
                // you do not have a lot of blank space between the results and
                // the last buttons on the page
                var divTbl = document.getElementById('div_"+this.ID + @"');
                var resultsTable = divTbl.children[0];
                if ( typeof(resultsTable) != 'undefined' )
                {
                    //alert( 'div ' + divTbl.offsetHeight + ' results: ' 
                    //         +  resultsTable.offsetHeight );

                    if( divTbl.offsetHeight + 3 > resultsTable.offsetHeight ) 
                        divTbl.style.height = resultsTable.offsetHeight + 50;
                    }
                ";
                //If <div> generated then only
                if (freezeColumns >= 1 || freezeHeader == true) 
                    Page.RegisterStartupScript("GridOnLoad", 
                         "<script language="javascript">\n" + 
                         gridOnLoad + "\n</script>");
            #endregion

            base.OnPreRender(e);
        }

        /// <summary>
        /// Render datagridservercontrol
        /// </summary>
        /// <param name="output"></param>
        protected override void Render(HtmlTextWriter output)
        {                        
            if ( freezeRows >= 1 || addEmptyHeaders >= 1 || 
                 freezeColumns >= 1 || freezeHeader == true ) 
            {
                #region Only execute when need to any of this: 
                        Freeze Header, Row, Add Empty Header, 
                        Freeze Columns  
                //Get the output string rendered.
                System.Text.StringBuilder sb = new 
                       System.Text.StringBuilder();
                HtmlTextWriter writer = new HtmlTextWriter(new 
                                        System.IO.StringWriter(sb));
                base.Render(writer);

                //the output has been retrieved 
                //in the stringbuilder AND do the modification here      
                string datgridString = sb.ToString();

                //FREEZE MORE ROWS OTHER THAN JUST HEADER
                if ( freezeRows >= 1 )
                {            
                    int cnt = 0;
                    for ( int i=0; i<=freezeRows; i++)
                    {
                        cnt = datgridString.IndexOf("</tr>",cnt);
                        if ( i < freezeRows)
                            cnt++;
                    }
                    if ( cnt != -1 )
                    {
                       string firstpart,secondpart;
                       firstpart = datgridString.Substring(0,cnt);
                       secondpart = datgridString.Substring(cnt);
                
                       firstpart = Regex.Replace(firstpart, @"<td", 
                                @"<th class='innerBorder' " );
                       firstpart = Regex.Replace(firstpart, 
                                @"</td>", @"</th>");

                       datgridString = firstpart + secondpart;
                    }
                }
            
                //ADD EMPTY HEADERS. <TH>
                //This all done as through client side JAVASCRIPT 
                //you can not add more than one row TH 
                //tag to existing TABLE.
                if ( addEmptyHeaders >= 1 )
                {
                    string rowData="";
                    for ( int i=1; i<=addEmptyHeaders; i++)
                    {
                        rowData +="<TR class='" + EmptyHeaderClass + 
                                                              "' >";
                            
                        for ( int j=0; j<this.Columns.Count; j++)
                        {
                            string clsname = "";
                            if ( j == 0 )
                                clsname="leftBorder";
                            if ( j == this.Columns.Count-1 )
                                clsname="rightBorder";
                            if ( j > 0  && j < this.Columns.Count )
                                clsname="innerBorder";

                            rowData += "<TH class='" + clsname + 
                                       "' > </TH>";
                        }
                        rowData += "</TR>";
                    }
                    if ( addEmptyHeaders >= 1)
                    {
                        int headerEnd = 
                            datgridString.IndexOf("</tr>",0);
                        string prePart = 
                            datgridString.Substring(0,headerEnd+5);
                        string postPart = 
                            datgridString.Substring(headerEnd+5);
                        datgridString = prePart + rowData + postPart;
                    }
                }

                if ( freezeColumns >= 1 )
                {
                    if ( this.gridWidth == Unit.Empty )
                        this.gridWidth = 800;
                
                    string freezeGridColumn="FreezeGridColumns('" + 
                           this.ID + "',"+freezeColumns.ToString()+");";
                    Page.RegisterStartupScript("freezeGridColumn", 
                         "<script language="javascript">\n" + 
                         freezeGridColumn + "\n</script>" );
                }

                if ( freezeColumns >= 1 || freezeHeader == true )
                {
                    string divstring;
                    divstring = "<div id='div_" + this.ID + 
                                "' style='";            
                    if ( freezeHeader == true )
                        divstring += " HEIGHT:"+this.gridHeight+"; ";
                    if ( freezeColumns >= 1 )
                        divstring += " WIDTH:"+this.gridWidth+"; ";

                    datgridString = divstring + " OVERFLOW:auto; ' >" 
                                    + datgridString + "</div>";
                }

                output.Write(datgridString.ToString());    
                #endregion
            }
            else
            {
                base.Render(output);
            }
        }
        #endregion
    }
}
Display.ASPX
<%@ Register TagPrefix="Tittle" 
      Namespace="Tittle.Controls" Assembly="Controls" %>
<Tittle:CustomDataGrid id="dgTittle" 
         FreezeHeader=true runat="server" 
         AutoGenerateColumns=False GridHeight="150"
    >
<Columns>
    <asp:TemplateColumn HeaderText="User Name" >
        <ITEMTEMPLATE>
            <asp:Label Runat="server" ID="lblName" Width="100" 
  Text='<%# DataBinder.Eval(Container, "DataItem.Name") %>'  />
        </ITEMTEMPLATE>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText="Grade" 
              HeaderStyle-HorizontalAlign=Right  >
        <ITEMTEMPLATE>
            <asp:TextBox Runat="server"  Width="100" ID="txtAge"
 Text='<%# DataBinder.Eval(Container, "DataItem.Age") %>'  />
        </ITEMTEMPLATE>
    </asp:TemplateColumn>
</Columns>                  
</Tittle:CustomDataGrid>
查看输出

FreezeRow.ASPX
<%@ Register TagPrefix="Tittle" 
        Namespace="Tittle.Controls" Assembly="Controls" %>
<Tittle:CustomDataGrid id="dgTittle2" FreezeHeader=true
  runat="server" AutoGenerateColumns=True
  GridHeight="110"
  FreezeRows=1
> />
查看输出

AddEmptyHeaders ASPX
<%@ Register TagPrefix="Tittle" 
         Namespace="Tittle.Controls" Assembly="Controls" %>
<body onload="AddEmptyHeaderDataLoad()">
..
<style>
.greyed
{
    background-color:#c0c0c0;
}
</style>
<script language="javascript">
function AddEmptyHeaderDataLoad()
{
    if ( typeof(dgTittle4) != 'undefined' )
    {
        dgTittle4.rows[1].cells[0].innerText = 'Tittle Joseph';
        dgTittle4.rows[1].cells[1].innerText = '29';
        dgTittle4.rows[1].cells[2].innerText = 
                              '5/67 Rachna Vaishali Ghaziabad';
    }
}        
</script>
<Tittle:CustomDataGrid id="dgTittle4" GridHeight="209" AutoGenerateColumns=false
    AddEmptyHeaders=1 EmptyHeaderClass="greyed" PageSize=7 
    FreezeHeader=true PagerStyle-Mode=NumericPages 
    AllowPaging=True 
    OnPageIndexChanged="dgTittle4_PageIndexChanged"
>
<Columns>
    <asp:TemplateColumn HeaderText="Name" >
        <ITEMTEMPLATE>
            <asp:Label Runat="server" ID="Label3" Width="100"
 Text='<%# DataBinder.Eval(Container, "DataItem.Name") %>'  />
        </ITEMTEMPLATE>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText="Grade" 
             HeaderStyle-HorizontalAlign=Right  >
        <ITEMTEMPLATE>
            <asp:Label Runat="server" ID="Label4" 
                 Text='<%# DataBinder.Eval(Container, 
                           "DataItem.Age") %>'  />
        </ITEMTEMPLATE>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText="Address" 
              HeaderStyle-HorizontalAlign=Right 
              ItemStyle-Wrap=False >
        <ITEMTEMPLATE>
            <asp:Label Runat="server" ID="Label5" 
              Text='<%# DataBinder.Eval(Container, 
                        "DataItem.Address") %>'  />
        </ITEMTEMPLATE>
    </asp:TemplateColumn>
</Columns>         
</Tittle:CustomDataGrid>
..
</body>
查看输出

常见问题解答

在使用这里提供的自定义控件后,我是否需要更改代码隐藏文件或包含其他一组行或文件来冻结DataGrid的列/行/标题?

否。只需使用提供的自定义控件并设置公开的属性即可。

标题/列是透明的,滚动时可以看到数据。

冻结标题/列时,所有冻结的列/行的backcolor是必需的,所以请提供背景颜色,这样透明度问题就会消失。

它支持跨浏览器吗?

我不确定,我只在IE中测试过,在那里工作正常。

此代码将在.NET 2.0或更高版本中运行吗?

我不知道。我没有.NET 2.0可以测试。

我可以一起冻结任意数量的列和行吗?

是的。没有问题。

在哪里可以使用属性“FreezeRows”?

假设您想比较一个获得最高分数的学生与其他学生的成绩;那么您可以将排名最高的用户放在网格的顶部,然后设置FreezeRows=1,这将冻结顶部的学生,然后将其与其他学生进行比较。

什么是“AddEmptyHeaders”,我无法判断将在哪里使用它?

假设您想将客户[A]的销售额与其他客户的销售额(成千上万条记录)进行比较。您可以将客户[A]的信息冻结在顶行,并将其与DataGrid所有页面上的其他客户进行比较。

我认为可以使用“FreezeRows”而不是“AddEmptyHeaders”?

两者都有各自的优点。FreezeRows总是从顶部冻结行,所以下一页您将看不到相同的记录被冻结,因此FreezeRows最适合在所有记录都在同一页上时使用,因为您不会在所有页面上看到相同的记录。而当您使用“AddEmptyHeaders”时,您会在页面加载时在客户端填充顶行数据,这在DataGrid的所有页面上都保持不变。所以结论是,如果DataGrid没有分页,请使用“FreezeRows”(冻结的行将从服务器端填充)。或者使用“AddEmptyHeaders”(冻结的行不需要在客户端填充)。

我收到一个错误。“UseAccessibleHeader”属性未找到

您的系统必须安装.NET Framework 1.1 SP1。请从MSDN下载。

资源

有几个网站帮助我实现了这一点。每次我想在一个新的DataGrid中冻结标题/列时,我都必须重新编写所有复杂的逻辑。为了缓解这个问题,我在自定义DataGrid中实现了所有逻辑,并公开了属性,可以轻松地冻结标题和列,而无需了解实际的实现和背后的逻辑。

其他类似的冻结标题相关网站

结论

如果您通过任何方式从这个代码中受益,我将非常感兴趣。请不要犹豫或懒惰地留下您的评论,告诉我您对这个提交的看法以及它对您有多大帮助。如果您在使用该控件的代码中保留此页面的URL,我将不胜感激。

历史

  • 2006年2月20日:创建
  • 2006年2月24日:修改
© . All rights reserved.