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

TreeGridView Trick

, 31 Jan 2013
Rate this:
Please Sign up or sign in to vote.
Synchronize a TreeView with a DataGridView to get TreeGridView functionality.

Introduction

This is a brute force method for providing a TreeGridView while retaining full TreeView and DataGridView features.

Background

I have searched for many years for a fully functional set of source code that doesn't compromise the built-in features of core .NET controls. I have seen many examples that trick a GridView or a ListView to act the way we want them to but these fall short for my purposes.

So, why not lash a TreeView and DataGridView together?

Using the code

The code assumes a basic hierarchical dataset with a simple DataTable (named TreeGridItem, thus, the TGI datatable) with intID, intParentID, and Order fields to run the primary methods (additional fields and/or pointers to other tables to be added as needed). This example just happens to have a strName, strLongName, and intNumber in it for grins.

Populate the dataset with whatever hierarchy you desire and then push it to your treeview. I've included a temporary dataset filler just to get it going. For good measure, use an Order field in your dataset to ensure you load nodes in the proper order and populate the DataGridView in the same manner. I copied the source datatable rows into the datagridview table just to get it going quickly (a clone would probably be better).

The code uses the NodeMouseClick to select DataGridView rows, the AfterCollapse to hide rows, and the AfterExpand to un-hide rows. The hide and un-hide methods are very inefficient by looping through all rows to find the node-row match, but you get the point. This does not scale well past about 1000 rows, so it is for smaller datasets. Likewise, I have not implemented a scrollbar synchronizer to keep the TreeView and DataGridView in sync once you start scrolling.

#region Fields
TreeNode ndSelected;
#region Data
DataSet1.TreeGridItemDataTable dtTGI = new DataSet1.TreeGridItemDataTable();
DataSet1.TreeGridItemDataTable dtDgvw = new DataSet1.TreeGridItemDataTable();
#endregion
#endregion

public TreeGridViewControl()
{
    InitializeComponent();
    Init();
}

private void Init()
{
    InitData();
    InitTree();
    InitDataGridView();
}

private void InitData()
{            
    DataSet1.TreeGridItemRow dr;
    DataSet1.TreeGridItemRow dr1;
    DataSet1.TreeGridItemRow dr2;

    Int32 rootID;
    Int32 preFixID;
    Int32 lvl1ID;

    string[] preFix = new string[] { "A", "B", "C" };

    int lvl1 = 3;
    int lvl2 = 3;

    DataSet1.TreeGridItemRow drRoot = dtTGI.NewTreeGridItemRow();
    drRoot.intParentID = -1;
    drRoot.strName = "Root";
    drRoot.strLongName = "Long Root";
    drRoot.intNumber = -1;
    drRoot.Order = 0;
    dtTGI.Rows.Add(drRoot);

    rootID = Convert.ToInt32(dtTGI.Rows[dtTGI.Rows.Count - 1]["intID"]);
    for (int i = 0; i < preFix.Length; i++)
    {
        dr = dtTGI.NewTreeGridItemRow();
        dr.intParentID = rootID;
        dr.strName = preFix[i];
        dr.strLongName = "Long " + dr.strName;
        dr.intNumber = 0;
        dr.Order = i;
        dtTGI.Rows.Add(dr);

        preFixID = Convert.ToInt32(dtTGI.Rows[dtTGI.Rows.Count - 1]["intID"]);
        for (int j = 0; j < lvl1; j++)
        {
            dr1 = dtTGI.NewTreeGridItemRow();
            dr1.intParentID = preFixID;
            dr1.strName = preFix[i] + j;
            dr1.strLongName = "Long " + dr1.strName;
            dr1.intNumber = 1;
            dr1.Order = j;
            dtTGI.Rows.Add(dr1);

            lvl1ID = Convert.ToInt32(dtTGI.Rows[dtTGI.Rows.Count - 1]["intID"]);
            for (int k = 0; k < lvl2; k++)
            {
                dr2 = dtTGI.NewTreeGridItemRow();
                dr2.intParentID = lvl1ID;
                dr2.strName = preFix[i] + j + k;
                dr2.strLongName = "Long " + dr2.strName;
                dr2.intNumber = 2;
                dr2.Order = k;
                dtTGI.Rows.Add(dr2);
            }
        }    
    }
}

private void InitTree()
{
    DataSet1.TreeGridItemRow drRoot = dtTGI.FindByintID(0);
    TreeNode ndRoot = new TreeNode();
    ndRoot.Text = drRoot.strName;
    ndRoot.Tag = drRoot.intID;
    AddDataGridViewRow(ndRoot);
    getNodeChildren(ndRoot);
    treeView1.Nodes.Add(ndRoot);
    treeView1.ExpandAll();
}

private void getNodeChildren(TreeNode currNode)
{
    Int32 currNodeID = Convert.ToInt32(currNode.Tag);
    DataRow[] sel = dtTGI.Select("intParentID = " + currNodeID, "Order ASC");
    DataSet1.TreeGridItemRow dr;
    if (sel.Length > 0)
    {
        dr = (DataSet1.TreeGridItemRow)sel[0];
        foreach (DataSet1.TreeGridItemRow cdr in sel)
        {
            TreeNode childNode = new TreeNode();
            childNode.Text = cdr.strName;
            childNode.Tag = cdr.intID;
            AddDataGridViewRow(childNode);
            getNodeChildren(childNode);
            currNode.Nodes.Add(childNode);
        }
    }
}

private void AddDataGridViewRow(TreeNode currNode)
{
    DataSet1.TreeGridItemRow drSel = dtTGI.FindByintID(Convert.ToInt32(currNode.Tag));
    DataSet1.TreeGridItemRow dr = dtDgvw.NewTreeGridItemRow();
    dr.intID = drSel.intID;
    dr.intParentID = drSel.intParentID;
    dr.strName = drSel.strName;
    dr.strLongName = drSel.strLongName;
    dr.intNumber = drSel.intNumber;
    dr.Order = drSel.Order;

    dtDgvw.Rows.Add(dr);
    dtDgvw.AcceptChanges();
}

private void InitDataGridView()
{
    dataGridView1.DataSource = dtDgvw;
}

private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
    ndSelected = e.Node;
    treeView1.SelectedNode = ndSelected;
    SelectActiveRow();
}

private void treeView1_AfterCollapse(object sender, TreeViewEventArgs e)
{
    foreach (TreeNode nd in e.Node.Nodes)
    {
        HideRow(nd);
        HideAllChildRows(nd);
    }
}

private void HideAllChildRows(TreeNode nd)
{            
    foreach (TreeNode ndChild in nd.Nodes)
    {
        HideRow(ndChild);
        HideAllChildRows(ndChild);
    }
}

private void HideRow(TreeNode nd)
{            
    for (int i = 1; i < dataGridView1.Rows.Count - 1; i++)
    {
        Int32 ndID = Convert.ToInt32(nd.Tag);
        Int32 rowID = Convert.ToInt32(dataGridView1.Rows[i].Cells[0].Value);
        if (ndID == rowID)
        {
            if (dataGridView1.Rows[i].Selected)
            {
                dataGridView1.Rows[i].Selected = false;
                dataGridView1.CurrentCell = dataGridView1.Rows[0].Cells[0];
            }
            dataGridView1.Rows[i].Visible = false;
        }
    }
}

private void treeView1_AfterExpand(object sender, TreeViewEventArgs e)
{
    foreach (TreeNode nd in e.Node.Nodes)
    {
        UnhideRow(nd);
        UnHideExpandedChildRows(nd);
    }
}

private void UnHideExpandedChildRows(TreeNode nd)
{
    if (nd.IsExpanded)
    {
        foreach (TreeNode ndChild in nd.Nodes)
        {
                UnhideRow(ndChild);
                UnHideExpandedChildRows(ndChild);
        }
    }
}

private void UnhideRow(TreeNode nd)
{
    for (int i = 1; i < dataGridView1.Rows.Count - 1; i++)
    {
        Int32 ndID = Convert.ToInt32(nd.Tag);
        Int32 rowID = Convert.ToInt32(dataGridView1.Rows[i].Cells[0].Value);
        if (ndID == rowID)
        {
            dataGridView1.Rows[i].Visible = true;
        }
    }
}

private void SelectActiveRow()
{
    for (int i = 1; i < dataGridView1.Rows.Count - 1; i++)
    {
        Int32 ndID = Convert.ToInt32(ndSelected.Tag);
        Int32 rowID = Convert.ToInt32(dataGridView1.Rows[i].Cells[0].Value);
        if (ndID == rowID)
        {
            dataGridView1.Rows[i].Selected = true;
            dataGridView1.CurrentCell = dataGridView1.Rows[i].Cells[0];
        }
        else
        {
            dataGridView1.Rows[i].Selected = false;
        }
    }
}

Points of Interest

Note: You cannot set a DataGridView.Rows[Index].Visible = false if the row is selected. Thus, deselect it and set the CurrentCell value to the root node's datarow.

I will add a scrolling synchronizer next to meet my needs.

History

2013-01-01: Initial version.

License

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

About the Author

Mike Prochko
Architect
United States United States
No Biography provided

Comments and Discussions

 
QuestionNice post PinmemberTridip Bhattacharjee12-Dec-13 21:57 
GeneralMy vote of 4 PinmemberAmol Kute9-Jan-13 20:13 
Questioncoloque un demo PinmemberOsvaldo Mendez7-Jan-13 4:19 

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 | Mobile
Web02 | 2.8.140709.1 | Last Updated 31 Jan 2013
Article Copyright 2013 by Mike Prochko
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid