Freeze ASP.NET GridView Headers by Creating Client-Side Extenders

Lately I've been working on a pet project where I needed to freeze a GridView header so that it didn't move while the grid records were scrolled by an end user.  After searching the Web I came across a lot of "pure" CSS ways to freeze a header.  After researching them more they tend to rely on a lot of CSS hacks to do the job and require HTML to be structured in a custom way.  I also found a lot of custom GridView classes (another one here), and more.  Some of the solutions were really good while others seemed a bit over the top and required a lot of custom C# or VB.NET code just to do something as simple as freezing a header.  Freezing a header requires CSS and potentially JavaScript depending upon what needs done.  Regardless if CSS or JavaScript are used, these are client-side technologies of course.  I started to write my own derivative of the GridView class but quickly realized that it made more sense to use the standard GridView control and simply extend it from the client-side.  An example of what I was after is shown next:



In the past I've always made a scrollable GridView by wrapping a <div> tag around the control, adding a table with header rows inside the <div> (but above the GridView control code), and setting the CSS overflow property on the wrapper div to "auto".  Scott Mitchell has a nice article on doing this with a DataGrid and you can see a sample of doing something like that at my XML for ASP.NET Developers Website if you're interested.  This technique works well but makes it a bit hard to line up the header and child rows perfectly (although it can be done).

I decided to build my own client-side GridView "extender" code to accomplish the task since I didn't want to worry about yet another GridView assembly being in my toolbox just to freeze a header.  It appears to work great in IE6+ and Firefox 2 (haven't tried FireFox 1 – 1.5) although I'm sure there are some ways it could be improved and I can't say I've done extensive testing.  The code basically grabs the first header row from the GridView using JavaScript and moves it inside of a THEAD tag so that CSS can easily be applied to all of the TH tags and things work well in FireFox.  It then applies a few styles to the appropriate items within the GridView HTML that is generated. 

The JavaScript and CSS code is shown below.  On online version can be viewed here.

<style type="text/css">
    
.WrapperDiv {
        width
:800px;height:400px;border: 1px solid black;
    
}        
    
.WrapperDiv TH {
        position
:relative;
    
}
    
.WrapperDiv TR 
    
{
        
/* Needed for IE */
        height
:0px;
    

</
style>
<script>
    
function onLoad()
    {
        FreezeGridViewHeader(
'GridView1','WrapperDiv');
    
}    
    
    
    
function FreezeGridViewHeader(gridID,wrapperDivCssClass) 
    {
        
/// <summary>
        ///   Used to create a fixed GridView header and allow scrolling
        /// </summary>
        /// <param name="gridID" type="String">
        ///   Client-side ID of the GridView control
        /// </param>
        /// <param name="wrapperDivCssClass" type="String">
        ///   CSS class to be applied to the GridView's wrapper div element.  
        ///   Class MUST specify the CSS height and width properties.  
        ///   Example: width:800px;height:400px;border:1px solid black;
        /// </param>
        
var grid = document.getElementById(gridID);
        if 
(grid !'undefined')
        {
            grid.style.visibility 
'hidden';
            var 
div = null;
            if 
(grid.parentNode !'undefined'
            {
                
//Find wrapper div output by GridView
                
div grid.parentNode;
                if 
(div.tagName == "DIV")
                {
                    div.className 
wrapperDivCssClass;  
                    
div.style.overflow "auto";                   
                
}
            }                
            
//Find DOM TBODY element and remove first TR tag from 
            //it and add to a THEAD element instead so CSS styles
            //can be applied properly in both IE and FireFox
            
var tags grid.getElementsByTagName('TBODY');
            if 
(tags !'undefined')
            {
                
var tbody tags[0];
                var 
trs tbody.getElementsByTagName('TR');
                var 
headerHeight 8;
                if 
(trs !'undefined'
                {
                    headerHeight +
trs[0].offsetHeight;
                    var 
headTR tbody.removeChild(trs[0]);
                    var 
head = document.createElement('THEAD');
                    
head.appendChild(headTR);
                    
grid.insertBefore(head, grid.firstChild);
                
}
                
//Needed for Firefox
                
tbody.style.height 
                  
(div.offsetHeight -  headerHeight) + 'px';
                
tbody.style.overflowX "hidden";
                
tbody.overflow 'auto';
                
tbody.overflowX 'hidden';
            
}
            grid.style.visibility 
'visible';
        
}
    }
</script>


This solution works well but it means I'd have to reference a CSS file and JavaScript file for each GridView control that needs a frozen header.  I decided to build an ASP.NET AJAX Extender control based upon the ASP.NET AJAX Toolkit which turned out to be fairly straightforward.  This means that I have to reference an assembly in my project (which I complained about above), but I can still use the stock GridView control.  I'll talk more about the extender control  (called GridViewHeaderExtender) in a future post.  Those interested in seeing the code can download it here.  The extender seems to work great with IE6+ and FireFox 2 but doesn't work with Safari on Windows (very few of the frozen header solutions I found online worked with Safari). 

Update:  Matt Berseth recently blogged about freezing GridView headers that are rendered using ControlAdapters.  Read more about his technique here.

comments powered by Disqus

21 Comments

  • Looks like a nice solution but the example doesn't seem to work in my IE6 browser - lots of room between rows and the header scrolls right off. I'll download and try it out though. Seems to work fine in Firefox 2.

  • Yep...you're right. I moved the div height and width out of the CSS and into the JS code and it messed it up on IE6. That's what I get for thinking something simple like that wouldn't affect IE6. I'll look into it and post and work-around. :-)

  • I've uploaded some new code that should fix the issue in IE6. It acts really wierd when I try to set the div height and width through script so I've changed things up a little bit to get it working in IE6, IE7 and Firefox 2.

  • This is great.
    But is there a similar solution to freeze the first column of a grid?

  • You could do that as well. The following code shown in the blog moves the appropriate "frozen" header row into a new tag called THEAD.

    if (trs != 'undefined')
    {
    headerHeight += trs[0].offsetHeight;
    var headTR = tbody.removeChild(trs[0]);
    var head = document.createElement('THEAD');
    head.appendChild(headTR);
    grid.insertBefore(head, grid.firstChild);
    }


    If you moved trs[0] (the header) and trs[1] (the first data row) into THEAD you could fix that row too although you'd have to set each TR's CSS position to "relative" for IE. It would take some work to play around with the styles, but the core of the code is already done.

  • Have you tried it on Opera and the new Safari for windows?

  • I tried it on Safari and it scrolls but the header doesn't stay frozen (I mention Safari in the post above). &nbsp;I haven't tried it on Opera though. &nbsp;The initial goal was to target IE and Firefox but if you happen to know of any changes that could be made to get it going on Safari and Opera please post them as I'd be interested in enhancing the code to work on those as well.

  • I implemented a gridview last year with freezed headers. However, I used jquery.

  • how can i maintain the position in postback

  • Theren,

    To maintain the scroll position you'd have to add some JavaScript to track the existing position and write it to a hidden field (or store it some other way). You might want to check out how ASP.NET does it when you add the MaintainScrollPostionOnPostBack attribute to the page directive. They add X and Y scroll position hiddens and use JavaScript to move the scroll bar back to where it was when the page reloads. You'd have to write similar code.

  • I really like this approach Dan. &nbsp;It works well when sorting in the gridview as long as it's not in an update panel. &nbsp;When in an update panel the grid, if there, becomes hidden.

  • Good stuuf.

    Having an issue with firefox though when scrolling the table cells do not scroll although the test does. This means i can get text half in one cell, half in another!

  • Man, you are saviour!!
    This is exactly what I was looking for!!

  • If you have a border on your GridView of size 2px, just change '.WrapperDiv TH {position:relative;}' in the section to '.WrapperDiv TH {position:relative;top:-2px;}'. This will fix your cell values showing above your header when scrolling down. Adjust the '2px' in the above example to whatever size your border is around your 'GridView1'.

    Just for the ones that don't know...

  • I'm getting a javascript error that says object required on this line - grid.style.visibility = 'hidden';

    Any ideas?

  • Make sure that your grid is being found and that the ID it's looking for matches with the client-side ID. If you're running the online example I put together then I'm afraid I don't know why you'd get that since it works fine in Firefox, IE6, and IE7 on this end.

  • any way to do this with a datagrid?

  • Hey have you tried having a drop down in the grid. It looks very bad in scrolling

  • I haven't played with the exact issue you're talking about, but you're obviously on the right track with z-index. It's one of those tricky issues that I'd have to play with though. If you do happen to find the solution for your particular situation please post it here though.

  • Nice article, and it works but...
    when I add styling to the gridview for highlighting the rows when hovered:

    gridViewRow.Attributes["onmouseout"] = (gridViewRow.RowIndex % 2) == 0 ? "this.className='gridRow'" : "this.className='gridAlternatingRow'";
    gridViewRow.Attributes.Add("onmouseover", "this.className='gridSelectedRow'");

    the header jumps to the top of the scrolled div, and is no longer fixed.

  • You'll need to ensure that the CSS classes added to the rows when the GridView first loads are preserved as you switch classes with your JavaScript code.

Comments have been disabled for this content.