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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.