Introduction
The TreeGridDemo demonstrates a custom method for DataGrid paging. The DataGrid behaves much like a tree control, which collapses or expands sections of the grid rows. This way of paging is useful when the grid contains too many records and you have to render all the records in on page because ,for example, you need to display a summary underneath the grid like total or average or etc. By this way the user will be able to either see the summary or even open all or part of the sections of the grid.
Using the code
I use both server sdie and client side script to do this functionality.The client side script is responsible for showing and hiding a number of grid rows according to the number of rows per section. Also, the script sets the current state of the section in hidden fields for server side state presesrvation. This is important beacause when the grid is bound to its datasource it loses its client state as a result of being rerendered. This is the key method of the client script:
<P>
function ExpandCollapse(sender,gridClientID,rowIndex,numItemsPerPage)
{
var grid=document.getElementById(gridClientID);
for(i=rowIndex+1;i<(rowIndex+numItemsPerPage+1);i++)
{
try
{
CURRENTSTATEgrid.rows[i].style.display=sender.value=='-'?'none':'block';
hiddenShowHideStatus=grid.rows[i].cells[0].children[0];
if(hiddenShowHideStatus&&hiddenShowHideStatus.type=='hidden')
{
hiddenShowHideStatus.value=sender.value=='-'?'none':'block';
}
}
catch(ex)
{
break;
}
}
sender.value=(sender.value=='-')?'+':'-';
}
<P></P>
In the server side I render separator rows in the PreRender event of the DataGrid. The separator rows contains two cells. One cell for rendering the button control that fires client side event for collapsing or expanding the grid sections, and the other cell is for section numbering ;"From x to y". The key method on the server side is below:
<P>
private void treeGridToManage_PreRender(object sender, EventArgs e)
{
PopulateGridSeparators((DataGrid)sender);
}
private void PopulateGridSeparators(DataGrid treeGridToManage)
{
int pagerCellColSpan=0;
int numItemsPPage=int.Parse(treeGridToManage.Attributes["NumItemsPPage"]);
if(treeGridToManage.Items.Count>0)
{
pagerCellColSpan=treeGridToManage.Items[0].Cells.Count;
int numSeparatorsCreated=0;
for(int index=1;index<treeGridToManage.Items.Count+numSeparatorsCreated;index+=(numItemsPPage+1),numSeparatorsCreated++)
{
string clientText="";
clientText+="<INPUT TYPE =BUTTON ID=BTN"+index+" ";
clientText+="VALUE=\"-\" ";
clientText+="ONCLICK=\"javascript:ExpandCollapse(this,'"+treeGridToManage.ClientID+"',"+index+","+numItemsPPage+")\" ";
clientText+="STYLE=\"BORDER-RIGHT: gray thin solid; BORDER-TOP: gray thin solid; BORDER-LEFT: gray thin solid; WIDTH: 25px; BORDER-BOTTOM: gray thin solid; HEIGHT: 28px\"";
clientText+=">";
TableCell cell=new TableCell();
cell.Text=clientText;
cell.Width=30;
cell.BorderStyle=BorderStyle.Dotted;
TableCell cell2=new TableCell();
cell2.ColumnSpan=pagerCellColSpan;
cell2.HorizontalAlign=HorizontalAlign.Center;
cell2.Text="From "+(index-numSeparatorsCreated)+" To "+(((index-numSeparatorsCreated)+numItemsPPage-1));
cell2.BorderStyle=BorderStyle.Dotted;
DataGridItem item=new DataGridItem(0,0,ListItemType.Separator);
item.BackColor=Color.LightGray;
item.Cells.Add(cell);
item.Cells.Add(cell2);
treeGridToManage.Controls[0].Controls.AddAt(index,item);
}
}
}
</P>
I have spoken about state preservation between the client and the server. I do this by holding hidden fields within the grid, these hidden fields holds the last state of every section on the client (wherther it is Collapsed or Expanded). I read the values of these hidden fields in an ArrayList before binding to the grid, after binding I set the formerly read values againg in the newly rendered hidden fields. When the page is loaded on the client side I read the values of the hidden fields and set the state of the different sections of the grid according to hidden fields. In brief I do three action for state preservation:
- Dynamically add a template column to the datagrid containing the hiden fields
class DataGridTemplate : System.Web.UI.ITemplate
{
ListItemType templateType;
string columnName;
public DataGridTemplate(ListItemType type, string colname)
{
templateType = type;
columnName = colname;
}
public void InstantiateIn(System.Web.UI.Control container)
{
HtmlInputHidden hiddenShowHideStatus=new HtmlInputHidden();
hiddenShowHideStatus.Value="none";
hiddenShowHideStatus.ID="hiddenShowHideStatus";
switch(templateType)
{
case ListItemType.Item:
case ListItemType.EditItem:
container.Controls.Add(hiddenShowHideStatus);
break;
}
}
}
- Read the hidden fields before calling grid.DataBind() and set the again in the newly rendered fields
public void ManageGridBinding()
{
treeGridToManage.PreRender+=new EventHandler(treeGridToManage_PreRender);
ArrayList stateList=new ArrayList();
for(int i=0;i<treeGridToManage.Items.Count;i++)
{
HtmlInputHidden hiddenTreeGridState=(HtmlInputHidden)treeGridToManage.Items[i].Cells[0].FindControl("hiddenShowHideStatus");
if(hiddenTreeGridState!=null)
{
stateList.Add(hiddenTreeGridState.Value);
}
}
treeGridToManage.DataBind();
if(stateList.Count>0)
{
for(int i=0;i<stateList.Count;i++)
{
HtmlInputHidden hiddenTreeGridState=(HtmlInputHidden)treeGridToManage.Items[i].Cells[0].FindControl("hiddenShowHideStatus");
if(hiddenTreeGridState!=null)
{
hiddenTreeGridState.Value=stateList[i].ToString();
}
}
}
}
- Create a client side function executed on page load to set the current state of the grid
this.onload=function()
{
grids=new Array('DataGrid1','DataGrid2');
for(j=0;j<grids.length;j++)
{
var grid=document.getElementById(grids[j]);
if(grid)
{
for(i=0;i<grid.rows.length;i++)
{
hiddenShowHideStatus=grid.rows[i].cells[0].children[0];
if(hiddenShowHideStatus)
{
if(hiddenShowHideStatus.type=='hidden')
{
grid.rows[i].style.display=hiddenShowHideStatus.value=='none'?'none':'block';
}
}
}
SetButtonState(grid);
}
}
};
Points of Interest
You donot have to worry about all of that code, all you have to do to get a grid behaving this way is just include TreeGridUIManager.cs in your project and this file contains all code required for the following tasks:
- Manages DataGrid Binding
- Handles Pre_Render event of the DataGrid to render separator rows
The DataGridTemplate class is Responsible for
- Dynamically creating template column
- Adding hidden fields to that column
The TreeGridScriptRenderer class is responsible for creating dynamic client side script
To get one or more DataGrids behaving this way on your page do the following:
- Include TreeGridUIManager.cs in your project
- when biding donot call dataGrid.DataBind but do the following
sqlDataAdapter1.Fill(dataSet11);
sqlDataAdapter2.Fill(dataSet11);
TreeGridUIManager _TreeGridUIManager=new TreeGridUIManager();
_TreeGridUIManager.AddGridToManage(DataGrid1,5);
_TreeGridUIManager.AddGridToManage(DataGrid2,7);
_TreeGridUIManager.DataBind();
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.