Partager via


Real World GridView: Excel-like Frozen Headers for ASP.NET 2.0

Many of us are familiar with frozen cells in Excel, but it is typically quite difficult to implement something like that HTML. In this “Real World GridViews”, we investigate adding this functionality to GridView to make frozen headers easy to reuse across pages. Though I am only going to go over freezing headers here, once you get the idea, it is a short step to freeze columns (right or left) and footers.

 

 

This is part 3 of “Real World GridViews”, and if you missed part 1 or 2 then you have some reading to do J Here is a link to Part 2: https://blogs.msdn.com/mattdotson/articles/541795.aspx

 

I’m starting with a refresher course before we delve into the details. Since a web control’s primary purpose is to spit out HTML, it’s pretty darn important to understand exactly what HTML a web control spits out, in relation to what HTML you want it to render. Really GridView just renders a HTML table, surrounded by a DIV, but it is important to understand that.

 

<div>

    <table cellspacing="0" rules="all" border="1" id="SampleGrid" style="border-collapse:collapse;">

        <tr>

            <th scope="col">Header</th>

            ...

        </tr>

        <tr>

            <td>Data</td>

            ...
</tr>
</table>
</div>

 

Notice that all the properties you set on the GridView (id, borders, etc) end up getting set on the TABLE element. That is going to be of particular interest to us later in this article. Now we are ready to get started!!

 

This work is based on the brilliant algorithm of Brett Merkey, and if you want to really understand how it works, visit his web site (https://web.tampabay.rr.com/bmerkey/examples/locked-column-csv.html). To summarize his work, he has devised a way to use CSS expressions to create the illusion of “relative fixed positioning”. His algorithm is fixing the position of a TH or TD relative to the surrounding DIV. In case you’ve never heard of a CSS expression, it is just some JavaScript in the CSS style which reevaluates whenever the page changes (i.e. someone scrolls, or a dependant element changes). IE does some black-magic to figure out if a style bound to an expression needs to be reevaluated, and seems to err on the side of forcing reevaluation if it’s not sure if something changed. That said, be warned that if you have a lot of elements with CSS expressions applied to them (over a thousand on my machine), CSS expression can use up a LOT of client side CPU, so be judicious in their use. Secondly, IE is the only browser which supports CSS expressions, so this is an IE only solution.

 

I have made some performance optimizations to Brett’s original code, but conceptually it’s the same thing. Brett uses getElementById which can get VERY slow with large tables, though he does include a note about using “parentNode.parentNode.parentNode.parentNode.scrollTop” which is a little better. After some extensive testing, I’ve found an even faster way: “this.offsetParent.scrollTop”. The key to the outer div being the offset parent is that it’s positioning is set to “relative”, which seems odd because that is the default, but it has to be set!

 

We are going to make this control completely self contained, so you will not have to deploy a separate CSS with it. The first thing we need to do is register the styles. In ASP.NET 1.1, this would have been difficult, but fortunately ASP.NET 2.0 has made this significantly easier. First we are going to create a class for our style which inherits from “Style”.

 

private class FrozenTopStyle : Style

{

    protected override void FillStyleAttributes(CssStyleCollection attributes, IUrlResolutionService urlResolver)

    {

        base.FillStyleAttributes(attributes, urlResolver);

        attributes[HtmlTextWriterStyle.Top] = "expression(this.offsetParent.scrollTop)";

        attributes[HtmlTextWriterStyle.Position] = "relative";

        attributes[HtmlTextWriterStyle.ZIndex] = "2";

    }

}

 

And now that we’ve done that, we can register it with the page. I’ve added a simple check to make sure that we only register the class once when we have multiple grids on the same page.

 

protected override void OnPreRender(EventArgs e)

{

    base.OnPreRender(e);

    if (this.FreezeHeader && !this.Page.Items.Contains(FrozenGridView.FrozenTopCssClass))

    {

        this.Page.Items[FrozenGridView.FrozenTopCssClass] = "Registered";

        this.Page.Header.StyleSheet.CreateStyleRule(new FrozenTopStyle(), null, "." + FrozenGridView.FrozenTopCssClass);

    }

}

 

 Now that we have our styles registered, it’s time to apply those styles to our header. As you’ve seen in the other articles, we do this after data binding. The FreezeCells() function just loops through the cells in a header and applies the CSS class to each cell. It’s interesting to note that this code combines the existing style and our style. Many people don’t realize that you can assign more than one CSS class to an element just by separating them by a space (i.e. class=“style1 style2” applies both style1 and style2).

 

private void FreezeCells()

{

    if (this.FreezeHeader)

    {

        foreach (DataControlFieldHeaderCell th in this.HeaderRow.Cells)

        {

            th.CssClass = FrozenGridView.FrozenTopCssClass + " " + th.CssClass;

        }

    }

}

This is where we run into a challenge. We have applied the styles to all the header cells, and now we need to apply some styles to the DIV surrounding the GridView’s table. Unfortunately GridView does not supply us with a way to easily modify the surrounding DIV, and in some cases it doesn’t even display the DIV!! Because of this, we are forced to override Render(). Using my favorite tool, .NET Reflector, I was able to figure out what GridView’s implementation of Render did, and just add the stuff I needed.

 

Naturally, you are asking, “what do we need to add”? Well, we want this thing to scroll, so we need to set the overflow style to something appropriate. We actually let the page tell us what type of scrolling it wants with our Scrolling property, so we need to translate a ScrollBars variable into the appropriate CSS style. We’ve created two private properties which help us out. Here we’ll just look at OverflowX, because OverflowY is very similar:

 

private string OverflowX

{

    get

    {

        if (this.Scrolling == ScrollBars.Horizontal || this.Scrolling == ScrollBars.Both)

        {

           return "scroll";

        }

        else if (this.Scrolling == ScrollBars.Auto)

        {

            return "auto";

        }

        else

        {

            return "visible";

        }

    }

}

Secondly, scrolling only happens when the DIV is smaller than the TABLE, so we need to be able to set the Height and Width of the DIV separately from the dimensions of the TABLE. As we saw in the refresher course at the beginning of this article, GridView’s implementation only decorates the TABLE tag, and does nothing to the DIV. In order to get the behavior we want, we are going to have to change that. I’ve overridden the GridView’s default implementation of the Height and Width to set the DIV’s dimensions and added ScrollHeight and ScrollWidth to set the dimensions of the TABLE.

 

public Unit ScrollHeight

{

    get

    {

        return base.Height;

    }

    set

    {

        base.Height = value;

    }

}

public override Unit Height

{

    get

    {

        object val = this.ViewState["DivHeight"];

        if (null == val)

        {

            return Unit.Empty;

        }

        return (Unit)val;

    }

    set

    {

        this.ViewState["DivHeight"] = value;

    }

}

Finally we need to write all these styles to the DIV. We do that in our override of the Render() function.

 

writer.AddAttribute(HtmlTextWriterAttribute.Id, String.Format(CultureInfo.InvariantCulture, "__gv{0}__div", clientID), true);

writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowX, this.OverflowX);

writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowY, this.OverflowY);

if (!this.Width.IsEmpty)

{

    writer.AddStyleAttribute(HtmlTextWriterStyle.Width, this.Width.ToString(CultureInfo.InvariantCulture));

}

if (!this.Height.IsEmpty)

{

    writer.AddStyleAttribute(HtmlTextWriterStyle.Height, this.Height.ToString(CultureInfo.InvariantCulture));

}

writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative");

writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "Black");

writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "3");

writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid");

writer.RenderBeginTag(HtmlTextWriterTag.Div);

That’s it! We now have a GridView which will provide frozen column headers and scrolling just by setting a few properties from the page!! You can see from the series of articles that most of the time GridView is kind to inheritors, however there are those few cases (like our render function) where simply extending the base implementation just won’t cut it. When you find yourself in one of these situations, don’t panic, make sure you have .NET Reflector in your toolbox.

 

I’ve posted the code for all three articles @ https://www.codeplex.com/ASPNetRealWorldContr . You can actually see what I am working on next if you look around!!

Comments

  • Anonymous
    March 02, 2006

    Thanks a lot. This help me out a lot.

  • Anonymous
    March 05, 2006
    Cassini v2 / Visual Studio 2005 Web Server
    Source [Via: gduthie ]
    Debugging AJAX Apps, Part 3 - IE...

  • Anonymous
    April 05, 2006
    "this.FreezeHeader && !this.Page.Items.Contains(FrozenGridView.FrozenTopCssClass)"
    i get an error at freezeheader & fronzentopssclass. What are these??

  • Anonymous
    April 17, 2006
    The comment has been removed

  • Anonymous
    April 18, 2006
    Matt, thanks for these articles, they are fantastically helpful.  I have one problem at the moment with the scrolling grid:  I would like to just use horizontal scrolling along with the in-built paging option rather than vertical scrolling as I need to freeze 6 columns and the number of possible rows can put too much strain on the user's browser.  However, when I try this I get a null reference exception from GridView BuildCallbackReference when processing the paging at data binding time (I am binding to a data table).  Any thoughts on how I might resolve this?

  • Anonymous
    April 25, 2006
    I seem to be having problems with the code i downloaded from the gotdotnet site.  i created the new control, compiled, and then added to my website.  i'm able to get the grid and show but the problem is the scrolling portion of the grid seems to push the text up into the header area.

  • Anonymous
    April 27, 2006
    In the given scenario when you maximize screen header diappears.

    width  empty
    Height empty
    FreezeHeader set to true
    and scrolling set Both

  • Anonymous
    May 01, 2006
    Will This Work with Dropdown lists in Gridview?
    as Dropdown always comes over the freezed column?


  • Anonymous
    May 08, 2006
    Well, how can i write it in VB ? Please help me.

  • Anonymous
    May 25, 2006
    In trying to implement this code, I am getting the following build error:

    Error 1 'Forms_Admin_Documents.Height': no suitable method found to override C:InetpubwwwrootPhysicianOrdersFormsAdminDocuments.aspx.cs 96 26 C:...PhysicianOrders


    I see the height method as part of the page.  What am I missing??

    Thanks,

    Kent

  • Anonymous
    May 30, 2006
    Thanks for the excellent articles and code! I ran into one problem using the DropDowns.aspx. I changed the BulkEditGridView control to allow Insert. The new row did get created but the state dropdown was not populated.

    The only change I made to your code was:
    <rwg:BulkEditGridView .........
                   InsertRowCount="1" EnableInsert="true" ShowFooter="false">

    Thanks again.

  • Anonymous
    June 01, 2006
    Will this work with DropDownLists in Gridview, as DropDownLists run on top of the frozen headers as soon as you scroll?

  • Anonymous
    June 20, 2006
    I'm having difficulties with the VB version you posted on GotDotNet. It works brilliantly except that it overwrites any styles I've got declaratively setup in my GridView.

    e.g.

    <asp:BoundField DataField="EnrolmentID" HeaderText="Enrolment ID" HeaderStyle-Wrap="false" HeaderStyle-Width="90" ItemStyle-HorizontalAlign="Center" HeaderStyle-CssClass="gridEndCol" />

    The gridEndCol CSS class isn't in the HTML served to the browser.

  • Anonymous
    June 26, 2006
    I was getting an object instance error when the result set was empty, and The EmptyData Template would not display.  To work around this error, here is the code I used:

    Private Sub FreezeCells()
    If Me.FreezeHeader Then
    If (Not Me.HeaderRow Is Nothing) Then
    For Each th As DataControlFieldHeaderCell In Me.HeaderRow.Cells
    th.CssClass = FrozenGridView.FrozenTopCssClass & " " & th.CssClass
    Next th
    End If
    End If
    End Sub

    The issue was that Me.Freezheader was TRUE, but there was no object Me.HeaderRow, and since there was a reference to this object, the error was being generated.

    Now the EmptyData Template is being displayed in there are no records returned.

  • Anonymous
    June 26, 2006
    Usually you have a GridView working with a FormView to show details of the selected record.

    The unanswered question is how to maintain scroll position of the GridView after postback functions, problematic if you have more than one view inside a multiview linked to the GridView. SmartNavigation="true" works on the first view only.

    Anyone??

  • Anonymous
    July 03, 2006
    The comment has been removed

  • Anonymous
    July 03, 2006
    The comment has been removed

  • Anonymous
    July 12, 2006
    I'm using the BulkEditGridView and can't get it to save changes. the HandleRowChanged event handler never gets called when I change the value in the textbox. Got any ideas on a cause?

    Thanks for the nice control.

  • Anonymous
    August 03, 2006
    Is there any solution that works with Firefox 1.5x ?

  • Anonymous
    August 04, 2006
    Very helpful and interesting artical. I found some other articals which cover the same problem but use an pure CSS approach without CSS expressions.

    http://blogs.crankygoblin.com/blogs/geoff.appleby/articles/49529.aspx

    http://www.imaputz.com/cssStuff/bigFourVersion.html

  • Anonymous
    August 07, 2006
    I need to allow users to use the arrow keys on a BulkEditGridView. I figured I'd add an event handler to the RowCreated event so I could find the TextBox instance being created and then add a javascript handler to the textbox for the keyup event. However, things aren't working quite like I'd assumed they would. Any suggestions?

  • Anonymous
    August 31, 2006
    Where is the Database "Pubs"?

  • Anonymous
    August 31, 2006
    The requirement I had was that within the frozen grid table the first column had a series of hyperlinks. Clicking on the hyperlink would load details for that person and display those details loaded in another table AND that the scroll position of the first table be maintained. A solution for this is as follows:

    Add the following function top a js file that will be referenced.

    function scrollToRow(inputId)
    {
       var aObj = document.getElementById(inputId);
     
       var offsetY = 0;
       var elem = aObj;

       offsetY += elem.offsetTop;
       elem = elem.offsetParent;

       offsetY += elem.offsetTop;
       elem = elem.offsetParent;

    elem.offsetParent.scrollTop = offsetY - document.getElementById(elem.id).rows[1].offsetTop;
    }

    Download and modify FrozenGridView Render method as follows:

    protected override void Render(HtmlTextWriter writer)
    {
       …..
       …..
       …..
                   writer.RenderBeginTag(HtmlTextWriterTag.Div);

                   
               }            this.RenderContents(writer);
               if (!this.DesignMode)
               {
                   writer.RenderEndTag();

                   // Now check if we are in a post back situation
                   // If we are then need to check that if the user has trigged the postback by clicking
                   // select on one of the rows of the table.
                   if(this.Page.IsPostBack)
                   {
                       string ctrl = this.Page.Request.Params["__EVENTTARGET"].Replace("$", "");

                       // handle the situation of multiple frozen grids on a single page.
                       if (ctrl.StartsWith(this.ClientID + "
    "))
                       {
                           this.Page.ClientScript.RegisterStartupScript(typeof(Page), "frozenGrid", "<script>scrollToRow('" + ctrl + "');</script>");
                       }
                   }
               }


    Richard Peters
    Senior Application Developer
    Core Technology Ltd
    t:  +64 (0)4 801 2250
    m: +64 (0)21 114 7132
    e: Richard.Peters@coretech.co.nz
    w: www.coretech.co.nz

  • Anonymous
    September 28, 2006
    Matt, awesome example!  I ended up creating a custom control within a web user control.  Two things for anyone that might be stuck out there.

    1.) To see the scrolling do not let the GridView control its own size.  Set the Height and Width properties.

    2.) I used used the @Register assembly in my .ascx file rather than in the web.config.  I couldn't get the assembly to load that way.

    Matt, thanks again!

    - Jesse Williams

  • Anonymous
    October 05, 2006
    Can't seem to get the vb version working. Can you please help on anything that i may be doing wrong.  What do i have to add to the page after implementing all of this in a class?

  • Anonymous
    October 07, 2006
    Hi Matt! This is great stuff! ASP.NET 2.0 as multiple serious lacunes gaps and you just solved a big one... or almost. ;-) I am having the same problem as Mehul above. As soon as I use DropDownFields with EnableInsert in a BulkEditGridView, the insert rows DropDowns don't get populated. Is there an easy solution to this problem? Can you post a bugfix? Best regards, Jean-François

  • Anonymous
    October 12, 2006
    Is there any solution to fix dropdown problem in gridview

  • Anonymous
    October 31, 2006
    Brett Merkey's solution looks just like this one:  http://slingfive.com/pages/code/scrollTable/

  • Anonymous
    November 01, 2006
    The comment has been removed

  • Anonymous
    November 01, 2006
    The comment has been removed

  • Anonymous
    November 21, 2006
    Hey Scott Roberts,  thanks for posting the code snippet on saving scroll postion on postback in an AJAX update panel.  This is exactly what I needed. I ran into one issue in point 3 where the compiler complains that divID does not exist in the current context. Any thoughts on how to solve? Thanks in advance

  • Anonymous
    December 19, 2006
    I can't seem to be able to link the Frozen Grid to a SQL Data source.  It tells me it can't find the data source ID - Error setting value 'dsCustomers' to property 'DataSourceId. The Method or Operation is not implemented. Any suggestions?

  • Anonymous
    January 21, 2007
    I was trying to use this, but I could not find that it honour Theming and Skins? All styles was removed, and I still could not get the header to freeze. I have several grids on one page, so maybe that is the problem? I also use your twoline header fix. The gotdotnetworkspace has problems with using the sourcecontrol, so it is not possible to get something out of that. Maybe you could move to codeplex?

  • Anonymous
    February 14, 2007
    The row freezing is a excellent solution which i ever seen Is there as solution for first freezing column...?im not able to get right code...

  • Anonymous
    February 18, 2007
    Okay, I'm totally lost.  Between work-around code-snippets for Ajax and a control that isn't working using a SQL data source for me either and a really stupid simple basic need to freeze the header of a gridview and yet maintain the scroll position of a selected or edited item, I'm totally cnofused. Please provide a link or a zip or something with a really small but complete example of how to do this in VB using a SQL Data source on an Ajax-enabled webform that uses a master page.  I think that would help a lot of people. Thanks

  • Anonymous
    March 13, 2007
    Great article!! I can't undestand when and by who the ScrollHeight and ScrollWidth are call?

  • Anonymous
    March 19, 2007
    The comment has been removed

  • Anonymous
    March 26, 2007
    Ahhh! After weeks of getting "Property value is not valid" and "The method or operation is not implemented" errors when trying to set the ScrollHeight and ScrollWidth properties in the property window in the designer (VS 2005), I finally changed the name of the ScrollHeight and ScrollWidth properties in the control to ScrollGridHeight and ScrollGridWidth, respectively.  And voila, no more errors!  I am assuming that ScrollHeight and ScrollWidth are reserved html properties???  When I would try to set the ScrollHeight property using the property window, I noticed that the properties were not being persisted to the html on the aspx page.  Just don't ask why it took me two weeks to try changing the property names.  Matt, thanks for the code.

  • Anonymous
    May 21, 2007
    can somebody send me aspx page with the whole code working? marduk@o2.pl thanx

  • Anonymous
    May 27, 2007
    Hello Can someone please gove me the compiled library as I dont have visual studio 2005 though I want to use the library in Visual web developer express. Please email to yousuf.khaan AT rediffmail DOT com Thanx

  • Anonymous
    August 22, 2007
    Any help? When in The VS2005 Design View, it parse the properties Task very slowly.  The BulkEdit GrivView contain <asp:TextBox> <asp:Label> I switch between Disign View betweenm Source View and click to use the Wizard to edit. It took very long to read the context. I use the VS 2005 put the .dll into C:Program FilesMicrosoft Visual Studio 8Common7IDE

  • Anonymous
    September 03, 2007
    Hi every body, I have read this article , Its perfectly working in IE 7, But its not working in Netscapae Navigator, Help me Why its not working in Netscape navigator regards

  • Anonymous
    September 24, 2007
    Any one has solution to fix the dropdown list isse with the fixed header?

  • Anonymous
    September 24, 2007
    Any one has solution to fix the dropdown list issue with the fixed header?

  • Anonymous
    October 03, 2007
    hi, i am having a page, and a grid inside a div which has both the scroll bars. if i fixed the header and if i have more columns then the headers are moving out of the div. how will you avoid this. you can mail me at badri.prasad@kavniya.com. Thanks.

  • Anonymous
    November 18, 2007
    I could not find any discussion regarding the problem about dropdowns being present in the DataGrid. Kindly discuss this as all the solutions fail there.

  • Anonymous
    December 10, 2007
    Hi, I have problem with dropdownlist in IE6. It floats over the fixed header of scrollable Gridview.Please help me .Its urgent.

  • Anonymous
    January 01, 2008
    how to fix the header if there is drop dwon list in a template column

  • Anonymous
    January 10, 2008
    If I have dropdownlist as a Template column then it scrolls over the Header region...plz help

  • Anonymous
    January 10, 2008
    If I have dropdownlist as a Template column then it scrolls over the Header region...plz help mayureshs@gmail.com

  • Anonymous
    January 16, 2008
    http://www.w3schools.com/css/css_positioning.asp

  • Anonymous
    March 02, 2008
    Hi, Is any one has the source code for this? please upload the same. Unni

  • Anonymous
    May 19, 2008
    Hi, I wrote a different option to the gridview Freeze header. http://www.grupokino.com/content/view/24/34/ in this option you don not need css or javascript i hope this help you.

  • Anonymous
    June 24, 2008
    Great article. Keep it up sir...

  • Anonymous
    October 29, 2008
    Your story was featured in Drigg! Here is the link to vote it up and promote it: http://codebounce.com/ASPNET/Real_World_GridView_Excel_like_Frozen_Headers_for_ASP_NET_2_0

  • Anonymous
    February 25, 2009
    Matt, thanks for these articles, they are greatfuly helpful. I wonder that, is there any possible situation for sorting header? For I have not been succesfull :s thanks right now.

  • Anonymous
    July 01, 2009
    Your code is very good. I'm coding in VB so I had to convert it, but it only took about 15 minutes to do so because you were so explanatory with your texts in all areas....except one, and I just had to comment on it because It bothers me a lot... (brace yourself, as it is vert trivial) Your Gridview's name...."FreezeHeader"??????? I give you credit for your exceptional code, but please, use standard naming conventions i.e. "grdFreezeHeaderGridView"...

  • Anonymous
    April 09, 2010
    Hi Matt, Is there any way to change the border around the control from 3px to 1 without recompiling (with say, css or override)? For some reason I can only use your compiled version.  When I recompile (w/ VS2008) it won't run on our server. Anyway, thanks. Tom