Freezing GridView Column Headers using Only CSS

Recently I wrote an article describing how you could use CSS plus a GrdiView Control Adaptor to add the 'frozen column headers' feature to your GridView. 

Live Demo (IE only) | Download

I received the following comment from a kind reader named Mitch:

I have also noticed that applying a skin to the gridview also seems to create TH elements for the headers. I have a simple solution that freezes the headers by doing the following:

    • apply a skin to a gridview.
    • wrap the gridview in a div or fieldset.
    • Apply a css class to the wrapper that sets the overflow to scroll and the TH position to relative. You may need to set the TR height to 0px for IE.

I set the height of the wrapper in the local style so I can reuse the css class. I have tested this in IE6 & 7 and Firefox and it works great.

Wow.  If this is true, it has a number of advantages over my original solution:

  1. It can be applied without using Control Adaptors - one less dependency
  2. It works with both IE and FF
  3. It is really simple!

So I took this feedback and tried to fix up my old sample site.  I created a new site from my trusty GridView web site template and started following Mitch's advise.  First off, I created a regular GridView and placed it inside a DIV element like so.  Notice I assign the DIV to the container class.

<div class="container" style="height:300px; width:700px;">
    <asp:GridView 
        runat="server" AutoGenerateColumns="false" 
        AllowSorting="true" DataSourceID="odsCustomers" BorderWidth="0px">
        <Columns>
            <asp:BoundField HeaderText="ID" DataField="CustomerID" SortExpression="CustomerID" />
            <asp:BoundField HeaderText="Name" DataField="ContactName" SortExpression="ContactName" />
            <asp:BoundField HeaderText="Title" DataField="ContactTitle" SortExpression="ContactTitle" />
            <asp:BoundField HeaderText="Address" DataField="Address" SortExpression="Address" />
            <asp:BoundField HeaderText="City" DataField="City" SortExpression="City" />
        </Columns>
    </asp:GridView>
</div>

Next, I created the container css style class as Mitch suggests ...

/* So the overflow scrolls */
.container {overflow:auto;}

/* Keep the header cells positioned as we scroll */
.container table th {position:relative;}

/* For alignment of the scroll bar */
.container table tbody {overflow-x:hidden;} 

Finally, I added back in the other styles that are not related to the frozen headers to spruce things up a bit.  At this point I opened IE to test it out and sure enough it works great.  The only problem is that (again) it doesn't work in FireFox.  It behaves better than my original solution in that the grid renders fine, just the header columns do not stay stationary.  Even though it isn't perfect, I figured I would pass Mitch's solution on since it seems to be hands down better than my original one.  If anyone know's how FF can be supported with pure CSS, I would be interested.

That's it.  Enjoy!         


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/67

Comments


Posted by: Mustafa Basgun on September 18, 2007 12:00 AM

Matt,

In this approach, using Panel control instead of DIV may make more sense since Panel itself is more playable at the VB side.



By this way, we can dynamically set the height of the panel (well, actually DIV) when freezing of the header is really needed depending on the real estate on the UI. Sample code can be like:

Dim dsa As DataSourceSelectArguments = New DataSourceSelectArguments()
dsa.AddSupportedCapabilities(DataSourceCapabilities.RetrieveTotalRowCount)
Dim dv As Data.DataView = CType(SQLDataSource1.Select(dsa), Data.DataView)
Dim m As Integer = CType(dv.Count.ToString, Integer) # of rows in GridView1

If m > 10 Then
Panel1.Height = "500"
End If

Thanks for this great post!

Would position:fixed on the TH work in Firefox?

Posted by: Atanas Korchev on September 18, 2007 12:00 AM

I think you can overflow just the tbody for FF. Check this out:http://web.tampabay.rr.com/bmerkey/examples/nonscroll-table-header.html

Posted by: Mike on September 27, 2007 12:00 AM

Have you tried this in a file that uses a MasterPage? I added the style to the Master and my gridview headers dont freeze.

@Mike

I did try using this with a Master page and I didnt have any issues. There must be something else going on ...

You can donwload the sample project here
to see if you can spot any differences.

Hope that helps.
Matt.

Posted by: Mesut on October 11, 2007 12:00 AM

this works great for me. Thanks Matt.

mesut

Posted by: Rohit Singh on October 11, 2007 12:00 AM

Hi Matt,
By looking at this article, Im really hopefull of getting some clue for my strange problem with Gridview.
The requirement is that, I need to provide a gridview in a tag with width in % not in px and same with each bounded columns width. And using css "table-layout:fixed" and "word-wrap:break-word" for grid and coulumn respectively to achieve word wrapping. The issue is word wrapping is happening but the width is not what i have defined.
For eg: width of grid is 50% and for two columns with 20% and 80%. But its rendering the columns as 50% each.
One thing I found, if we remove the tag from rendered page (view source) it renders correctly as 20% and 50% but if dont it renders as 50% each.

i guess some thing related to im missing.

Posted by: Steve on October 14, 2007 12:00 AM

Good stuff, but without firefox support its not even usable.

Interesting that we get all this great ajax toolkit from MS, etc... but we dont have simple header freezing code built into gridviews. Especially since weve already rewritten the datagrid once already lol

@Steve -

Thanks for the feedback. You are right, without FF support this isnt a complete solution.

Matt.

Posted by: Vince on November 21, 2007 12:00 AM

Matt, seems to work but has a few side effects.

My gridview was inside a div with .
That no longer works, it seems the culprit is the "style:height: width:" attribute. I tried adding text-align:center; but that didnt help. Any idea how to keep the table centered?

The second problem was that the table has a border and when scrolled, the border scrolls too and leaves a 2px space on top of the header through which you can see part of the text as you scroll by. Im using master pages by the way.

Posted by: Vinc3 on November 21, 2007 12:00 AM

Regarding the centering issue, I enclosed the div containing the gridview in a panel with a cssstyle which says "text-align:center;" and that worked fine.

Posted by: Vince on November 21, 2007 12:00 AM

Ok, after much fiddling, I found that if I made the enclosing table have no top border and the header have a 2px top border, it seems to solve the problem of the top table border scrolling up. Heres what I used:
.container {overflow:auto;}.container table {border-width: 0px 2px 2px 2px;}.container table th { position:relative; border-width: 2px 1px 0px 1px; border-style: solid; border-color:Black White Black #336699; }

Posted by: Gaurav Rawat on November 23, 2007 12:00 AM

Hi Matt,
This works fine if we had one row of column headers.My probleim is that I can have 2 or three rows of column headers.So when I scroll I am able to keep my original column header row fixed but other rows of column headers above it are gone.Any idea how to solve this.
Thanks in advance.

Posted by: Socko on November 28, 2007 12:00 AM

Does this technique work if the table also needs to scroll horizontally?

I have used other CSS methods (like: http://www.codeproject.com/aspnet/DataGridFixedHeader.asp)

which works well - unless the table needs to scroll horizontally - in which case the header cells stay in place while the detail scrolls left and right.

Your live demo only scrolls vertically - so I am curious whether it suffers from the same limitation.

Posted by: Manoj Aggarwal on November 29, 2007 12:00 AM

I am using gridview in update panel. I was loading UserControl dynamically in update panel. My gridview contains a checkbox column. What happens is whenever i scroll the gridview and checks a checkbox the header resets its position to top of the page equivalent to the amount of scroll that i have done in div. Any ideas behind the strange behaviour?

Posted by: varadhg on December 6, 2007 12:00 AM

Hi Matt,

Is there a way to freeze the gridview column wise ? so for e-x, if we freeze column 0, column 1 and when scroll horizontally these 2 columns are always visible ?

Thanks,

varadhg

Posted by: vlad on January 3, 2008 12:00 AM

Thanks for the post, but when I change one data in the XML file (the database) and i tried to exceed with more than 100 char without space, the header ruin the layout. Is there other way to limit the width and wrap the row data?

Posted by: mary on January 20, 2008 12:00 AM

how do you save the scroll position on refresh/postback?

Posted by: David on January 24, 2008 12:00 AM

I am also havng a similar problem to Socko. Quote:

"I am using gridview in update panel. I was loading UserControl dynamically in update panel. My gridview contains a checkbox column. What happens is whenever i scroll the gridview and checks a checkbox the header resets its position to top of the page equivalent to the amount of scroll that i have done in div. Any ideas behind the strange behaviour? "

I also have two cases of this in my project. One is a gridview that has a mouse over event which is inside an Accordion Control (essentially updatepanel) and if you have scrolled down and then mouse over, the heder resets its position instead of staying fixed.

My other problem is a gridview control within an AJAX Tab Control. When you have scrolled down and then mouse over the tab header (causing it to flash) the header zooms up and the whole gridview shoots to bottom of page.

Thanks for your help

Posted by: Adeel on January 30, 2008 12:00 AM

It is not working in mozilla firefox

Posted by: Anu on February 4, 2008 12:00 AM

Hi,

This works fine util you attach a cssclass to the mouseover event for every datarow. When you do this the header moves up by the no of rows that you have scrolled and moves down when scrolled down using the mouse. Works fine though when u scroll using arrow keys or click on the scroll bar.
Can you please explain this and also how to solve the problem.

Thanks.

Posted by: RagingKore on February 20, 2008 12:00 AM

This is the only thing that works and is very simple.

.div_grid_container
{
overflow: auto;
height: 150px;
}

.grid_header
{
background-color: #01a0bc;
height: 26px;
position: relative;
top: expression(this.offsetParent.scrollTop-2);
}

Posted by: Paul on April 10, 2008 12:00 AM

I found the solution for the moving header problem: For the parent div that the table headers are located in, set the position to relative as well.

Heres example code:
/* parent div */
div.scrollTable
{
position: relative;
width: 95%;
height: 400px; /* must be greater than tbody*/
overflow: auto;
margin: 0 auto;
padding-right: 10px;
}

/* static header */
.scrollTable th
{
position: relative;
top: expression(offsetParent.scrollTop);
}
--------------
Not 100% sure why it works, but it may be because the jabascript needs to compute the headers location from the parents location (which was set to fixed before, and is now relative).

Posted by: Paul on April 10, 2008 12:00 AM

Incidentally... this also fixes the horizontal scroll issue. Neat!

Posted by: Elaine Seah on April 25, 2008 12:00 AM

Hi, i have the paging function on my grid in top and bottom, may i know how to fix the position for the paging? Thank for help ^^

Posted by: MikeT on April 30, 2008 12:00 AM

Great code, thanks! I was able to use Matts code plus some of the code in the comments (Paul and RagingKore) in order to solve all of my needs. Thanks! Your site rules!

Posted by: Dharmeen on May 5, 2008 12:00 AM

Thanks. it solved all my problems

Im trying to follow this for Firefox 2. Posts by Elaine on 4-25 imply she got working for Firefox but Im not really clear about that. Heres what I adapted from the above posts (works fine in IE7 but in Firefox the table header row just scrolls out of sight; also the CSS expression on the top property is indicated invalid by Visual Studio 2005 editor)
/* So the overflow scrolls */
div.coveragesContainer
{
overflow: auto;
width: 95%;
height: 380px; /* must be greater than tbody*/
position: relative;
margin: 0 auto;
}
/* Keep the header cells positioned as we scroll */
.coveragesContainer table th
{
position:relative;
top: expression(this.offsetParent.scrollTop);
}

/* For alignment of the scroll bar */
.coveragesContainer table tbody
{
overflow:hidden;
}

Posted by: Satheesh on May 19, 2008 12:00 AM

Hi am facing performance issue while using CSS to freeze column. The browser gets hang when number of records increases. let me if you have any solution for this issue.Thanks in advance.

Posted by: Andrew on June 10, 2008 12:00 AM

No matter what method I employ, I can not get the header to freeze in place. This "simple" method of applying the css to the wrapper does not work. I have even compared the html source from the sample site and my own.

Any ideas what would prevent the solution from working?

Posted by: Noor on June 18, 2008 12:00 AM

Can you please tell me how to fix coloum for the GridView

any idea ?

Posted by: Vijai Prakash Maurya on June 20, 2008 12:00 AM

Hi,

I am still facing moving down whole grid some time while clicking on Girdview Sorting.

Can any one provide Solution for this.

Regards
Vijai

Posted by: Travis on June 25, 2008 12:00 AM

For some reason - when I scroll and move off the tabpanel I have the GV in - the header just disappears.

Posted by: Travis on June 26, 2008 12:00 AM

On my post - when I scroll back to the top of the GV, it "reappears" - why would the css just "disappear"?

Posted by: Phillip Beeke on July 1, 2008 12:00 AM

Hey chaps just tried this out and it works for me. Strange behaviour tho when there is a dropdown list in the grid. The dropdowns float over the header. Check boxes dont tho. Any ideas please

Posted by: Phil on July 3, 2008 12:00 AM

Re: Travis

Yeah Ive got the same thing. Ive found that if I have a link on one of my table cells that has a style on it A:HOVER {background: yellow} - it causes the header to disappear. Really frustrating. Will have to ditch this solution and have my table header row in a separate table (yuck)... This CSS solution just doesnt seem to be stable enough, not in IE anyway.

Unless anyone has any ideas...?

Posted by: Irman on July 3, 2008 12:00 AM

Mine have more than 30 columns with multiple row header, its become so "messy", the width is set to 100% but the row header widht extended out of the , no longer inside the .

Please, is there any solution for this?

Very good solution to the problem, However does anyone know the css to have alternating Rows ?

Posted by: J on August 2, 2008 07:31 PM


Solution Posted by: Paul on April 10, 2008 12:00 AM

Works perfectly for me and it solved the mouseover problem as well.

Posted by: Frankie on August 5, 2008 05:09 AM

I've modified the CSS and allow freezing first few columns and header row jst like Excel.

Here is my CSS code:
/* Div container to wrap the datagrid */
div#div-datagrid {
width: 1000px;
height: 460px;
overflow: auto;
scrollbar-base-color:#ffeaff;
}


/* Locks the left column */
th.locked, td.locked
{
background-color: #C9C9C9;
position: relative;
cursor: default;
left: expression(document.getElementById("div-datagrid").scrollLeft-2); /*IE5+ only*/
}

/* Locks table header */
th {

text-align: center;
background-color: navy;
color: white;
border-right: 1px solid silver;
position:relative;
cursor: default;
top: expression(document.getElementById("div-datagrid").scrollTop-2); /*IE5+ only*/
z-index: 10;
}

/* Keeps the header as the top most item. Important for top left item*/
th.locked {z-index: 99;}

/* DataGrid Item and AlternatingItem Style*/
.GridRow {font-size: small; color: black; font-family: Arial; background-color:#ffffff; height:35px;}
.GridAltRow {font-size: small; color: black; font-family: Arial; background-color:#eeeeee; height:35px;}

.Grid
{
font-size: small;
border-color: #CCCCCC;
border-style: none;
border-width: 1px;
font-family: Tahoma;
font-size:x-small;
padding: 0, 0, 0, 100;
background-color:White


Here's problem, my grid contains text box. The aligment or position of the freeze pane went missing or mis-alligned when one of the text box is filled.

Any solution?

Thanks.

Michael,

your CSS doesn't seem to be suitable for the templated stuff on GridView etc., since you use additional CSS for TH or TD. Or how can you get that into the HTML through the ASPX-page?

All the best

Michael

Also: I wanted to add a "Date"-column, so I modified your code by inserting (after the lblSize.Text):

Label lblDat = (Label)args.Row.FindControl("lblDat");
lblDat.Text = fileInfo.LastWriteTime.ToString("d");

...and after the lblSize-template:





But now the next challenge is to sort the directory-listing after Name/Size/Date - and of course it should correctly sort size and date, not based on their string-representation in the table. I guess it should be possible because the 'raw' data is available via the datasource, but the exact implementation is a bit beyond me currently.

Ideas anyone? ;)

Posted by: cb on September 9, 2008 04:42 PM

This is the easiest way I have found:

Protected Sub uxItemDetails_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs)

If e.Row.RowType = DataControlRowType.Header Then

e.Row.Style.Add("position", "relative")
e.Row.Style.Add("top", "0px")
e.Row.Style.Add("z-index", "2")

End If

End Sub

Posted by: Khushi2005 on September 18, 2008 01:02 PM

Here is what I came up with
It is combination of all the solutions I got after Google search and combination of the code in the above examples


In css Stylesheet

.scrollTable
{
position: relative; /*width: 200px; set it on individual page
height: 120px; must be greater than tbody*/
overflow: auto;
margin: 0 auto;
/*padding-right: 10px;*/
border-top: gainsboro thin solid;
border-left: gainsboro thin solid;
border-right: gainsboro thin solid;
border-bottom: gainsboro thin solid;

padding-bottom:0px;




}

scrollTable table
{

border-spacing:0px;
padding-top:0px;
}

th.MyStaticCol, td.MyStaticCol
{
position: relative;
left: expression(offsetParent.scrollLeft); /*top: expression(offsetParent.scrollTop);*/
color: Black;
/*background-color: #b0c4de;*/
border-right: orange thin inset;
border-width: 1px; border-style: solid outset;
padding-top:0px;
border-spacing:0px;
}

.scrollTable th
{
/* this is the class which gets attache dto gridview header */
/******imp position :relative and top expression set so that hearder do not scroll with scroll*/
position: relative;
top: expression(offsetParent.scrollTop);
color: White;
height: 20px;
padding-top: 0px;
border-right: gainsboro 1px solid;
border-top: gainsboro 1px solid;
/*border-left: darkviolet 1px;remove left border since it makes the header col border bold getting overlapped with right border of adjacent cell */
border-bottom: gainsboro 1px solid;

font-size:10px;
font-family:Tahoma;


}

th.MyStaticCol
{
z-index:99;
}


.View_RowStyle
{
background-color: #EFF3FB;
Font-Size:10px ;
Color:#333333;
/* color:Navy;*/
height:15px;
/* border-width: 1px; border-style: solid dotted; */
word-wrap:break:word;
}

.View_AlternatingRowStyle
{
background-color:#ffffff;
Font-Size:10px;
height:15px;
Font-Size:10px ;
}

.View_HeaderStyle
{
color: White;
height:15px;
border-width: 1px 1px 1px 1px;
border-style: solid;
border-color:Black;

background-color: #b0c4de;

font-weight: bold;
text-align: center;

border-spacing:0px;
padding-top:0px;
font-size:10px;
font-family:Tahoma;

/* position: Relative; this is set so that hearder do not scroll with scroll
border-bottom-width: thick;
z-index: 10;
*/
}


/* GridView Skin settings */

/* GridView Skin settings */

.View_RowStyleForEdit
{
background-color: #EFF3FB;
Font-Size:8px ;
Color:#333333;
/* color:Navy;*/
height:10px;


}

.View_AlternatingRowStyleForEdit
{
background-color:#ffffff;
Font-Size:8px;
height:10px;
}

.GridStyle
{
font-family:Tahoma ;
Font-Size:Medium;
Color:#333333;

border-style:solid;
border:solid 1px black;
padding:0px;
margin:0px
/* HorizontalAlign:Left;
BorderStyle:Solid;
BorderWidth:1px */
}

.View_FooterStylL
{
background-color: #b0c4de; /*lightSteelBlue;*/
color: #fff;
font-weight: bold;
Font-Size:Small ;
}

/*.View_RowStyle
{
background-color: #EFF3FB;
Font-Size:10px ;
Color:#333333;

height:15px;


}
*/


.View_SelectedRowStyle
{
background-color: #D1DDF1;
color: Maroon;
font-weight: bold;
font-size:x-small;

}

.View_PagerStyle
{
/*background-color: orange;*/ /* #b0c4de;/* LightSteelBlue/* #FFc080;*/
color: #fff;
text-align: right;
font-size: x-small;
font-family: Tahoma;
/*background-image: url(Images/BkgImages/background_main.jpg);*/
background-image: url(../Images/BkgImages/background_main.jpg) ;
background-repeat: repeat-x;
}





In GridView RowDataBound event

e.Row.Cells[0].CssClass = "MyStaticCol";
e.Row.Cells[1].CssClass = "MyStaticCol";

With This 2 Col are locked

Thanks

Posted by: TheBluePrince on October 3, 2008 11:51 AM

Forget Firefox. It's cute, but if it can't play by the rules of the market-dominant browser (IE) it will never be useful for development. Developers should not be expected to write code multiple times because Firefox doesn't want to implement what Microsoft uses.

Posted by: Achutha Krishnan on October 6, 2008 01:27 AM

Hai,

@Author:

Your CSS works fine. But when Mouse over the Header, it disappears and won't come again. I need to refresh the page in order to bring it back. Any solution for that?

Posted by: Qin Liu on October 17, 2008 05:17 PM

Thanks for the codes. My challange is to contain the gridview in certain width, even though the actual columns can be many. So, for my view, everything works well, except the headers can not be contained within the div and it overflows. The rest of the table is contained within the div; when I horizontally scroll the table, the headers don't move. Is there a way to solve this problem?

thanks, Qin

Posted by: Ed on October 23, 2008 04:05 AM

With this sample in IE6 and IE7 the header will stay frozen and scroll horizontally with the details.
It sometime bounces, but always comes back.
Only works in IE6 and IE7.
Not a problem for me, as I use internally and only IE6 and IE7 can access the application.
While shown with ASPX, will work in any HTML page.


************************************
STYLESHEET EXAMPLE : See Freezing
************************************
th{
}

.Freezing {
position:relative;
top:expression(this.offsetParent.scrollTop);
z-index:10;
}

Just add class="Freezing" to each TR in your THEAD for your TABLE and the header is frozen.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Ed wrote: With this sample in IE6 and IE7 the header will stay frozen and scroll horizontally with the details...
  • Qin Liu wrote: Thanks for the codes. My challange is to contain the gridview in certain width, even though the act...
  • Achutha Krishnan wrote: Hai, @Author: Your CSS works fine. But when Mouse over the Header, it disappears and...
  • TheBluePrince wrote: Forget Firefox. It's cute, but if it can't play by the rules of the market-dominant browser (IE) it...
  • Khushi2005 wrote: Here is what I came up with It is combination of all the solutions I got after Google search and co...
  • cb wrote: This is the easiest way I have found: Protected Sub uxItemDetails_RowCreated(ByVal sender As Object...
  • Michael wrote: Also: I wanted to add a "Date"-column, so I modified your code by inserting (after the lblSize.Text)...
  • Michael wrote: Michael, your CSS doesn't seem to be suitable for the templated stuff on GridView etc., since you u...