Introduction
Most people don't need complex TreeView populators that obey the MVC (Model-View-Controller) pattern, and will simply create all the tree nodes they need on the fly. If you have already created a class that derives from the TreeNode
class, you know the power it gives you and also how it makes your code much more cleaner. Yep, very often, our Form code can be 10000+ lines of code which is very hard to maintain. So, the point is to move all the code needed for your nodes in your TreeView
to another class.
Background
I have already read the article "Generic TreeView"[^] by Mike Appleby that describes how to populate a TreeView
from your object model. But, its approach is very complex to understand and to use.
The Data
Here is a full example of what I have in mind. First, take a little look at the representation of the contents of a TreeView
:
+ Directory
+ Directory
+ File
+ File
+ File
+ File
Directory and File(s) could take any name. We can see that a Directory can have two types of object under it: a Directory or a File.
The Model
To handle the contents of our TreeView
, we will need to create two classes: CDirectory
, CFile
, and an abstraction of both:
public abstract class CContent
{
public string Name;
public CContent(string Name)
{
this.Name = Name;
}
};
public class CDirectory : CContent
{
public List<CContent> List = new List<CContent>();
public CDirectory(string Name)
: base(Name)
{ }
};
public class CFile : CContent
{
public CFile(string Name)
: base(Name)
{ }
};
The View (Not-MVC, Bad Solution)
Let say that we want that our directories show up with a green color in our TreeView
and our files show up with a red color. To make it easy to handle, we will create two other classes that will derive from TreeNode
: TN_Directory
and TN_File
, which looks like:
public class TN_Directory : TreeNode
{
public TN_Directory(CDirectory dir)
{
Tag = dir;
Text = dir.Name;
ForeColor = Color.Green;
foreach (CContent content in dir.List)
Nodes.Add(content.CreateNode());
}
};
public class TN_File : TreeNode
{
public TN_File(CFile file)
{
Tag = file;
Text = file.Name;
ForeColor = Color.Red;
}
};
This way, we could more easily populate our TreeView
using those custom TreeNode
s. In some programs, it becomes necessary to do so because of the complexity.
There is no good way to populate our TreeView
with that. The only way that I have found is to add the function CreateNode
to CContent
, CDirectory
, and CFile
, as follows:
public class CContent
{
...
public abstract TreeNode CreateNode();
};
public class CDirectory
{
...
public override TreeNode CreateNode()
{ return new TN_Directory(this); }
};
public class CFile
{
...
public override TreeNode CreateNode()
{ return new TN_File(this); }
};
Now, we want to populate our TreeView
. All we need is this: treeView1.Nodes.Add(m_root.CreateNode());
.
The full code for Form1
:
public partial class Form1 : Form
{
private TreeView treeView1;
private CDirectory m_root;
public Form1()
{
InitializeComponent();
SuspendLayout();
treeView1 = new TreeView();
treeView1.Dock = System.Windows.Forms.DockStyle.Fill;
Controls.Add(this.treeView1);
ResumeLayout(false);
CDirectory dir = new CDirectory("Windows");
dir.List.Add(new CFile("notepad.exe"));
dir.List.Add(new CFile("regedit.exe"));
dir.List.Add(new CFile("win.ini"));
m_root = new CDirectory("C:\\");
m_root.List.Add(dir);
m_root.List.Add(new CFile("autoexec.bat"));
treeView1.Nodes.Add(m_root.CreateNode());
}
}
We have a big problem here, and it is because we have stuff of our View in our Model (CreateNode
function). To avoid that, we will use a different approach. We will implement the Visitor pattern.
The View (MVC, Good Solution Using the Visitor Pattern)
A good description of the pattern can be found on Wikipedia [^], and I suggest that you read it before going any further. OK, the Visitor pattern is composed of four parts: the visitable interface, the Visitor class, a Visitor, and a class that implements the visitable interface. First, let's take a look at the visitable interface (we declared it here as an abstract class, but we could have also made it as an interface):
public abstract class CVisitable
{
public abstract void accept(CVisitor visitor);
}
Next, we need the Visitor class:
public abstract class CVisitor
{
public abstract void visit(CFile file);
public abstract void visit(CDirectory dir);
}
Yet again, we could have created an interface instead of an abstract class.
Now, we implement the CVisitable
in our Model:
public class CDirectory : CContent
{
...
public override void accept(CVisitor visitor)
{
visitor.visit(this);
}
};
public class CFile : CContent
{
...
public override void accept(CVisitor visitor)
{
visitor.visit(this);
}
};
This was very simple, and it will be the same code for any additional class that you could create. Now the interesting part starts. The part that will really make the difference. We will create a Visitor object. Our Visitor will be charged to create the corresponding TreeNode
and will be called CVisitor_TreeNode
:
public class CVisitor_TreeNode : CVisitor
{
public TreeNode TreeNode;
public override void visit(CFile file)
{
TreeNode = new TN_File(file);
}
public override void visit(CDirectory dir)
{
TreeNode = new TN_Directory(dir);
}
}
We are almost done. All we have to do now is to change our TreeView
populator code in the Form1
constructor and change the TN_Directory
to use our new technique:
public class TN_Directory : TreeNode
{
public TN_Directory(CDirectory dir)
{
Tag = dir;
Text = dir.Name;
ForeColor = Color.Green;
foreach (CContent content in dir.List)
{
CVisitor_TreeNode castTreeNode = new CVisitor_TreeNode();
content.accept(castTreeNode);
Nodes.Add(castTreeNode.TreeNode);
}
}
};
public partial class Form1 : Form
{
private TreeView treeView1;
private CDirectory m_root;
public Form1()
{
...
CVisitor_TreeNode castTreeNode = new CVisitor_TreeNode();
content.accept(castTreeNode);
treeView1.Nodes.Add(castTreeNode.TreeNode);
}
}
This is a simple and very clean solution. But it doesn't obey the MVC pattern though.
Points of Interest
We have learned in this article what the Visitor pattern is and how to separate the View from the Model in a complex TreeView architecture.
History
- Revision 0: Initial revision (2 Jul 2008).