The Datagrid is a lovely control when you need a web page whose
layout has to adapt to a a huge variety of browsers. Just set the width
of the grid to 100% and the grid's layout will scale quite well. When
it comes to the page (response) size the datagrid is not that nice. The
grid's viewstate will swallow a lot of bandwidth. In my case the size
of a page with a grid went from 50k to 150k by just setting the grid's EnableviewSate
property to true (which is the default). Most of the information in
that viewstate is not needed at all. The data in the grid is dynamic,
it is reread from a database on every roundtrip.
By switching off the grid's viewstate you do lose a little more than intended. A lot of events no longer fire. OnBubbleEvent (which fires when a control in a template has posted back), OnItemCommand (which fires when a linkbutton in the grid has posted back) and the specific command events like SelectCommand, SortCommand and the events dependending on these, like SelectedIndexChanged, no longer fire (on my .net server, some people report different). Using a little hack you can alwyas find out what kind of event posted back.
Some properties in the viewstate are hard to live without. To select an individual row you do need SelectedIndex.
In ASP.NET 2.0 the essential properties have moved from the viewstate
to the controlstate, in 1.x you have to do your own bookkeeping. The
SelectedIndex cannot be preserved over roundtrips in the grids's
viewstate but it can be stored in the viewstate of the page which owns
the grid.
Storing and retreiving something in the viewsate is pretty easy:
const string myViewStateItemName = "_myViewStateItemName";
protected int myViewStateItem
{
get
{
object o = this.ViewState[myViewStateItemName];
if (o == null)
return -1;
else
return (int) o;
}
set
{
this.ViewState[myViewStateItemName] = value;
}
}
The viewstate is a name- value collection, the items are identified
by a string. To make shure I use the same name when setting and getting
the item that name is a constant. All values are stored as an object,
it takes some casting to get the right type and some default value when
the items wasn’t found (yet). Now the page has an integer property
myViewStateItem.
Page is a property of a web-control, the grid can reach the
page from code. I have created a custom datagrid which inherits
from the DataGrid. A first attempt to store the selectedindex
might look like this:
public class IndatoDataGrid : System.Web.UI.WebControls.DataGrid
{
const string myViewStateItemName = "_myViewStateItemName";
public override int SelectedIndex
{
get
{
object o = this.Page.ViewState[myViewStateItemName];
if (o == null)
return -1;
else
return (int) o;
}
set
{
this.Page.ViewState[myViewStateItemName] = value;
}
}
There are two things wrong with that
- The ViewState property has a proptected visibility and is not available here
- When two of these grids co-exist on one page they will both store their property in the same viewstate member
This is where the page base class comes in. You do not need a visual
page, just add a class to your project which inherits from
System.Web.UI.Page.
public class BasePage : System.Web.UI.Page
{
public int GetViewStateInt(string name)
{
object o = this.ViewState[name];
if (o == null)
return -1;
else
return (int) o;
}
public void SetViewStateInt(string name, int value)
{
this.ViewState[name] = value;
}
}
The class exposes two methods which store and reterieve an integer
value to and from the page’s viewstate. Any webform can inherit from
this class.
///
/// Summary description for StudieOnderdeel.
///
// <Update> this code has been slightly updated since the original </update>
public class StudieOnderdeel : BaseWebForm
{
protected System.Web.UI.WebControls.Panel Panel1;
// Etcetera
Generating an unique name for the value is done in the code of the grid
public class IndatoDataGrid : System.Web.UI.WebControls.DataGrid
{
private int GetInt(string name)
{
BasePage bp = this.Page as BasePage;
if (bp != null)
return bp.GetViewStateInt(VSname(name));
else
return -1;
}
private void SetInt(string name, int value)
{
BasePage bp = this.Page as BasePage;
if (bp != null)
bp.SetViewStateInt(VSname(name), value);
}
private string VSname(string varName)
{
return string.Format("{0}{1}", this.ID, varName);
}
const string myViewStateItemName = "_mySelectedIndexame";
public override int SelectedIndex
{
get
{
return GetInt(VSname(myViewStateItemName));
}
set
{
SetInt(VSname(myViewStateItemName), value);
}
}
The grid has two methods, GetInt and SetInt. These cast the grid’s Page to a BasePage
and use the BasePage’s methods to get the value in and out of the
viewstate. The name of the viewstate member is composed by the VSname
method, which combines the ID of the grid itself with the name passed.
The combination will be unique on the page. The overridden
SelectedIndex property uses these to preserve the selectedindex over
roundtrips.
Now I have a datagrid which does preserve its SelectedIndex over
roundtrips without a dramatic increase of viewstate size.