Click here to Skip to main content
15,885,244 members
Articles / Web Development / ASP.NET
Article

Creating a DataGrid that expands and collapses like a tree control

Rate me:
Please Sign up or sign in to vote.
4.56/5 (10 votes)
10 Apr 2006CPOL3 min read 74.5K   1.4K   43   15
A Custom DataGrid Paging Techniuque

Sample Image - DataGridTree.jpg

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>
//******************************************************************************************************** 
//CALLED EVERY TIME YOU COLLAPSE OR EXPAND THE GRID ITEMS
//********************************************************************************************************</P>
function ExpandCollapse(sender,gridClientID,rowIndex,numItemsPerPage)
{
    var grid=document.getElementById(gridClientID);
    for(i=rowIndex+1;i<(rowIndex+numItemsPerPage+1);i++)
    { 
        try 
        {
             //SHOW OR HIDE THE ROW ACCORDING TO
            CURRENTSTATEgrid.rows[i].style.display=sender.value=='-'?'none':'block';
                
            //SET THE HIDDEN VALUE FOR SERVER SIDE STATE PRSERVATION
            hiddenShowHideStatus=grid.rows[i].cells[0].children[0];
            if(hiddenShowHideStatus&&hiddenShowHideStatus.type=='hidden')
            {
                hiddenShowHideStatus.value=sender.value=='-'?'none':'block';
            }
        }
        catch(ex)
        {
            break; 
        } 
    } 
    //CONVERT THE TEXT OF THE BUTTON CONTROL FROM + TO - ANDVICEVERSA
    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:

  1. 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)
        {
            // The Hidden Field Built Dynamically
            HtmlInputHidden hiddenShowHideStatus=new HtmlInputHidden();
            hiddenShowHideStatus.Value="none";
            hiddenShowHideStatus.ID="hiddenShowHideStatus";
            
            switch(templateType)
            {
                case ListItemType.Item:
                case ListItemType.EditItem:
                        container.Controls.Add(hiddenShowHideStatus);
                        break;
            }
        }
    }
  2. 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();
                }
            }
        }
    
    }
  3. 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:

  1. Manages DataGrid Binding
  2. Handles Pre_Render event of the DataGrid to render separator rows

The DataGridTemplate class is Responsible for

  1. Dynamically creating template column
  2. 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: 

  1. Include TreeGridUIManager.cs in your project
  2. when biding donot call dataGrid.DataBind but do the following

sqlDataAdapter1.Fill(dataSet11);
sqlDataAdapter2.Fill(dataSet11);

//Don't call datagrid.DataBind do the following:

//Create Instance
TreeGridUIManager _TreeGridUIManager=new TreeGridUIManager();

//Define Grids To Behave Like Tree passing section rowcount for every grid
_TreeGridUIManager.AddGridToManage(DataGrid1,5);
_TreeGridUIManager.AddGridToManage(DataGrid2,7);

//Call 
_TreeGridUIManager.DataBind();

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Kuwait Kuwait
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralTemplate column Value Pin
FrancoisB22-Jun-06 2:27
FrancoisB22-Jun-06 2:27 
I am having difficulty in retrieving an updated template column value when "looping" through the grid. A textbox is added to the ItemTemplate of the template column.

This is my code:
foreach(DataGridItem dgi in dbgInfoDetail.Items)
{
string txtAdjustment = ((TextBox)dgi.Cells[0].FindControl("txtAdjustment")).Text;


I tried different cell positions. It does not fail it just doen not return the updated value in the txtAdjustment textbox. The orginal value is retrieved before the update.

Any ideas.

Thanks

Francois
GeneralRe: Template column Value Pin
FrancoisB22-Jun-06 3:20
FrancoisB22-Jun-06 3:20 
GeneralRe: Template column Value Pin
FrancoisB22-Jun-06 3:57
FrancoisB22-Jun-06 3:57 
GeneralRe: Template column Value Pin
Ahmed Abdelgawad23-Jun-06 23:36
Ahmed Abdelgawad23-Jun-06 23:36 
GeneralRe: Template column Value Pin
Ahmed Abdelgawad23-Jun-06 23:37
Ahmed Abdelgawad23-Jun-06 23:37 
QuestionCan this be done on a TreeView control? Pin
Mad M0nk18-Apr-06 22:26
Mad M0nk18-Apr-06 22:26 
AnswerRe: Can this be done on a TreeView control? Pin
Ahmed Abdelgawad18-Apr-06 22:52
Ahmed Abdelgawad18-Apr-06 22:52 
QuestionI am not able to select Datagrid Row Pin
imnexensys18-Apr-06 19:02
imnexensys18-Apr-06 19:02 
AnswerRe: I am not able to select Datagrid Row Pin
Ahmed Abdelgawad18-Apr-06 19:34
Ahmed Abdelgawad18-Apr-06 19:34 
QuestionRe: I am not able to select Datagrid Row Pin
imnexensys18-Apr-06 19:55
imnexensys18-Apr-06 19:55 
AnswerRe: I am not able to select Datagrid Row Pin
Ahmed Abdelgawad18-Apr-06 20:02
Ahmed Abdelgawad18-Apr-06 20:02 
GeneralRe: I am not able to select Datagrid Row Pin
imnexensys18-Apr-06 20:14
imnexensys18-Apr-06 20:14 
GeneralRe: I am not able to select Datagrid Row Pin
Ahmed Abdelgawad18-Apr-06 21:17
Ahmed Abdelgawad18-Apr-06 21:17 
GeneralRe: I am not able to select Datagrid Row Pin
imnexensys18-Apr-06 23:57
imnexensys18-Apr-06 23:57 
Generalyou can add expand allll / collapse all Pin
eng_asem7810-Apr-06 20:39
eng_asem7810-Apr-06 20:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.