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.
Doesn't work for me :(
ReplyDeleteI 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
ReplyDeletethis 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.
ReplyDeleteThe only way that I can get this to "work" is by setting my cols/cells with finite widths - also, the cell border disappears
ReplyDeleteHeaders are not displayed if AutoGenerateColumns is set to "false". I have a grid with dynamically created columns.
ReplyDeleteRegards
It work but the width of header and grid not syn. Please check synwidth code. And it don't have footer
ReplyDeletethank you very very much
ReplyDeletesadettin çetin
türkiye
template field in error
ReplyDeletesadettin çetin
türkiye
I want to use paging with this. How would you make the pager fixed as well?
ReplyDeleteDoing this with CSS seems to be a better deal all alltogether...
ReplyDeletehttp://mattberseth.com/blog/2007/09/freezing_gridview_column_heade_1.html
Not sure if Matt's solution works for all browsers,
ReplyDeleteI 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"?
ReplyDeleteJust drop the dll from the link Kirill posted to your Bin in your project.
ReplyDeleteI don't download the source code
ReplyDeleteplease send to me,thank you,
emain:zb_zbzb#163.com
And horizontal scrollbar ?
ReplyDeleteAre you going to implement it?
I don't think I have a time to implement horizontal scrollbar, sorry.
ReplyDeleteI think there is a lot of work to do to achieve that.
Best Regards,
Kirill
Can we have 1st column fixed during horizontal scroll bar.
ReplyDeleteI need a Microsoft excel like functionality where we can fixed a column and other columns can scroll horizontally but the 1st or first two columns would be fixed.
can you plz help me in this regard.
Komail Noori
www.KomailNoori.com
Hi,
ReplyDeletei am getting problem while runtime binding gridview datasource.
its not working while grid is bind after click on search button.
can any one give me solution ?
Hi,
ReplyDeletei got solution. Problem is due to not getting "height" and "width" value of gridview render table in extender control javascript, while page is first time load and not binding grid datasource.
I had set default value of "height" and "width" in extender control javascript file.
i had extended control functionality by adding two client-side property like "GVHeight" and "GVWidth".
refer this link how to achive this
for simple extender control example
http://pietschsoft.com/post/2008/05/ASPNET_35_Create_AJAX_Extender_Controls_using_the_ExtenderControl_base_class.aspx
for more details contact send email to me dhiren4uk@gmail.com
thanks & regards,
dhiren mistry
Hello. And Bye.
ReplyDeleteHi,
ReplyDeleteI have been trying to implement this functionality for the past few days and I had tried a lot of codes but yours worked like a charm...no changes were required...Thanks!!!
Hi,
ReplyDeleteI implemented this gridview extender. It works fine. But there is a problem when editing rows. I have AJAX enabled controls and they don't work...like my calender extendar no longer works. Do you know a fix for this?
I have a question about your extender. I implemented it and it works great, if the grid has data. My GridView displays an EmptyDataTemplate sometimes, and after the EmptyDataTemplate is displayed, the 'locked' header effect no longer works if I rebind and the GridView has data. Do I need to call a function from the extender when I rebind?
ReplyDeleteThanks for sharing this! I should try them in a minute!
ReplyDelete