Click here to Skip to main content
Click here to Skip to main content

Creating a DataGrid that expands and collapses like a tree control

, 10 Apr 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
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)

Share

About the Author

Ahmed Abdelgawad
Web Developer
Kuwait Kuwait
No Biography provided

Comments and Discussions

 
GeneralTemplate column Value PinmemberFrancoisB22-Jun-06 3:27 
GeneralRe: Template column Value PinmemberFrancoisB22-Jun-06 4:20 
GeneralRe: Template column Value PinmemberFrancoisB22-Jun-06 4:57 
GeneralRe: Template column Value PinmemberAhmed Abdelgawad24-Jun-06 0:36 
I am sorry for replying late.
For sorry you w'll not be able to get the updates!!!!
This happens because in every (PageLoad), I bind the grids again and again.
So when you update, before the update event fires, the <b>Page_Load </b>is executed and binds again, and overrides the changes you have recently done with the original value that is supposed to be changed, so you lose the change that was done.
 
So??
 
Actually, the normal scenario is:
 
<code>if(!IsPostBack)
{
      UpdateUI();
}</code>
 
And the question is:Why donot you do that?????
When I tried to do that, I lose a record between post backs!!,
to tell the truth, I couldnot figure out why does that happen.
To give you more information, if you are willing to fix that bug, the whole staff is in the <b>PreRender </b>event.
 
I hope to have time to update this version
 
Sorry for not helping you.
 

 


GeneralRe: Template column Value PinmemberAhmed Abdelgawad24-Jun-06 0:37 
QuestionCan this be done on a TreeView control? PinmemberMad M0nk18-Apr-06 23:26 
AnswerRe: Can this be done on a TreeView control? PinmemberAhmed Abdelgawad18-Apr-06 23:52 
QuestionI am not able to select Datagrid Row Pinmemberimnexensys18-Apr-06 20:02 
AnswerRe: I am not able to select Datagrid Row PinmemberAhmed Abdelgawad18-Apr-06 20:34 
QuestionRe: I am not able to select Datagrid Row Pinmemberimnexensys18-Apr-06 20:55 
AnswerRe: I am not able to select Datagrid Row PinmemberAhmed Abdelgawad18-Apr-06 21:02 
GeneralRe: I am not able to select Datagrid Row Pinmemberimnexensys18-Apr-06 21:14 
GeneralRe: I am not able to select Datagrid Row PinmemberAhmed Abdelgawad18-Apr-06 22:17 
GeneralRe: I am not able to select Datagrid Row Pinmemberimnexensys19-Apr-06 0:57 
Generalyou can add expand allll / collapse all Pinmembereng_asem7810-Apr-06 21:39 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411019.1 | Last Updated 10 Apr 2006
Article Copyright 2006 by Ahmed Abdelgawad
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid