Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

How to populate a TreeView using the MVC pattern

2.08/5 (8 votes)
2 Jul 2008CPOL3 min read 1   764  
The MVC (Model-View-Controller) pattern being used to populate a TreeView.

FormScreenShot.png

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:

C#
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:

C#
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());
            // See lower for the code of 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 TreeNodes. 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:

C#
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:

C#
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);

        // Add data to our Model
        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"));

        // Add our Model to our View
        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):

C#
public abstract class CVisitable
{
    public abstract void accept(CVisitor visitor);
}

Next, we need the Visitor class:

C#
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:

C#
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:

C#
public class CVisitor_TreeNode : CVisitor
{
    // Result
    public TreeNode TreeNode;

    // Visitor
    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:

C#
public class TN_Directory : TreeNode
{
    public TN_Directory(CDirectory dir)
    {
        Tag       = dir;
        Text      = dir.Name;
        ForeColor = Color.Green;

        // This is where all the magic happen!
        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()
    {
        ...

        // Add our Model to our View
        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).

License

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