Click here to Skip to main content
13,630,931 members
Click here to Skip to main content
Add your own
alternative version


140 bookmarked
Posted 11 Mar 2004
Licenced CPOL

How to fill hierarchical data into a TreeView using base classes and data providers.

, 11 Mar 2004
Rate this:
Please Sign up or sign in to vote.
This tutorial shows how to use the System.Windows.Forms.TreeView control in a way that the end user and the developer can have phun at the end of the day.

Sample Image - treeview.gif


This tutorial shows how to use the System.Windows.Forms.TreeView control in a way that the end user and the developer can have phun at the end of the day.

Basically, I would like to talk about:

  • Load On Demand
  • Recursive method calls for hierarchical data structures
  • The use of DataProviders

developer view

Before I start to describe the data, I would like to note that all developers like to write code that they don't have to change often. They like to write code which they can reuse later to develop straight forward on a tested code base.

If we now create a TreeView class, for instance, which meets exactly the requirements for our current project, the chances to reuse the same code later in another project are <=0. We have to find a generic way to implement the project related stuff separated and reuse our tested TreeView class which provides only the most common features.

That's where the IDataProvider interface comes in. With data providers, we can reuse our tested TreeView code for new projects which require different specific features and behaviors.

Normally, you will have to fill data into a tree view which reflects a hierarchical data schema. Typically, your data contains a text and ID field, which should be used as underlying data for your tree nodes. This example uses an Access database which is delivered in the sample application and makes use of this schema and data:

As you can see, we have items with a text field identified by an ID field, which can hold sub items identified by a parent_id field. The root items have typically no parent_id or values like null or -1. Now, this looks like real world data to me, I guess there are thousands of data tables like this one out there, and typically these ones are filled into a tree view. But hey, which user would like to wait until all rows have been filled, it's not a good idea to try to load all rows into a tree view. Because...

User view

The user expects a fast and short tree view handling, that you can't provide if you fill all items at once, we have to fill on demand. This means we fill all the items the user expands during his work. The user don't need all items for his work, only a few ones, which he expand automatically during his work. To load only a few items is a fast operation and gives the users a good experience about working with the software.



After this short analysis, we should notice the following requirements:

  • We need to use a fill mechanism that can work on different item levels (for instance root items, sub items...).
  • We need a way to assign a + sign to our (empty) parent nodes which we would like to fill later, otherwise the user doesn't realize that he can expand the node.
  • We need to declare a data provider interface.

This UML diagram shows the classes created to design the tutorial base classes.

TreeNodeBaseTreeNodeBase inherits from TreeNode and provides methods to handle dummy child nodes for filling on demand operations.
TreeViewStrategyThe TreeViewStrategy is tree view which aggregates a tree view data provider interface (Strategy pattern). The goal of this design is to provide an easy way to extend or add data providers without changing a single line of tree view code. Basically, the tree view interface will not change so far, but the data providers will change their behavior and features.
IDataProviderDataProvider interface is based on TreeViewStrategy and TreeNodeBase and it is responsible to fill a TreeViewStrategy instance with the requested data.

But, let's take a closer look at the classes now...

Base classes


First, we create a new class which inherits from System.Windows.Forms.TreeNode. Let us add the required handling for dummy nodes to our base tree node class. Dummy nodes are used to assign a + sign to an empty parent node. An empty parent node is a node, which must have a + sign because we exactly know that there are sub items to be filled later.

public class TreeNodeBase : System.Windows.Forms.TreeNode
Public Instance Properties
/// Gets a value indicating if this node owns a dummy node.
public virtual bool HasDummyNode
        return (Nodes.Count>0 && Nodes[0].Text == "@@Dummy@@");
Public Instance Methods
    /// Adds a dummy node to the parent node
    public virtual void AddDummyNode()
        Nodes.Add(new TreeNodeBase("@@Dummy@@"));
    /// Removes the dummy node from the parent node.
    public virtual void RemoveDummyNode()
        if ((Nodes.Count == 1 ) & (Nodes[0].Text == "@@Dummy@@"))

Every TreeNodeBase instance can now be easily made an empty parent node, by calling AddDummyNode(). If we would like to know if a node instance has currently a dummy node assigned, we can determine the return value of HasDummyNode. To get rid of our dummy node, we just call RemoveDummyNode().


Now, we create a new class which inherits from System.Windows.Forms.TreeView.

public class TreeViewStrategy : System.Windows.Forms.TreeView
Public Instance Fields
/// <SUMMARY>Fired if the datasource has changed</SUMMARY>
public EventHandler DataSourceChanged;
/// <SUMMARY>The underlying data provider which is
/// used to start requests.</SUMMARY>
private IDataProvider _dataProvider;

Because our IDataProvider is responsible to handle the ContextMenu, we have to create and assign a ContextMenu instance to our TreeView.

/// Initializes a new instance of the TreeViewStrategy class.
public TreeViewStrategy()
    // create the context menu and assing it to the tree view
    ContextMenu = new ContextMenu();
    ContextMenu.Popup += new EventHandler(ContextMenu_Popup);
Public Instance Properties

The DataSource property will let us attach a IDataProvider implementation later.

/// Gets or sets the IDataProvider which is responsible
/// to manage this <SEE cref="TreeView" /> instance.
public IDataProvider DataSource
        return _dataProvider;
        _dataProvider = value;
        // fire the DataSourceChanged event
Public Instance Methods

The Fill method is called to fill the root level of the tree view. Note that the Fill method will handle the WaitCursor and clears all the nodes before it calls a method on the IDataProvider interface which really does the work behind the scene.

/// Fill's the root level.
public void Fill()
    // show wait cursor, maybe there is
    // a longer operation on the data provider
    Cursor.Current = Cursors.WaitCursor;
        // clear all nodes
        // request the new root nodes
        // Make sure to reset the current cursor.
        // NOTE: There is no catch block, because we don't
        // want to handle occured exceptions at this level,
        // the user of this code is responsible
        // to manage it.
        Cursor.Current = Cursors.Default;

This class provides a DataSourceChanged event. It's a commonly used technique to provide a virtual method which internally fires the event for us. This way, it's possible for inheritors to consume this event in a derived class without subscribing to our delegate, by overriding the method.

/// Raises the DataSourceChanged event.
protected virtual void OnDataSourceChanged(EventArgs e)
    if(DataSourceChanged!=null) DataSourceChanged(this, e);

Because the IDataProvider interface is responsible to handle the ContextMenu, we should serve a virtual method for the ContextMenu.Popup event which we delegate to the IDataProvider. To do this, we have to create a ContextMenu for our TreeView and consume its ContextMenu.Popup event in our class and call this virtual method.

/// Invokes the <SEE cref="IDataProvider.QueryContextMenuItems" /> method
protected virtual void OnContextMenuPopup(EventArgs e)
    // retrieve node which was clicked
    TreeNodeBase node =
      GetNodeAt(PointToClient(Cursor.Position)) as TreeNodeBase;
    if(node==null) return;
    // clear previous items
    // let the provider do his work
    _dataProvider.QueryContextMenuItems(this.ContextMenu, node);

This event enables load on demand, because the event is fired every time the user expands a node. Because we're only dealing with TreeNodeBase instances, we can safely cast e.Node. If the node has a dummy node, we can fill the node now with the real child nodes. To do this, we remove the dummy node and let the IDataProvider fill the child nodes. Note that this method also handles the WaitCursor.

/// Invokes the <SEE cref="IDataProvider.RequestNodes" /> method
protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
    // cast node back to our base type
    TreeNodeBase node =  (TreeNodeBase)e.Node;
    // fill expanded node if not filled at this time (on demand)
        // remove dummy node before we fill the child nodes
    // and let the node expand itself to show
        // it's children nodes
        Cursor.Current = Cursors.WaitCursor;
            // let the provider do his work
            _dataProvider.RequestNodes(this, node, e);
            Cursor.Current = Cursors.Default;
    base.OnBeforeExpand (e);

We handle the ContextMenu.Popup event with a private event handler method.

/// Fired by the <SEE cref="ContextMenu" />
/// before the shortcut menu is displayed.
/// The private event handler is used to call the virtual
/// protected method <SEE cref="OnContextMenuPopup" />
private void ContextMenu_Popup(object sender, EventArgs e)

Don't forget to close the bracket, otherwise your compiler will let you know about messy things :).



Now, we are ready to define the IDataProvider interface. This interface is responsible to fill our TreeView and to query ContextMenu items.

/// DataProvider interface is based on TreeViewStrategy and TreeNodeBase and
/// it is responsible to fill a <SEE cref="TreeViewStrategy" />
/// instance with the requested data.
public interface IDataProvider
    /// Request to fill the root nodes.
    void RequestRootNodes(TreeViewStrategy treeView);
    /// Request to fill child nodes.
    void RequestNodes(TreeViewStrategy treeView, 
        TreeNodeBase node, TreeViewCancelEventArgs e);
    /// Request to fill a <SEE cref="ContextMenu" />
    /// with <SEE cref="MenuItem" />'s for the
    /// specified <SEE cref="TreeNode" />.
    void QueryContextMenuItems(ContextMenu contextMenu, TreeNodeBase node);

Now, we can start to write different IDataProvider implementations which work together with the TreeView class without touching the TreeView code in the future to support new features and behaviors.

Sample Application

The sample application provides a IDataProvider implementation and a demo form which makes use of our TreeViewStrategy class. I used a strongly typed DataSet class to retrieve the Access database data. Basically, my IDataProvider interface implementation retrieves the data from the DataSet and creates associated nodes for the tree view.


The first step was to create a TreeNode class which reflects the current database schema. The node has to provide ID and text property. The ID property has to be readonly. So, I defined a struct to hold the database text and ID values.

public struct DataBaseNodeInfo
Public Instance Fields
/// <SUMMARY>Holds the text</SUMMARY>
public string Text;
/// <SUMMARY>Hodsl the id</SUMMARY>
public readonly int Id;
    /// Initializes a new instance of the DataBaseNodeInfo class.
    public DataBaseNodeInfo(string text, int id)
        Text = text;
        Id = id;            

And a TreeNode class which inherits from TreeNodeBase which uses the struct as underlying data source. This is a clean way to deal with database items, without referencing any data row class that was generated from the strongly typed DataSet wizard. Note the internal constructor which is only used to create dummy nodes.

public class TreeNodeDatabase : Raccoom.Windows.Forms.TreeNodeBase
Public Instance Fields
/// <SUMMARY>holds the underlying data
/// in a <SPAN lang=de-ch>simple struct</SUMMARY>
private DataBaseNodeInfo _dbInfo;
/// Initializes a new instance of the TreeNodeDatabase class.
internal TreeNodeDatabase(string text) : base(text)
    _dbInfo = new DataBaseNodeInfo(text, -2);
/// Initializes a new instance of the TreeNodeDatabase class.
public TreeNodeDatabase(DataBaseNodeInfo dbInfo) : base(dbInfo.Text)
    _dbInfo = dbInfo;
/// Initializes a new instance of the TreeNodeDatabase class.
public TreeNodeDatabase(string text, int id) : base(text)
    this._dbInfo = new DataBaseNodeInfo(text, id);
Public Instance Properties
    /// Gets the node Id.
    public int Id
            return _dbInfo.Id;
    /// Gets or sets the text property
    new public string Text
            return _dbInfo.Text;
            _dbInfo.Text = value;

Now we have completed the basic classes that can deal with the database data in a proper manner. The only thing that is missing is the IDataProvider implementation, and I guess the only reason that you don't have stopped reading is that you would like to know how to implement it, right? :)


So, let's dive into the code, the code that controls the core process, retrieving data. To give you an example how the data provider can extend the existing TreeView with new features, this data provider contains a search based on node ID.

/// DataProviderDatabase implements the
/// <SEE cref="IDataProvider" /> interface
/// and it supports hiearchical database schemas.
public class DataProviderDatabase : IDataProvider
Public Instance Fields
/// <SUMMARY>DataTable which holds the data to fill</SUMMARY>
Raccoom.Sample.DataSet1.treeview_dataDataTable _table = null;
/// Initializes a new instance of the DataProviderDatabase class.
public DataProviderDatabase(){}
Public Instance Properties

The filled DataTable which the data provider internally uses as data source:

/// Gets or sets the DataTable which holds the data to fill.
public Raccoom.Sample.DataSet1.treeview_dataDataTable DataTable
        return _table;
        _table = value;
Public Instance Methods

Interface method which calls the private Fill method. To retrieve the child nodes, the database ID provided by the current node, which is the parent node, is used.

/// Request to fill child nodes.
public void RequestNodes(TreeViewStrategy treeView,
         TreeNodeBase node, TreeViewCancelEventArgs e)
    System.Diagnostics.Debug.Assert(node is TreeNodeDatabase);
    // fill the childs from data source (table)
    Fill(node.Nodes, ((TreeNodeDatabase)node).Id);

Interface method which also calls the private Fill method. Note: this method works for both, root and sub nodes.

/// Request to fill the root nodes.
public void RequestRootNodes(TreeViewStrategy treeView)
    // Fill the root level
    Fill(treeView.Nodes, -1);

Simple create some context menu items for the demo:

/// Request to fill aContextMenu
/// with MenuItem's for the specified TreeNode.
public void QueryContextMenuItems(ContextMenu contextMenu,
                                             TreeNodeBase node)
    contextMenu.MenuItems.Add("&Open " + node.Text);
    contextMenu.MenuItems.Add("&Edit "+ node.Text);
    contextMenu.MenuItems.Add("&Delete "+ node.Text);

This method deals with a TreeNodeCollectoin, because every TreeNode and either the TreeView provides a TreeNodeCollection. It is more generic to pass this collection as a parameter, otherwise you can't use this method recursively. Because, you have to provide a method to deal with the root nodes, and a special one to deal with a specific node. But if you deal with collections, you can call your method recursively, no matter which level you are currently in. This works for n levels.

Maybe, a possible newbie question can be: I can't provide nested for loops for each level, because I don't know at design time how deep my data structure will be. But with this kind of method, you don't have to mess around with such stuff, because the recursive design will work for every level.

/// Does a recursive search looking for the node
/// that represents the specified database id
/// starting by specified root collection.
public TreeNodeDatabase FindNodeById(TreeNodeCollection col, int id)
    // go through collection and compare
    // each which against the db id.
    foreach(TreeNodeDatabase node in col)
        if(object.Equals(node.Id, id)) return node;
        // call this method recursive to process
        // all nodes in the treeview.
        TreeNodeDatabase subNode = FindNodeById(node.Nodes, id);
        if(subNode!=null) return subNode;
    return null;

The Fill method also use a TreeNodeCollection as input parameter, this way I can create root nodes and child nodes. It makes no difference, only the parent_id is different. To decide if the currently created node is a parent node, I use a select which determines if there are any children associated with this database item. If there are any child rows, the currently created node gets a dummy node associated.

    /// Retrieves the child nodes for the given node
    /// and add's them to the node childs collection
    private void Fill(TreeNodeCollection col, int parent_id)
            row in _table.Select("parent_id = "+parent_id))
            TreeNodeDatabase node = 
                new TreeNodeDatabase(row.node_text,;
            // count on childs, if node is parent node,
            // add a dummy node for + sign
            if(_table.Select("parent_id = "+node.Id).Length>0)

Possible improvements

  • DataProvider provides ImageList property.
  • TreeNodeBase provides Refresh method and a related CanRefresh property.


This is my first tutorial and I hope it is somewhat useful for others. Keep in mind that this doesn't have to be the only or best way to deal with a TreeView, but it's the best I know of. If you would like to see more code based on this design, take a look here.

As always, any feedback or criticism is appreciated.



  • 12.03.2004 - Initial release.

Have phun...


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


About the Author

Chris Richner
Software Developer (Senior)
Switzerland Switzerland
My interest is in the future because I am going to spend the rest of my life there. (Charles Kettering)


  • 1996 - 1998 PC Board PPL, HTML, DHTML, Javascript and ASP
  • 1999 - 2001 coding Centura against Sql Database (SqlBase,MSSQL,Oracle)
  • 2002 - 2004 C# Windows Forms
  • 2005 - 2006 C# ASP.NET, Windows Forms
  • 2006 - 2009 C#, WCF, WF, WPF
  • 2010 - 2012 C#, Dynamics CRM, Sharepoint, Silverlight
  • 2013 - 2013 C#, WCF DS (OData), WF, WPF
  • 2014 - 2016 C#, Azure PaaS, Identity, OWIN, OData, Web Api
  • 2017 - now C#, aspnet.core, IdentityServer4, TypeScript & Angular @ Azure IaaS or PaaS


  • family & friends
  • chilaxing ,)
  • coding

You may also be interested in...


Comments and Discussions

GeneralFill method doubt Pin
Meera B L28-Feb-11 17:21
memberMeera B L28-Feb-11 17:21 
GeneralRe: Fill method doubt Pin
Chris Richner28-Feb-11 22:04
memberChris Richner28-Feb-11 22:04 
GeneralRe: Fill method doubt Pin
Meera B L8-Mar-11 19:14
memberMeera B L8-Mar-11 19:14 
GeneralRe: Fill method doubt Pin
Chris Richner8-Mar-11 21:18
memberChris Richner8-Mar-11 21:18 
Questionshow the winform when click the treeview node Pin
Arul Soosai11-May-09 16:31
memberArul Soosai11-May-09 16:31 
AnswerRe: show the winform when click the treeview node Pin
Chris Richner12-May-09 20:27
memberChris Richner12-May-09 20:27 
Generalquite nice for your first try, but... Pin
m.siarkowski17-May-07 22:55
memberm.siarkowski17-May-07 22:55 
GeneralRe: quite nice for your first try, but... Pin
Chris Richner18-May-07 5:01
memberChris Richner18-May-07 5:01 
Generalcannot comple on vs 2005 c# express Pin
throwit125-Jan-07 10:06
memberthrowit125-Jan-07 10:06 
GeneralRe: cannot comple on vs 2005 c# express Pin
Chris Richner25-Jan-07 11:59
memberChris Richner25-Jan-07 11:59 
GeneralRe: cannot comple on vs 2005 c# ___ thanks Pin
throwit130-Jan-07 15:35
memberthrowit130-Jan-07 15:35 
GeneralRe: cannot comple on vs 2005 c# ___ thanks Pin
Chris Richner31-Jan-07 7:52
memberChris Richner31-Jan-07 7:52 
Questionproblem with the example Pin
zizzzz25-Sep-06 0:21
memberzizzzz25-Sep-06 0:21 
AnswerRe: problem with the example Pin
Chris Richner25-Jan-07 12:02
memberChris Richner25-Jan-07 12:02 
QuestionHow to complete the sample application? Pin
fragmonster13-Oct-05 2:09
memberfragmonster13-Oct-05 2:09 
GeneralData from multiple tables Pin
Martijn de Munnik2-May-05 5:18
sussMartijn de Munnik2-May-05 5:18 
QuestionHow to change data source Pin
wws3580120-Mar-05 3:25
memberwws3580120-Mar-05 3:25 
AnswerRe: How to change data source Pin
Jerry Maguire20-Mar-05 20:17
memberJerry Maguire20-Mar-05 20:17 
QuestionHow to select the Pin
wws3580117-Mar-05 16:58
memberwws3580117-Mar-05 16:58 
AnswerRe: How to select the Pin
Jerry Maguire17-Mar-05 20:12
memberJerry Maguire17-Mar-05 20:12 
GeneralAttach ContextMenu to the specific node. Pin
greg_mike6-Mar-05 2:45
membergreg_mike6-Mar-05 2:45 
QuestionHow do I update the text property of a treenode without refill the treeview? Pin
Origon17-Jan-05 4:53
memberOrigon17-Jan-05 4:53 
AnswerRe: How do I update the text property of a treenode without refill the treeview? Pin
Jerry Maguire17-Jan-05 8:04
memberJerry Maguire17-Jan-05 8:04 
GeneralQuestions for context menu Pin
LZF13-Jul-04 11:04
memberLZF13-Jul-04 11:04 
GeneralRe: Questions for context menu Pin
Jerry Maguire13-Jul-04 18:22
memberJerry Maguire13-Jul-04 18:22 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.180712.1 | Last Updated 12 Mar 2004
Article Copyright 2004 by Chris Richner
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid