I am currently developing an ASP.NET web based planning application for a client. The application is very data heavy with a lot of pages displaying large amounts of data in a tabular format. As you would expect I’ve implemented this using a combination of SQL stored procedures, .NET DataTables and finally a GridView on the page itself. I thought I was almost done until a resent review meeting where the client suggested it would be great if they could freeze the table headers and left most columns just like Excel so that they could view the large tables more easily.
After a bit of googling I found a number of examples of how this can be done to html tables all using a combination of CSS and javascript expressions contained within the style sheet itself. A great example can be found here. However when I tried to apply this to an ASP.NET GridView it didn’t work without a fair bit of tweaking! I thought I’d step through what I did to help anybody out faced with a similar demanding clients!
There are three parts to setting this up:
- The Mark-up
- The CSS
- The server side code to apply the relevant styles
The Mark Up
The main issue is that by default the GridView does not render its table header cells as <th> elements instead using the <td> element (Bad Microsoft!). However there is a way around this, by adding the UseAccessibleHeader="True" attribute to the datagrid we can force the control to render in a more compliant way and use the <th> elements. We also need to wrap the Gridview control in a div element to provide the scroll functionality.
<div id="GridViewScrollContainer">
<asp:GridView UseAccessibleHeader="true" ID="GridView1" runat="server" OnRowDataBound="GridView1_RowDataBound">
</asp:GridView>
</div>
The CSS
The following CSS is then used to implement the frozen elements, one thing to note is that if you change the name of the surrounding Div in your mark-up make sure you also change the name in the getElementById expressions.
#GridViewScrollContainer
{
width: 97%;
height: 400px;
overflow: auto;
margin-left: 5px;
} div#GridViewScrollContainer table th
{
width: 100px;
} #GridViewScrollContainer th
{
top: expression(document.getElementById( "GridViewScrollContainer" ).scrollTop-2);
z-index: 20;
} #GridViewScrollContainer td.locked, #GridViewScrollContainer th.locked
{
left: expression(document.getElementById( "GridViewScrollContainer" ).scrollLeft);
position: relative;
z-index: 100;
border: solid 1px white;
}
#GridViewScrollContainer th, #GridViewScrollContainer th.locked
{
position: relative;
cursor: default;
border: solid 1px white;
}
#GridViewScrollContainer th.locked
{
z-index: 110;
}
The Server side code
Finally on the RowDataBound event of the GridViewControl we can allocate the columns that we wish to freeze the relevant CssClass.
protected void gvResults_RowDataBound(object sender, GridViewRowEventArgs e)
{
//lock the first 3 columns
e.Row.Cells[0].CssClass = locked;
e.Row.Cells[1].CssClass = locked;
e.Row.Cells[2].CssClass = locked;
}
One major thing to note is that this solution will not work in IE7 if you mark your page with an XHTML 1.0 doc type, I believe this is due to the lack of support for the CSS expressions going forward. To solve the problem change the doc type from XHTML 1.0 to the old HTML 4.0 shown below:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
This obvisly isnt ideal because you are essentially throwing the browser back into ‘qwerks’ mode but for my particular page this didn’t have any adverse effects, plus the application will be running in a closed envirnment where IE6 and 7 are the only browsers in use.
I Hope this helps people and please let me know if you have any comments / suggestions for a more cross browser solution.