Introduction
This is a custom control which eases the implementation of frozen header, rows, and columns in an ASP.NET DataGrid
by just changing a single attribute value. It is inherited from System.Web.UI.WebControls.DataGrid
, so all features of the DataGrid
are still available.
Prerequisites
You need to have .NET Framework 1.1 Service Pack 1 installed on your system, as headers are rendered in <TH>
tags.
Attributes
FreezeHeader
: Freeze the header or not. Default: false
. FreezeRows
: Specify the number of rows to be frozen. This will automatically freeze the header as well. Default: 0
. FreezeColumns
: Specify the number of columns to freeze in the grid. Default: 0
. AddEmptyHeaders
: This will add empty <tr><th></th>..</tr>
rows; it is beneficial when you want to keep a static header row in all of the pages of a DataGrid
. EmptyHeaderClass
: When AddEmptyHeaders
is non-zero, specify the empty row CSS class here. GridHeight
: Grid height; this is actually the height of the <DIV>
tag not of <table>
which can be set through Height
. When FreezeHeader
is true
or FreezeRows
is non-zero, always provide some value here. GridWidth
: Grid width, this is actually the width of the <DIV>
tag not of <table>
which can be set through Width
. When FreezeColumns
is non-zero, always provide some value here.
Using the Code
Just a frozen header in the DataGrid
:
<Tittle:CustomDataGrid GridHeight="150" FreezeHeader=true
..
/>
The above would be rendered as:
Freeze table rows in a DataGrid
as shown below. Please note that the header automatically freezes in this case.
<Tittle:CustomDataGrid GridHeight="110" FreezeRows=1
..
/>
The above would be rendered as:
Freeze table columns in a DataGrid
:
<Tittle:CustomDataGrid GridWidth="250" FreezeColumns=2
..
/>
The above would be rendered as:
Add frozen empty headers:
<Tittle:CustomDataGrid GridHeight="209"
AddEmptyHeaders=1 EmptyHeaderClass="greyed"
FreezeHeader="true"
..
/>
The above would be rendered as:
Technical Implementation Insight
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
{
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
public CustomDataGrid(): base()
{
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);
this.BorderWidth = Unit.Parse("1");
this.GridLines = GridLines.Both;
this.CellPadding = 2;
this.CellSpacing = 0;
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);
this.PageSize = 10;
}
#endregion
#region Exposed Attributes
<Browsable(true),
Category("Freeze"),
DefaultValue("false"),
Description ("Freeze Header.") >
public bool FreezeHeader
{
get { return freezeHeader; }
set
{
freezeHeader = value;
if ( freezeHeader == true )
{
this.UseAccessibleHeader = true;
if ( this.gridHeight == Unit.Empty )
this.gridHeight = 800;
}
}
}
<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;
}
}
}
<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; }
}
<Browsable(true),
Category("Freeze"),
DefaultValue("0"),
Description ("It will add empty rows.") >
public int AddEmptyHeaders
{
get { return addEmptyHeaders; }
set { addEmptyHeaders = value; }
}
<Browsable(true),
Category("Freeze"),
Description ("Class of empty rows.") >
public string EmptyHeaderClass
{
get { return emptyHeaderClass; }
set { emptyHeaderClass = value; }
}
<table ID="Table1"> which can be set through "Height"
<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; }
}
<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
protected override void OnPreRender(EventArgs e)
{
#region DataGrid freezing script
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);
}
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 (freezeColumns >= 1 || freezeHeader == true)
Page.RegisterStartupScript("GridOnLoad",
"<script language="javascript">\n" +
gridOnLoad + "\n</script>");
#endregion
base.OnPreRender(e);
}
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
System.Text.StringBuilder sb = new
System.Text.StringBuilder();
HtmlTextWriter writer = new HtmlTextWriter(new
System.IO.StringWriter(sb));
base.Render(writer);
string datgridString = sb.ToString();
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;
}
}
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>
View Output
FreezeRow.ASPX
<%@ Register TagPrefix="Tittle"
Namespace="Tittle.Controls" Assembly="Controls" %>
<Tittle:CustomDataGrid id="dgTittle2" FreezeHeader=true
runat="server" AutoGenerateColumns=True
GridHeight="110"
FreezeRows=1
> />
View Output
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>
View Output
FAQ
Do I need to make any change in the code-behind or need to include any other set of lines or files for freezing column/row/header of a DataGrid after I use the custom control supplied here?
No. Just use the custom control provided and set the exposed attribute.
Header/Column is transparent and I could see the data while scrolling.
While freezing header/column, it is mandatory to have a backcolor
of all frozen columns/rows, so provide the background color, and the transparency problem will go away.
Does it support cross-browsers?
I don't think so, I checked this in IE only and works fine there.
Will this code work in .NET 2.0 or above?
I don't know. I do not have .NET 2.0 to test it.
Can I freeze any number of columns and rows altogether?
Yes. Without a problem.
Where I can use the attribute "FreezeRows"?
Let's say you want to compare the marks of a student who got the highest marks with others; then you can bring the highest ranked user on top of the grid and then set FreezeRows=1
, which freezes the top student and then compares him with other students.
What is "AddEmptyHeaders", I'm not able to judge where I'll be using it?
Let's say, you want to compare a customer [A] sales with other customes sales (thousands of records). You can keep customer [A] information frozen in the top row and compare it with other customers in all of the pages.
I think I can use "FreezeRows" instead of "AddEmptyHeaders"?
Both have their own benefits. FreezeRows
always freezes the rows from top, so in the next page you won't be seeing the same record frozen, so FreezeRows
is best used when all records are on the same page, because you don't see same records on all the pages. Whereas when you use "AddEmptyHeaders
", you fill the top row data on the client side on page load, which remains same on all pages of a DataGrid
. So the conclusion is, if there is no paging in DataGrid
, use "FreezeRows
" (frozen rows will be filled from the server side). Or use "AddEmptyHeaders
" (frozen rows need not be filled at client side).
I'm getting an error. Attribute "UseAccessibleHeader" not found
You must have .NET Framework 1.1 SP1 installed on your system. Download it from MSDN.
Resources
There are a few sites which helped me in implementing this. Every time I wanted to freeze header/columns in a new DataGrid
, I'd to re-write all the complex logic once again. To mitigate this problem, I implemented all the logic within the custom DataGrid
and exposed the attributes which easily let freezing of headers and columns without having to know the actual implementation and the logic behind it.
Other Such Freeze Header Related Sites
Conclusion
I would really be interested in knowing if this code has helped you by any means. Please do not hesitate or be lazy in dropping your comments telling what you have felt about this submission and how much it has helped you. I would appreciate it if you keep the URL of this page inside the control's code while using it.
History
- 20th February, 2006: Created
- 24th February, 2006: Modified