I have created ASP.NET AJAX Extender control. It extends GridView control fixing it's header on the top while adding vertical scroll bar to it's contents.
The control also supports maintaining scroll position inside grid between postbacks.
The control works fine inside or outside of UpdatePanel.
To extend GridView, just drop extender onto the design surface and set its TargetControlID:
Width and height of scrollable area should be specified on GridView control itself:
And here is the result:
Now let's assign width 100% to the grid:
let's resize browser window:
As you see header cells are always above corresponding column.
I will place code for the control explaining main operations it does.
The steps it does are the following:
1) when extender is being initialized in the browser, Table element of the GridView is "shallow cloned" using clone javascript function, the cloning is done with it all attributes including style. The clone is placed before actual GridView in the document tree.
2) Header of original GridView is cloned and placed to the cloned table (see above)
3) Create a div element which will allow us scroll the contents of a table and place it inside new row in the cloned table
4) copy remaining rows from original GridView inside div element, also hide original GridView's header and remove height and width style elements from table element we are copying.
5) call function that iterates over all first row's cells in the contents table and sets corresponding widths to the header cells.
6) subscribe to "resize" event of window object, and in the handler do the same widths check for header
7) subscribe to "scroll" event of div element, every time div is scrolled store scroll position in hidden field registered by extender
8) if we already have scroll position stored - just set it to the div position (this step is important after postback - when we need to restore scroll position.
Ok here is the code for GridViewFixedHeaderExtender.cs file:
using System; using System.Collections.Generic; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.Web; using System.Security.Permissions; namespace Devarchive.Net { [ AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), Designer("Devarchive.Net.SimpleDesigner, Devarchive.Net"), ToolboxData("<{0}:GridViewFixedHeaderExtender runat=server></{0}:GridViewFixedHeaderExtender>"), TargetControlType(typeof(GridView)) ] public class GridViewFixedHeaderExtender : ExtenderControl { #region Overrides protected override void OnLoad(EventArgs e) { base.OnLoad(e); } protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl) { if (TargetControl == null || !TargetControl.Visible || TargetControl.Rows.Count == 0) { TargetControl.Height = Unit.Empty; yield break; } ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("Devarchive.Net.GridViewFixedHeaderExtender", targetControl.ClientID); descriptor.AddProperty("scrollField", HiddenFieldID); yield return descriptor; } protected override IEnumerable<ScriptReference> GetScriptReferences() { if (TargetControl == null || !TargetControl.Visible || TargetControl.Rows.Count == 0) { TargetControl.Height = Unit.Empty; yield break; } yield return new ScriptReference("Devarchive.Net.GridViewFixedHeaderExtender.js", this.GetType().Assembly.FullName); } protected override void Render(HtmlTextWriter writer) { ScriptManager.RegisterHiddenField( this, HiddenFieldID, LastScroll.ToString() ); base.Render(writer); } private GridView TargetControl { get { GridView result = this.NamingContainer.FindControl(TargetControlID) as GridView; return result; } } private int LastScroll { get { int result = 0; if (Page.Request[HiddenFieldID] != null) { int.TryParse(Page.Request[HiddenFieldID], out result); } return result; } } private string HiddenFieldID { get { return String.Format("{0}_GVFHE_Scroll", ClientID); } } #endregion } }
And here is js file listing:
-----------------------------
/// <reference name="MicrosoftAjax.debug.js" /> /// <reference name="MicrosoftAjaxTimer.debug.js" /> /// <reference name="MicrosoftAjaxWebForms.debug.js" /> Type.registerNamespace("Devarchive.Net"); Devarchive.Net.GridViewFixedHeaderExtender = function(element) { Devarchive.Net.GridViewFixedHeaderExtender.initializeBase(this, [element]); this._documentResizeDelegate = null; this._lock = false; this._mainTableID = null; this._innerTableID = null; this._divChild = null; this._scrollField = 0; } Devarchive.Net.GridViewFixedHeaderExtender.prototype = { // Overrides //#region initialize: function() { Devarchive.Net.GridViewFixedHeaderExtender.callBaseMethod(this, 'initialize'); this.initGrid(); }, dispose: function() { //Add custom dispose actions here $removeHandler(window, "resize", this._documentResizeDelegate); if(this._divChild) { $clearHandlers(this._divChild); } Devarchive.Net.GridViewFixedHeaderExtender.callBaseMethod(this, 'dispose'); }, //#endregion // Properties //#region get_scrollField : function() { return this._scrollField; }, set_scrollField : function(value) { if (this._scrollField !== value) { this._scrollField = value; this.raisePropertyChanged('scrollField'); } }, //#endregion // Methods //#region getLastScroll : function() { var result = 0; var hf = $get(this._scrollField); if(hf) { result = parseInt(hf.value); if(!result) result = 0; } return result; }, setLastScroll : function(value) { var hf = $get(this._scrollField); if(hf) { hf.value = value; } }, initGrid : function() { // create deep clone of target grid var target = this.get_element(); var clone = target.cloneNode(true); // get desired height of inner scrollable area var height = target.style.height; var width = target.style.width; var mainTable = target.cloneNode(false); mainTable.id = String.format("outer_{0}", target.id); target.parentNode.insertBefore(mainTable, target); var mainHead = document.createElement("thead"); mainTable.appendChild(mainHead); var mainBody = document.createElement("tbody"); mainTable.appendChild(mainBody); // Clone original header var header = target.rows[0].cloneNode(true); mainHead.appendChild(header); // add scrollable area mainTable var secondRow = document.createElement("tr"); mainBody.appendChild(secondRow); var mainTd = document.createElement("td"); secondRow.appendChild(mainTd) this.setAttribute(mainTd, "colspan", target.rows[0].cells.length); this.setAttribute(mainTd, "align", "left"); this.setAttribute(mainTd, "valign", "top"); var divChild = document.createElement("div"); mainTd.appendChild(divChild); divChild.style.width = width; divChild.style.height = height; $addHandler(divChild, "scroll", Function.createDelegate(this, this.syncScroll)); divChild.style.overflow = "auto"; divChild.style.overflowX = "hidden"; divChild.style.overflowY = "scroll"; this._divChild = divChild; // now remove old grid from document and insert new clone into the place target.parentNode.removeChild(target); divChild.appendChild(clone); // assign extender related data to clone clone._behaviors = target._behaviors; clone.GridViewFixedHeaderExtender = target.GridViewFixedHeaderExtender; // correct styles var attributes = []; for(var i = 0; i < clone.attributes.length; i++) { var attr = clone.attributes.item(i); var value = attr.value.trim().toLowerCase(); if(value != "cellpadding" && value != "cellspacing") { Array.add(attributes, attr); } } Array.forEach(attributes, this.deleteAttribute, clone); clone.deleteRow(clone.rows[0]); clone.border = "0"; clone.style.borderWidth = "0px"; clone.style.width = "100%"; clone.style.height = ""; mainTable.style.height = ""; target.style.height = ""; // correct widths of header columns and subscribe to document resize event: this._mainTableID = mainTable.id; this._innerTableID = clone.id; this._documentResizeDelegate = Function.createDelegate( this, this.syncWidths ); this._documentResizeDelegate.call(); // Attach to window's resize event to resize header cells when inner cells change their size $addHandler(window, "resize", this._documentResizeDelegate); // Restore scroll position from last time divChild.scrollTop = this.getLastScroll(); }, setAttribute : function(element, attribute, value) { var namedItem = document.createAttribute(attribute); namedItem.value = value; element.attributes.setNamedItem(namedItem); }, deleteAttribute : function(attribute, index, attributes) { this.removeAttribute(attribute); }, syncScroll : function(args) { if(this._divChild) { this.setLastScroll(this._divChild.scrollTop); } }, syncWidths : function(args) { if(!this._lock) { this._lock = true; var mainTable = $get(this._mainTableID); var innerCellPadding = mainTable.cellPadding; var header = mainTable.rows[0]; var innerTable = $get(this._innerTableID); var originalRow = innerTable.rows[0]; var headerWidth = Sys.UI.DomElement.getBounds(header).width; var originalRowWidth = Sys.UI.DomElement.getBounds(originalRow).width; var diff = headerWidth - originalRowWidth - innerCellPadding * 2; if (originalRow && header) { for(var i = 0; i < originalRow.cells.length; i++) { var bounds = Sys.UI.DomElement.getBounds(originalRow.cells[i]); var x = bounds.width; if(i == originalRow.cells.length-1) { x = x + diff - innerCellPadding * 2; } else { x = x - innerCellPadding; } header.cells[i].style.width = x + "px"; } } this._lock = false; } } //#endregion } Devarchive.Net.GridViewFixedHeaderExtender.registerClass('Devarchive.Net.GridViewFixedHeaderExtender', Sys.UI.Behavior);
You can download source code for the Devarchive.Net toolkit including this control here.
To the moment toolkit contains four controls:
StateBag control
HoverTooltip control
Timer control
GridViewFixedHeaderExtender control
StateBag control in the toolkit is little modified to support scenarios when you might need to use it inside UpdatePanel control.
Hope this helps.

14 comments:
Doesn't work for me :(
I get the following error:
"Microsoft JScript runtime error: 'cells[...].style' is null or not an object"
My scenario:
I have a grid with dynamically created columns.
ASP.NET 2.0
regards.
Please try to use the controls from this link: http://devarchive.net/downloads/Devarchive.Net_AjaxRepeaterSample.zip
this is latest code, As I remembered I improved some little points there.
Also if this does not help, please send me the code with issue to Administrator @ devarchive.net
I will look at it for sure.(please send only small part that makes an error)
Thanks
Actually, it appears to work at first - but my cell contents slide over to the right and arenot aligned perfectly under my headers - if I set a hard width for each of the cells, it works better for some o fthe cols/cells seem to "bleed" over into the adjacent cells/cols, etc.
The only way that I can get this to "work" is by setting my cols/cells with finite widths - also, the cell border disappears
Headers are not displayed if AutoGenerateColumns is set to "false". I have a grid with dynamically created columns.
Regards
It work but the width of header and grid not syn. Please check synwidth code. And it don't have footer
thank you very very much
sadettin çetin
türkiye
template field in error
sadettin çetin
türkiye
I want to use paging with this. How would you make the pager fixed as well?
Doing this with CSS seems to be a better deal all alltogether...
http://mattberseth.com/blog/2007/09/freezing_gridview_column_heade_1.html
Not sure if Matt's solution works for all browsers,
I looked at this solution first but gave up after a while
Kirill
ok, i am a little slow here. how do i get the extender available in visual studio so that i can "drag it onto the design surface"?
Just drop the dll from the link Kirill posted to your Bin in your project.
I don't download the source code
please send to me,thank you,
emain:zb_zbzb#163.com
Post a Comment