Creating a GridView with Resizable Column Headers

I was recently reading Dan Wahlin's excellent 'How To' for creating a GridView extender control.  While I was going through his code samples, I thought it might be interesting to create another GridView extender that allows you to resize the column widths of the GridView by clicking and dragging the header cell borders.  Before I went down the path of actually implementing the extender control, I wanted to get a feel for what the client side code for handling this might look like.  So I quick created a sample application where I added this functionality to the Google Analytics GridView I blogged about in an earlier post.

** Because this sample is a proof of concept, I only tested in IE7.  If I move the code to an extender control I will go back and test it with other browsers. 

Live Demo | Download

Before I get into the implementation details, here is a quick screen shot of the grid.  Unfortunately the mouse cursor didn't come across in the screen shot, but when it is placed over the cell borders in the header row, it displays the east/west pointing arrow.

Adding this behavior to the existing GridView required implementing a handful of JavaScript functions.  My approach for implementing this features was to do the following:

  1. Handle each of the header cells's mousemove events to determine when the user has the cursor placed roughly over the cell's right hand border.  If a resize is already in processes then I use this event to determine what the new width of the column should be
  2. Handle each of the header cell's mousedown events to determine when the resizing begins
  3. Handle the document's mouseup event.  When this occurs I make sure that the resize has stopped
  4. Handle the document's selectstart event.  I cancel this event if a resize is currently in executing.  Doing this make's sure that the header cell's text isn't highlighted when I am resizing the cell.  I learned this trick from the AjaxControlToolkit's ResizeableControlExtender.  Here is a screen shot of what this would look like if you don't cancel this event:

 

 

 

 

pageLoad Event Handler

First, I added the pageLoad JavaScript function to my page.  The ASP.NET AJAX framework will call this method for you so you will not have to worry about wiring it up.  I use this method to add event handlers to the mousemove and mousedown events for the table's header cells.  Additionally I add handlers for the document level mouseup and selectstart events as follows ...

//  true when a header is currently being resized
var _isResizing;
//  a reference to the header column that is being resized
var _element;
//  an array of all of the tables header cells
var _ths;

function pageLoad(args){
    //  get all of the th elements from the gridview
    _ths = $get('gvCustomers').getElementsByTagName('TH');
    
    //  if the grid has at least one th element
    if(_ths.length > 1){
    
        for(i = 0; i < _ths.length; i++){
            //  determine the widths
            _ths[i].style.width = Sys.UI.DomElement.getBounds(_ths[i]).width + 'px';
        
            //  attach the mousemove and mousedown events
            if(i < _ths.length - 1){
                $addHandler(_ths[i], 'mousemove', _onMouseMove);
                $addHandler(_ths[i], 'mousedown', _onMouseDown);
            }
        }

        //  add a global mouseup handler            
        $addHandler(document, 'mouseup', _onMouseUp);
        //  add a global selectstart handler
        $addHandler(document, 'selectstart', _onSelectStart);
    }       
}

TH's mousemove Event

Next, I add logic to the mousemove event handler.  This event will fire everytime the mouse position changes while the cursor is over any of the header cells.  The code in this handler first checks to see if we are currently resizing a cell.  If we are, we do a little math to figure out what the new width of the column being resized needs to be.  If a resize is not currently taking place, then I check to see how close the mouse cursor is to the cells right hand border.  If it is within 2 pixels I display the east/west cursor so the user knows they can resize the cell.  Here is the JavaScript code for this handler ...

function _onMouseMove(args){    
    if(_isResizing){
        
        //  determine the new width of the header
        var bounds = Sys.UI.DomElement.getBounds(_element); 
        var width = args.clientX - bounds.x;
        
        //  we set the minimum width to 1 px, so make
        //  sure it is at least this before bothering to
        //  calculate the new width
        if(width > 1){
        
            //  get the next th element so we can adjust its size as well
            var nextColumn = _element.nextSibling;
            var nextColumnWidth;
            if(width < _toNumber(_element.style.width)){
                //  make the next column bigger
                nextColumnWidth = _toNumber(nextColumn.style.width) + _toNumber(_element.style.width) - width;
            }
            else if(width > _toNumber(_element.style.width)){
                //  make the next column smaller
                nextColumnWidth = _toNumber(nextColumn.style.width) - (width - _toNumber(_element.style.width));
            }   
            
            //  we also don't want to shrink this width to less than one pixel,
            //  so make sure of this before resizing ...
            if(nextColumnWidth > 1){
                _element.style.width = width + 'px';
                nextColumn.style.width = nextColumnWidth + 'px';
            }
        }
    }   
    else{
        //  get the bounds of the element.  If the mouse cursor is within
        //  2px of the border, display the e-cursor -> cursor:e-resize
        var bounds = Sys.UI.DomElement.getBounds(args.target);
        if(Math.abs((bounds.x + bounds.width) - (args.clientX)) <= 2) {
            args.target.style.cursor = 'e-resize';
        }  
        else{
            args.target.style.cursor = '';
        }          
    }         
}

TH's mousedown Event

The mousedown event handler if very simple.  If checks if the cursor is style is 'e-resize'.  If so it sets the _isResizing bit to true so the other handlers know that a resize is currently taking place.  It also grabs the header cell that is being resized.  Here is the code ...

function _onMouseDown(args){
    //  if the user clicks the mouse button while
    //  the cursor is in the resize position, it means
    //  they want to start resizing.  Set _isResizing to true
    //  and grab the th element that is being resized
    if(args.target.style.cursor == 'e-resize') {
        _isResizing = true;
        _element = args.target;               
    }                    
} 

document's mouseup Event

Next, I added code for the mouseup event.  This is a handler is setup at the document level so no matter where the user lets go of the button this logic will be executed.  This handler does 2 things: resets the _isResizing and _element values back to there unitialized state and resets the header cells cursor style back to its initial state.  Here is the code for this ...

function _onMouseUp(args){
    //  the user let go of the mouse - so
    //  they are done resizing the header.  Reset
    //  everything back
    if(_isResizing){
        
        //  set back to default values
        _isResizing = false;
        _element = null;
        
        //  make sure the cursor is set back to default
        for(i = 0; i < _ths.length; i++){   
            _ths[i].style.cursor = '';
        }
    }
} 

document's selectstart Event

Finally, the last handler.  Like I mentioned earlier, I added this one to prevent the header cell's text from highlighting as I am resizing the cells.  All this function does is cancel's the event if a resize is currently executing.  Here is the code for this ...

function _onSelectStart(args){
    // Don't allow selection during drag
    if(_isResizing){
        args.preventDefault();
        return false;
    }
}    

That's it. Enjoy!


TrackBack

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

Listed below are links to weblogs that reference Creating a GridView with Resizable Column Headers:

» Creating a GridView with Resizable from Blog of Developer Mikkel Ovesen
Creating a GridView with Resizable Column Headers [Read More]

» Thursday Linkorama from Ross Hawkins
Thursday Linkorama [Read More]

Comments


Posted by: sarvesh on August 31, 2007 12:00 AM

I am using gridview control and i want to resize the column header of gridview. Please suggest me , where should i call pageLoad(args) frunction in my code. My site is not ajax enabled. pleae reply me

Awesome! It would be really cool if when you double-clicked on the dividers it automatically sized it perfectly, as Excel does. Great post, thanks for sharing!

Posted by: Lori on August 31, 2007 12:00 AM

Nice example. You could add an array to keep the column widths that gets filled in the first time through the pageLoad function and is updated by the mousemove function. Those values would then be used to reset the columns sizes in a subsequent page load to maintain the widths of the columns between pages.

Posted by: Lori on August 31, 2007 12:00 AM

Josh, if you are using IE, you could add the dblclick event processing along with the mousemove and mousedown on each TH to calculate the width necessary for autosizing the column by creating a text range, moving it to each cell in that column, getting the boundingWidth and getting the maximum width for the column entries and then setting the width with that value.

Another great post. Thank you.

Cheers,http://www.bloggingdeveloper.com/

Posted by: Andy on September 10, 2007 12:00 AM

hi,
_ths = $get(GVIBS).getElementsByTagName(TH);
if i put alert above this line it works but if i put alert after belowe this line it doesnt work.So i am not able to resize column in my gridview.So what should I do now?
Thanx

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

In Firefox, resizing will still select text of another column if your mouse is over its text.

Posted by: Q on September 17, 2007 12:00 AM

Mike,
FireFox doesnt implement onselectstart, for FireFox and other browsers you can use CSS browser directives like:

-moz-user-select: none;
-khtml-user-select: none;

Hope this helps.

-Q

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

For some instances, it may be a good practice to set the CellPadding property of the gridview to some values between 15 and 20 in order to avoid the overlapping of the HeaderText and the sorting arrow gifs.

Posted by: Akaka on September 18, 2007 12:00 AM

Thanks for your good stuff, I have you live demo and try to resize columns but when I click column to sort data, it does not persist the column size

Posted by: Mathew Uthup on September 23, 2007 12:00 AM

Thanks for this great article. It gave me the simple solution to implement keyboard navigation on the grid view.
Add a key board handler and a mouse click handler as follows;
var _trs;
var curSelRow;
var currow=0;
function _onKeyDown(ev){
//
var k = ev.keyCode ? ev.keyCode : ev.rawEvent.keyCode;
if (k === Sys.UI.Key.down){
var nextrow=currow +1;
if (nextrow >(_trs.length-1) ){
nextrow=_trs.length-1;
}
var trnext=_trs[nextrow];
trnext.focus();
_selrow(trnext);
}
if(k === Sys.UI.Key.up){
var nextrow=currow -1;
if (nextrow
nextrow=1
}

var trnext=_trs[nextrow];
trnext.focus();
_selrow(trnext);
}
}

Hook the Mouse keyboard event as shown below in the page script
function pageLoad(args){
// get all of the tr elements from the gridview

_trs=$get(GridView1).getElementsByTagName(tr);
if(_trs.length > 1){
for(i = 1; i
// attach Keydown Keyup
if(i
$addHandler(_trs[i], keydown, _onKeyDown);
}
}
}
}
Hook the Mouse click in Row created event as shown below in the code behind of the Aspx page
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{

e.Row.Attributes.Add("onclick", "_selrow(this)");
}
}

I can submit the working sample that I modified using your Column downloaded sample which has the implemented feature.

Posted by: Alisson Santana on October 17, 2007 12:00 AM

Great article!
I just added some treat to the code, cause my grid is only rendered after a post back.

try { _ths = $get(grvBusca).getElementsByTagName(TH); } catch (e) { return; }

Posted by: Nitin on November 6, 2007 12:00 AM

hi matt
your code works great

but i have an issue.my table is something as below


[table cellspacing="0" rules="all" border="1" id="gvCustomers" style="border-collapse:collapse;"]
[tr]
[th align="left" colspan="10"][table cellpadding="0" cellspacing="0" border="0" width="100%"]
[tr]
[td width="50%" align="left" class=""][span id="gvCustomers_ctl01_lblRecordSummary"]You have currently 0 item(s) defined.[/span][/td]
[td align="right" class=""][a id="gvCustomers_ctl01_btnExportExcel" href="javascript:__doPostBack(gvCustomers$ctl01$btnExportExcel,)"]ExportToExcel[/a][/td]
[/tr]
[/table]
[/th]
[/tr][tr]

[/tr][tr]
[th scope="col"]rollno[/th][th scope="col"]fname[/th][th scope="col"]lname[/th][th scope="col"]sex[/th][th scope="col"]class[/th][th scope="col"]subject1[/th][th scope="col"]subject2[/th][th scope="col"]subject3[/th][th scope="col"]phno[/th][th scope="col"]email[/th]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]0[/td][td] [/td][td] [/td][td] [/td][td] [/td][td] [/td][td] [/td][td] [/td][td] [/td][td] [/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]1[/td][td]nitin[/td][td]bajaj[/td][td]male[/td][td]10[/td][td]Physics[/td][td]Chemistry[/td][td]Maths[/td][td]1234[/td][td]a@b.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]2[/td][td]deepesh[/td][td]jain[/td][td]female[/td][td]11[/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]246457[/td][td]c@d.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]3[/td][td]ajay[/td][td]singh[/td][td]male[/td][td]12[/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]515474[/td][td]c@d.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]4[/td][td]sulabh[/td][td]gupta[/td][td]male[/td][td] [/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]246457[/td][td]a@b.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]5[/td][td]abhinav[/td][td]mehra[/td][td]male[/td][td]10[/td][td] [/td][td]Chemistry[/td][td]Maths[/td][td]543125[/td][td]a@b.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]6[/td][td]nihit[/td][td]bajaj[/td][td]male[/td][td]12[/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]4573757[/td][td]c@d.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]8[/td][td]parul[/td][td]mahajan[/td][td]female[/td][td]11[/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]3577[/td][td]a@b.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]9[/td][td]anshul[/td][td]mittal[/td][td]male[/td][td]11[/td][td]Physics[/td][td]Chemistry[/td][td] [/td][td]457437[/td][td]c@d.com[/td]
[/tr][tr onmouseover="this.style.backgroundColor = ;" onmouseout="this.style.backgroundColor = ;"]
[td]10[/td][td]nitin[/td][td]singh[/td][td]male[/td][td]10[/td][td]Physics[/td][td] [/td][td]Maths[/td][td]52436[/td][td]c@d.com[/td]
[/tr]
[/table]

i want to resize the columns of the base tablewhile not altering the nested table.hw do i go about doing it?
thanx

I made a Control Extender from this code. You can download the code here if you want:

http://blog.lavablast.com/post/ASPNET-GridView-column-resizing.aspx

Posted by: Anil on November 12, 2007 12:00 AM

Thanks Matt the code is working but the problem is the column is not being compressed its only extending in the grid view control can u help me

Posted by: Azhar on November 12, 2007 12:00 AM

Hi Matt,

Its very good.

will it work without ajax ?

If yes where should i call pageload method.

Thanks very much

Excellent, this is extremely useful and interesting. I have always used CSS to make the columns fixed in size to stop the jumping around effect; but this blends a deeper more usable interface for the user.

Posted by: Anil Kishore on January 10, 2008 12:00 AM

First Thanks for the grate code.
I would like to know how to create an extender control with resizable columns with the above code example. Thanks in advance for your response.

Posted by: parth on June 11, 2008 12:00 AM

Hi
indeed this is great but i am using Ajaxtoolkit in my project and if i am placing the after ajaxtoolkits script manager it is giving me error
if i put asp:scriptmanager then in name space ambiguity is occuring does any one has any idea about this , i really need this.

Thanks and Regards

Parth

Very useful code it was the solution I looking for. thanks

Posted by: yww on July 2, 2008 12:00 AM

this is really a simple solution for most cases.
but the demo page is not working. maybe you should add the pageLoad to body.onload
Thank you for your work.

Posted by: khushi2005 on September 18, 2008 01:05 PM

I am trying to implement it using a Javascript file
so I can call this function for all the GridViews on different pages
How do I accomplsh this

Page.RegisterClientScriptBlock("MyScript", @"");
Page.ClientScript.RegisterStartupScript(Type.GetType("System.String"), "addScript", "MyPageLoad('ctl00_ContentPlaceHolder1_GridView2');", true);


Posted by: khushi2005 on September 18, 2008 01:09 PM

How to maintain widht between subsequent page load

Posted by: halli on October 23, 2008 07:05 PM

Hi, very helpful post. I noticed one small problem: resizing doesn't work if the browser window scrollbars are in non-zero position. I think you need to consider the scrollbar position, as in:

var width = document.body.scrollLeft + args.clientX - bounds.x;

Posted by: Mohan M Devarinti on February 16, 2009 06:49 AM

Hi Matt,

It's very good.
it is helped me very lot..

I need one help..
i need to provide column and header freezing along with resizing...
Can any body help me on this?


Thanks and Regards
Murali.MD

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

  • Mohan M Devarinti wrote: Hi Matt, It's very good. it is helped me very lot.. I need one help.. i need to provide column and...
  • halli wrote: Hi, very helpful post. I noticed one small problem: resizing doesn't work if the browser window scro...
  • khushi2005 wrote: How to maintain widht between subsequent page load...
  • khushi2005 wrote: I am trying to implement it using a Javascript file so I can call this function for all the GridVie...
  • yww wrote: this is really a simple solution for most cases. but the demo page is not working. maybe you should ...
  • Bolsa de Trabajo wrote: Very useful code it was the solution I looking for. thanks ...
  • parth wrote: Hi indeed this is great but i am using Ajaxtoolkit in my project and if i am placing the after ajaxt...
  • Anil Kishore wrote: First Thanks for the grate code. I would like to know how to create an extender control with resizab...