I have search long and hard and spent much of my own time trying to find a good Fixed Header and Scrollable rows for the ASP.NET GridView. I think I have finally come up with a combination of my own efforts and the best of borrowed from other solutions. Unfortunately, I didn't keep track of where I got some of the code that I borrowed. My apologies to the original authors.
This solution attempts to remedy all the buggy solutions I found on the internet. Some of the common problems were
- The solution did work for the GridView
- The solution assumed we had more control of the generated html from the GridView
- The solution required modifying the html generated by the GridView
- The solution stopped working when windows is resized
- The solution assumed that there was only one scrollable area on the page. (To be fair, my solution doesn't assume this, but it would require some duplication of style sheets, jscript code, etc.)
Here is a sample implementation of my solution. It works quite well with only one scrollable area that has a fixed header. You will need to change duplicate the JavaScript file and CSS and all references to "container" if want more than one scrollable area that has a fixed header. Another alternative is to generate a customized .css .js file based on a special url. That is beyond the scope of this though.
There are several key things I would like to point about the .aspx page. The .js and .css are just really items you need to reference and don't really require any changes (except as noted above). So, I really just want to highlight what you would need to add to your page (that has a working GridView that does not have any special Fixed column header) to make this solution work.
- The DOCTYPE line is VERY important. This is NOT the default that Visual Studio adds to your page. Replace the line that Visual Studio puts in your .aspx page with the one shown below.
- Copy and Paste the GridView1_PreRender event handler to your .cs file. If you have a different name for your GridView you will need to change references to it to make the name you gave it. You will also want to set the height and width that you want. A word of warning, I did not implement the width yet, so actually that doesn't do anything. Currently the width of the GridView is not set here. Don't forget to change the references to GridView1 in the literals to match the name you use. Also, un/comment the appropriate example. If you want the GridView to be the Maximum height available and expand as the window resizes use the SetFixedHeaderWithMaxHeight call. Otherwise, if you want a fixed height, use the SetFixedHeader example.
- Register the GridView1_PreRender event handler with the GridView1. One easy way to do this is to add the following line to the GridView1 tag.
OnPreRender="GridView1_PreRender"
NOTE: You will of course need to make this match what you specified in step 2.
- Create a .css file by copying the CSS lines below into its own file. Alternatively, you could just include it in a style tag in the head of the page, though I don't recommend this approach.
- Create a .js file by copying the JavaScript lines below into its own file. Alternatively, you could just include it in a script tag in the head of the page, though I don't recommend this approach.
- Reference the .js and .css file in the head of the page.
- Copy and Paste the HeaderStyle tag in the GridView.
- Copy and Paste the DIV tag that has the id="container" and is directly around the GridView. It is important that there is no other DIV tags or other tags in between the DIV and the GridView. If you change this, you will need to make changes to the CSS and JavaScript.
<%@ Page Language="C#" %>
<!-- This comment keeps IE6/7 in the reliable quirks mode -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<script runat="server">
protected void GridView1_PreRender(object sender, EventArgs e)
{
if (GridView1.Rows.Count > 0)
{
//This replaces <td> with <th> and adds the scope attribute
GridView1.UseAccessibleHeader = true;
//This will add the <thead> and <tbody> elements
GridView1.HeaderRow.TableSection = TableRowSection.TableHeader;
}
GridView1.Style["border-collapse"] = "separate";
string height = "450px";
string width = "200px";
string gv1FixedHeaderJScript = string.Format("SetFixedHeader('{0}', '{1}', '{2}');", GridView1.ClientID, height, width);
// MAX Height Example
string gv1FixedHeaderJScript = string.Format("SetFixedHeaderWithMaxHeight('{0}', '{1}', '{2}');", GridView1.ClientID, width, "20px");
// Fixed Height Example
//ScriptManager.RegisterStartupScript(this, this.GetType(), "gvGridView1FixedHeaderKey", gv1FixedHeaderJScript, true);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>GridView Test</title>
<link href="FixedHeader.css" rel="stylesheet" type="text/css" />
<script language="JavaScript" src="FixedHeader.js"></script>
</head>
<body>
<form id="form1" runat="server">
<div id="container" style="border-style:none;">
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
OnPreRender="GridView1_PreRender">
<HeaderStyle CssClass="DataGridFixedHeader" />
</asp:GridView>
</div>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetDelinquentActions"
TypeName="DAL"></asp:ObjectDataSource>
</form>
</body>
</html>
// JScript File
// This is the function that gets called to resize the scrollable area to the size we want.
// We have to use the setTimeout() with a 1ms delay IF we call this function
// from within the form tag instead of just before the bottom of the body end tag.
// The reason is that in IE any of the height and width (scrollHeight, offsetHeight, style.height)
// are not set until after the form has finished rendering. This means, that the call to the
// SetFixedHeader2 method must occur after the form has been rendered. When using master pages
// as we are doing the location we need can only be specified in the .aspx page of the master page.
// Since we use this setting for different pages we can't hard code it there.
// Also, we can't use the ScriptManager.RegisterStartupScript unless we use the
// setTimeout either. This is because using this method puts the code just before the form end tag,
// which is again not where we need it to be. However, using setTimeout allows us to use this method.
function SetFixedHeader (gvClientID, height, width)
{
var expr = "SetFixedHeader2('" + gvClientID + "', '" + height + "', '" + width + "')"
setTimeout(expr, 1);
}
// This is essentially the same as SetFixedHeader function except the height is
// always going to be the height on the body - bottomMargin - top of GridView.
// bottomMargin is the number of pixels that create the gap between the bottom of
// the window and the bottom of the GridView
function SetFixedHeaderWithMaxHeight (gvClientID, width, bottomMargin)
{
// get the max height that the GridView can be
var maxHeight = getMaxHeight() - parseInt(bottomMargin);
// We need to resize the GridView when the Window is resized
window.onresize = MaximizeGridViewScrollableArea;
var expr = "SetFixedHeader2('" + gvClientID + "', '" + maxHeight + "', '" + width + "')"
setTimeout(expr, 1);
gridViewClientID = gvClientID;
desiredHeight = maxHeight;
desiredWidth = width;
desiredBottomMargin = bottomMargin;
}
function MaximizeGridViewScrollableArea()
{
SetFixedHeaderWithMaxHeight(gridViewClientID, desiredWidth, desiredBottomMargin);
}
var gridViewClientID = null;
var desiredHeight = null;
var desiredWidth = null;
var desiredBottomMargin = null;
// height - string - the height of the scrollable area in pixels (not percent) i.e. 330px
// width - string - the width of the scrollable area in pixels or percent i.e. 600px or 50%
function SetFixedHeader2 (gvClientID, height, width)
{
// get numeric values for height and width
var heightNum = parseInt(height);
// adjust the size since the grid view needs to be slightly smaller
// than the container div
var heightAdjustment = 40;
heightNum = heightNum - heightAdjustment;
// do we need scrolling or not?
var gv = document.getElementById(gvClientID);
var tbody = null;
// loop through the four (or fewer) child nodes of the table
// and find the tbody node
for (var i=0; i<gv.childNodes.length; i++)
{
var child = gv.childNodes[i];
if (child.tagName)
{
if (child.tagName.toUpperCase() == "TBODY")
{
tbody = child;
// we found what we needed, exit the loop
i = gv.childNodes.length;
}
}
}
if (tbody != null)
{
var gvDiv = GetDivGeneratedByGridView();
// scrolling is needed
if (parseInt(tbody.scrollHeight) > parseInt(heightNum))
{
//alert('needs scrolling');
//tbody.style.height = height;
tbody.style.height = (heightNum) + "px"
if (gvDiv != null)
{
// add the height adjustment back in for the container, so it is bigger
gvDiv.style.height = (heightNum + heightAdjustment) + "px";
}
}
// scrolling is NOT needed
else
{
//alert('NO scrolling');
tbody.style.height = '100%'
if (gvDiv != null)
{
gvDiv.style.height = "100%";
}
}
}
}
// returns the DIV surrounding the GridView.
// NOTE: This is NOTE the DIV with id="container" that we added.
// This is the DIV that is generated by the GridView when it is rendered.
// This the DIV between teh DIV with id="container" and the table that is
// generated by the GridView.
function GetDivGeneratedByGridView()
{
// set the size of the container div to be just a little bigger
// than the grid view
var container = document.getElementById("container");
var isIE = typeof container.children == 'object';
var gvDiv = null;
if (isIE)
{
gvDiv = container.children[0];
}
else // Firefox
{
// NOTE: First childNode is a textnode that is a new line
gvDiv = container.childNodes[1];
}
return gvDiv;
}
// get the max height the GridView can have
// NOTE: This is based on where the GridView is vertically on the page
// For example, if the GridView is 100 pixels from the top of the
// top of the body, then this will return the height of the body - 100.
function getMaxHeight() {
var div = GetDivGeneratedByGridView();
myHeight = 0;
// alert(document.body.topMargin);
if( typeof( window.innerWidth ) == 'number' ) {
//Non-IE
myHeight = window.innerHeight;
} else if( document.documentElement && document.documentElement.clientHeight) {
//IE 6+ in 'standards compliant mode'
myHeight = document.documentElement.clientHeight;
} else if( document.body && document.body.clientHeight) {
//IE 4 compatible
myHeight = document.body.clientHeight;
}
var maxGridViewHeight = myHeight - div.offsetTop;
return maxGridViewHeight;
}
/*** The Fixed Header Stylesheet ***/
.DataGridFixedHeader { POSITION: relative; TOP: expression(this.parentNode.parentNode.parentNode.scrollTop-1);}
#container div {
overflow: auto; /* so the extra columns and rows flow as needed */
margin: 0 auto;
}
#container table {
width: 99%; /*100% of container produces horiz. scroll in Mozilla*/
/* Gets rid of the 1 pixel space on the top of the header that shows through when scrolling */
border: none ! important;
}
#container table>tbody { /* child selector syntax which IE6 and older do not support*/
overflow: auto;
overflow-x: hidden;
}
#container thead tr {
position:relative;
top: expression(offsetParent.scrollTop); /*IE5+ only*/
}
#container table tfoot tr { /*idea of Renato Cherullo to help IE*/
position: relative;
overflow-x: hidden;
top: expression(parentNode.parentNode.offsetHeight >=
offsetParent.offsetHeight ? 0 - parentNode.parentNode.offsetHeight + offsetParent.offsetHeight + offsetParent.scrollTop : 0);
}
#container td:last-child {padding-right: 20px;} /*prevent Mozilla scrollbar from hiding cell content*/
#container thead td, thead th {
/* the background color for the header to something other than transparent
so that the rows don't show behind while scrolling */
background-color:white;
}
/*** Purely Cosmetics ***/
#container div {
width: 99%; /* table width will be 99% of this*/
height: 50px; /* a small efault value so user won't really see resize if delay rendering 1ms. it is changed by the SetFixedHeader() javascript function. Must be greater than tbody*/
}
/*** print style sheet ***/
@media print {
#container div {overflow: visible; }
#container table>tbody {overflow: visible; }
#container td {height: 14pt;} /*adds control for test purposes*/
#container thead td {font-size: 11pt; }
#container tfoot td {
text-align: center;
font-size: 9pt;
border-bottom: solid 1px slategray;
}
#container thead {display: table-header-group; }
#container tfoot {display: table-footer-group; }
#container thead th, thead td {position: static; }
#container thead tr {position: static; } /*prevent problem if print after scrolling table*/
#container table tfoot tr { position: static; }
}
/*** Global Print Styles ***/
@media print {
.noprint {display: none;}
body {
font-family:"Palatino Linotype", Georgia, Garamond, serif;
background-image: none;
}
#container {
border: none;
padding: 0;
}
}