Click here to Skip to main content
15,898,222 members
Articles / Desktop Programming / Windows Forms
Article

Advanced TreeView for .NET

Rate me:
Please Sign up or sign in to vote.
4.91/5 (178 votes)
10 Jul 20064 min read 1.3M   33.8K   532   304
The TreeViewAdv control is designed to replace the standard .NET TreeView. It can do the same things, plus a number of advanced features like multi-selection or multi-column view.

TreeViewAdv in Multi-Column mode

Introduction

Working on several different projects, I was needed to display and edit hierarchical data. Of course, the first thing you will do is to use the standard .NET TreeView control. It works pretty well if you only need basic features. But learning this control to do something more complex is not an easy job. I could not find an alternative TreeView control which is free and fully meets my needs, so finally I decided to write my own.

The architecture of this control comes mainly from the Java Swing component, with some modifications. These are the key features of the TreeViewAdv control:

  • Model-View architecture - Will be covered in a separate section of this article.
  • Multiselection - Maybe the first limitation which you will find in the standard TreeView is that it’s not possible to select more then one node.
  • Unlimited number of controls for each node - You can display three icons + a CheckBox + two Labels.
  • Multicolumns - You can split the TreeView into several columns.
  • Load on Demand - Lazy load of child nodes.
  • Drag & Drop highlighting - Dynamically highlight the drop position.
  • 100% pure .NET code - No WinAPI is used in this control.

The following screenshots illustrate the TreeViewAdv features:

Drag&Drop highlighting

Multiselection

Using ComboBox to edit node

Model-View Architecture

I really like the Model-View pattern, and decided to use it in this control. The main idea of this pattern is to split the model (business object) from its visualization (control). If the model changes, it notifies the view by firing corresponding events. The view asks the model for details, if needed, and displays the changes. The model is described by ITreeModelInterface:

C#
public interface ITreeModel
{
    IEnumerable GetChildren(TreePath treePath);
    bool IsLeaf(TreePath treePath);

    event EventHandler<TreeModelEventArgs> NodesChanged; 
    event EventHandler<TreeModelEventArgs> NodesInserted;
    event EventHandler<TreeModelEventArgs> NodesRemoved; 
    event EventHandler<TreePathEventArgs> StructureChanged;
}

It’s very simple, and you need to implement only two methods. GetChildren should return the list of child nodes of the specified parent (empty for root nodes). IsLeaf method tells TreeView whether it should try to read child nodes of the specified parent. If you wish TreeView to dynamically track model changes, you need to use one of several events of the ITreeModel interface. The most common is the StructureChanged event, which cause the TreeView to fully refresh the specified node (or empty, for the whole model). For example, see the default implementation of the ITreeModel interface – the TreeModel class.

To specify the exact node in the model, TreePath class is used. It stores the path from the root to the node, in the FullPath property.

C#
public class TreePath
{
    public object[] FullPath{ get; }
    public object LastNode{ get; }
    public object FirstNode{ get; }
}

Using TreeView

In the source code, you can find two examples of how to use TreeViewAdv. The simplest way is to use TreeModel. All you need is to populate it with data and display it in the view:

C#
_model = new TreeModel();
_model.Nodes.Add(new Node("Root"));
_tree.Model = _model;

The Node class, which is used in TreeModel, contains only the ‘Text’ and ‘IsChecked’ properties. If you need additional properties, you can create an ancestor of the Node class and use it in TreeModel.

But to use the full power of the TreeViewAdv, you should create your own realization of the ITreeModel interface. See the folder browser presented in the source code, for an example.

Customizing TreeView

There are a number of properties which help to customize the look and behavior of the TreeView. The main ones are:

  • Model - Assign your model to this property to display it.
  • NodeControls - The collection of controls which will be used to visualize the model. You should provide at least one NodeControl in order to see the model.
  • LoadOnDemand - Read all child nodes at start-up or when the parent node expands.
  • SelectionMode - Single (no multi-selection), Multi, MultiSameParent (children of only one node can be selected).
  • UseColumns - Display data in columns or not.
  • Columns - The collection of columns. For each column, you can specify its header, width and alignment.

NodeControls

The standard TreeView can display only one icon, CheckBox, and Label for each node. In TreeViewAdv, you can use any number of NodeControl. All controls must inherit from the ‘NodeControl’ abstract class. Inherited classes should contain the code to draw the control and the code to respond on user actions – mouse and keyboard events.

NodeControl

This is the class diagram of all NodeControls provided by the library:

Class diagram

The BindableControl class provides a ‘DataPropertyName’ which is used in the control to read and write data to the node. All that you need is to specify the name of the property of your class.

Terms and Conditions

The TreeViewAdv control is provided as free software with open source code. You can use it in your applications if the original copyright is kept.

The latest version of TreeViewAdv is always available here. Please feel free to add your comments and suggestions in the forum there.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


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

Comments and Discussions

 
AnswerRe: Text Color in node Pin
gewe17-Oct-08 23:06
gewe17-Oct-08 23:06 
Questionhow can i add tooltip for each node? Pin
margiex28-Apr-08 0:16
margiex28-Apr-08 0:16 
AnswerRe: how can i add tooltip for each node? Pin
Candace Bain25-Sep-08 6:25
Candace Bain25-Sep-08 6:25 
GeneralRe: how can i add tooltip for each node? Pin
protection_gel18-Jan-10 0:48
protection_gel18-Jan-10 0:48 
QuestionHorizontal scrolling: column header text not moving along Pin
gewe16-Apr-08 2:58
gewe16-Apr-08 2:58 
AnswerRe: Horizontal scrolling: column header text not moving along Pin
gewe14-May-08 4:07
gewe14-May-08 4:07 
QuestionWich is the simplest way to work with multicolumn? Pin
PuiuMT28-Feb-08 23:50
PuiuMT28-Feb-08 23:50 
AnswerRe: Wich is the simplest way to work with multicolumn? [modified] Pin
pj3452-Mar-08 5:54
pj3452-Mar-08 5:54 
You need to do a couple things.

1. Set the UseColumns property to true.
2. Define columns in the Columns collection.
3. Define nodes in the NodeControls collection. Make sure your nodes are connected to the columns by mapping ParentColumn to the columns in Columns collection. Also set the DataPropertyName to match with the Data Model that you define for holding tree data. In the DataTableTree example, the DataPropertyName are mapped to the properties in the DataRowNode class.
4. Bind the control to a model with multiple columns. You need to define a model class, and a node (item) class. In the Folder browser example, the FolderItems class defines the node, and provides access to the columns. In the DataTableTreeExample, the DataRowNode serves the same function, though DataTableTreeExample is a single column sample. In the model class, you need to implement at least 2 methods - GetChildren and IsLeaf.

I am studying the control also, and I took some notes as I went through the DataTableTreeModel.cs file. See inline comments.

Audrey: If you are reading this, I'll appreciate if you could double check my understanding of your code, and incorporate the comments into your sample if you think it is correct and useful. Having comments would make life easier for everyone. Thanks for making this great control.

- DataTableTreeModel.cs -------------------------------------------
using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using Aga.Controls.Tree;
using System.Drawing;
namespace SampleApp
{
///
/// Using a System.Data.DataTable to represent a tree structure
///

public class DataTableTreeModel : TreeModelBase
{
private DataRowNode m_root; // m_root represents the root row in a DataTable

DataTable m_table; // Internal variable to store the name DataTable that
// the control is bound to
string m_IDColumnName; // Name of the column that uniquely identifies a node

// Binds a DataTable to DataTableTreeModel by setting the internal
// variables of m_table and m_IDColumnName. Also create a root node
// by binding to data on the first row of the DataTable.
public DataTableTreeModel(DataTable table, string idColumnName)
{
// Set internal variables.
m_table = table;
m_IDColumnName = idColumnName;
// Find the root node by looking up a row in which the ID and ParentID
// columns have the same data.
DataRow[] rows = table.Select(m_IDColumnName+" = ParentID");
if( rows.Length ==0 ) // if root node is empty ...
{
throw new Exception("DataTableModel Requires a root Node");
}
m_root = new DataRowNode(rows[0],rows[0]["Name"].ToString());
// Only bind to the first row retrieved back. This means that only a
// single root node is created even if there are multiple roots in the
// DataTable.
m_root.Row = rows[0];
}

// When a parent node is expanded, this function gets the child rows and
// add them to the tree.
public override System.Collections.IEnumerable GetChildren(TreePath treePath)
{
List<datarownode> items = new List<datarownode>();

if (treePath.IsEmpty() )
{
items.Add(m_root);
}
else
{
// Set the current node to point to the last node on the
// sub-tree under "treePath".
DataRowNode n = treePath.LastNode as DataRowNode;

// Retrieve the DataRow represented by the last node.
DataRow row = n.Row;
// Get the parent row id.
int id = Convert.ToInt32(row[m_IDColumnName]);

// Select the rows on the DataTable in which ParentID = id of the
// parent node where tree expansion is taking place. The "<>"
// condition is to exclude the root node from getting added again
// as the root node would have ID = ParentID.
DataRow[] rows = m_table.Select("ParentID = " + id+" and "+m_IDColumnName+" <> "+id);
// Add the individual rows that are retrieved back from the DataTable.
foreach (DataRow r in rows)
{
DataRowNode node = new DataRowNode(r,r["Name"].ToString());
node.Row = r;
//SampleApp.Properties.Resources.ResourceManager.
//node.Icon = new Bitmap(SampleApp.Properties.Resources.Records,new Size(15,15));
items.Add(node);
}
}
return items;
}

// Function that returns true/false based on whether the node is a leaf
// node with no child.
public override bool IsLeaf(TreePath treePath)
{
DataRowNode n = treePath.LastNode as DataRowNode;
if (n.Row["IsFolder"] == DBNull.Value)
return false;
return !Convert.ToBoolean(n.Row["IsFolder"]);
}


//public event EventHandler<treemodeleventargs> NodesChanged;

//public event EventHandler<treemodeleventargs> NodesInserted;

//public event EventHandler<treemodeleventargs> NodesRemoved;

//public event EventHandler<treepatheventargs> StructureChanged;

// Create a new DataRow and add the row to the tree.
public void AddChild(TreePath parent, string text)
{
DataRowNode n = parent.LastNode as DataRowNode;

// Create and initial the new DataRow.
DataRow r = m_table.NewRow();
r["ID"] = GetNextID();
r["ParentID"] = n.Row["ID"];
r["IsFolder"] = false;
r["Name"] = text;
r["Tag"] = "";
m_table.Rows.Add(r);

// Create the corresponding DataRowNode on the tree.
DataRowNode child = new DataRowNode(r, text);

// Raise an OnStructureChanged event in order to trigger a
// reload of the sub-tree. The GetChildren function would
// get called by the event handler to handle the update.
// Under this scheme, only the set of nodes that are the
// the direct child of the sub-tree parent would get added
// back. If I have a sub-tree that is fully expanded with
// multiple sub-trees underneath, the 2nd to n-th level
// sub-tree would get collasped after this update. In other
// words, on the tree below, if I add "a4" to "a", the "a1"
// branch would get collapsed when "a4" is added. The data
// would still be in the DataTable - they just won't show up
// on the control until that part of the tree gets expanded
// again.
//
// a
// .a1
// ..a1b
// ..a1c
// .a2
// .a3

OnStructureChanged(new TreePathEventArgs(parent));
}

// Generate the next ID for the DataTable's primary key.
private int GetNextID()
{
int max = 1;
for (int i = 0; i < m_table.Rows.Count; i++)
{
int id = Convert.ToInt32(m_table.Rows[i]["ID"]);
if (id > max)
max = id;
}

return max + 1;
}
}
}

modified on Sunday, March 2, 2008 12:04 PM

GeneralRe: Wich is the simplest way to work with multicolumn? Pin
PuiuMT3-Mar-08 6:19
PuiuMT3-Mar-08 6:19 
GeneralProblem Loading Solution Pin
fishindude27-Feb-08 4:33
fishindude27-Feb-08 4:33 
GeneralSkin Pin
virtual.aussie24-Jan-08 6:44
virtual.aussie24-Jan-08 6:44 
GeneralRe: Skin Pin
pj34524-Feb-08 10:28
pj34524-Feb-08 10:28 
QuestionHow to include a button... Pin
robvon14-Jan-08 17:49
robvon14-Jan-08 17:49 
GeneralRefresh TreeView Pin
TweedleMB10-Dec-07 20:25
TweedleMB10-Dec-07 20:25 
GeneralEnable Edit nodes text Pin
anderslundsgard28-Nov-07 22:31
anderslundsgard28-Nov-07 22:31 
GeneralRe: Enable Edit nodes text Pin
John Giblin23-Mar-10 11:48
John Giblin23-Mar-10 11:48 
GeneralRe: Enable Edit nodes text Pin
lastunicorn3-Oct-10 1:33
lastunicorn3-Oct-10 1:33 
QuestionColumn header autofill Pin
Radu_2022-Nov-07 1:38
Radu_2022-Nov-07 1:38 
GeneralPrint Pin
yamadakun20-Nov-07 2:10
yamadakun20-Nov-07 2:10 
GeneralGreat job Pin
Massimo Colurcio1-Nov-07 17:01
professionalMassimo Colurcio1-Nov-07 17:01 
Generalother Backgroundcolor for each line Pin
SteveA6931-Oct-07 5:20
SteveA6931-Oct-07 5:20 
GeneralRe: other Backgroundcolor for each line Pin
Vivek_Minhas21-Jan-09 21:16
Vivek_Minhas21-Jan-09 21:16 
QuestionNot able to understand Pin
pratik_at_work25-Oct-07 1:26
pratik_at_work25-Oct-07 1:26 
GeneralWeb form Pin
Jim Brooks23-Oct-07 17:56
Jim Brooks23-Oct-07 17:56 
QuestionNodeControls design & latest version Pin
Octopod29-Sep-07 23:39
Octopod29-Sep-07 23: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.