Click here to Skip to main content
15,881,578 members
Articles / Web Development / HTML

XTree

Rate me:
Please Sign up or sign in to vote.
4.41/5 (19 votes)
7 Jan 20066 min read 91.2K   771   78   5
A template driven tree

Introduction

This is a .NET 2.0 article (I wonder how long it will be before that becomes unnecessary to say).

Whenever I use a TreeView, I get frustrated with the amount of application specific coding that I have to do. I've been mulling about an XML-based template that defines the master tree:

  • defining node "types",
  • specifying the root node type,
  • given a node type, what are the allowable child nodes,
  • auto-generating a context menu from the text in the XML defined in the node type,
  • the icon file specified in the template for the node type,
  • specifying the required child nodes when a node is added,
  • automatically adding/removing nodes from the tree based on the XML attributes for the context menu items,
  • having the entire process work without the backing objects to provide application specific node implementation,
  • specifying whether a node can be edited or not,
  • serializing the tree,
  • allowing recursive node types, like folder-folder-folder...,
  • automatically instantiate the backing object for a node type.

In this first cut, I've managed to put together everything except the last bullet item. That will be discussed in a separate article, as it can get complicated.

Of course, with any general purpose solution, customization of the look and function becomes more difficult. I haven't explored where all the hooks need to go to make this class truly customizable. I'll be looking at that later. For now, I thought I'll publish this as is and get feedback from you on the kind of features this should have, etc. As with many things I publish, this is a work in progress! I'm looking at peterchen's generic tree to see how his work might be used to map concrete, application-specific objects that back the actual nodes and thus also separate the view from the model (not to mention, I probably ought to have a separate controller as well). As it stands right now, the model, view, and the controller are all integrated into a single class.

How It Works

The XTree class is derived from the TreeView.

Parsing the template

When the Initialize method is called with an XmlDocument template file, the object graph defining the tree template is instantiated. I'm using my MycroXaml parser to instantiate the object graph (the MycroXaml parser in this source code is much more elaborate than the one presented in the original article). Once the object graph is instantiated, the XTree instance initializes the root of the tree:

C#
public void Initialize(XmlDocument xdoc)
{
    nodeList = new Dictionary();
    ImageList = new ImageList();
    MycroParser mp = new MycroParser();/>    
    mp.Load(xdoc, null, null); 
    mp.NamespaceMap[""] = "Clifton.Windows.Forms.XmlTree, 
       Clifton.Windows.Forms"; 
    rootNode=(Node)mp.Process(); 
    nodeList[rootNode.Name] = rootNode; 
    BuildFlatNodeList(rootNode); 
    TreeNode tn=CreateNode(rootNode); Nodes.Add(tn);
}

Populating a Context Menu

Now, when you right-click on the root (or any node), the object inspects the popup items defined in the selected node's template definition and adds them to the context menu. Next, it inspects all the child nodes and adds the menu items that are found in the ParentPopupItems collection for that child node. Menu separators are placed between each grouping:

C#
public ContextMenu BuildContextMenu()
{
  ContextMenu cm = new ContextMenu();
  TreeNode tn = GetNodeAt(mousePos);
  Node n=(Node)tn.Tag;
  bool first = true;

  // Populate from this node's popup collection.
  if (n.PopupItems.Count != 0)
  {
    foreach (Popup popup in n.PopupItems) 
    {
      NodeMenuItem nmi = new NodeMenuItem(popup.Text, 
                                   tn, n, null, popup);
      nmi.Click += new EventHandler(OnContextItem);
      cm.MenuItems.Add(nmi);
    }
    first = false;
  }

  // For each child node, populate from the child's 
  // ParentPopupItems collection.
  foreach (Node child in n.Nodes)
  {
    Node refNode = child;

    // Resolve any referenced node.
    if (child.IsRef)
    {
      if (!nodeList.ContainsKey(child.RefName))
      {
        throw new ApplicationException(
             "referenced node does not exist.");
      }    

      refNode = nodeList[child.RefName];
    }

    if (refNode.ParentPopupItems.Count != 0)
    {
      if (!first)
      {
        cm.MenuItems.Add("-");
      }

      first = false;

      // Populate the items.
      foreach (Popup popup in refNode.ParentPopupItems)
      {
        NodeMenuItem nmi = new NodeMenuItem(popup.Text, 
                                  tn, n, refNode, popup);
        nmi.Click += new EventHandler(OnContextItem);
        cm.MenuItems.Add(nmi);
      }
    }
  }
  return cm;
}

You will note that if a node is a "reference", then it implements a recursive node and the referenced node is used for the template.

Adding and Removing Nodes

A default click handler is assigned. In the XML template, there are attributes indicating whether the menu item adds the node or removes it:

C#
private void OnContextItem(object sender, EventArgs e)
{
  NodeMenuItem nmi = (NodeMenuItem)sender;

  if (nmi.PopupInfo.IsAdd)
  {
    TreeNode tn = CreateNode(nmi.ChildNode);
    nmi.TreeNode.Nodes.Add(tn);
    tn.Parent.Expand();
    tn.TreeView.SelectedNode = tn;
  }
  else if (nmi.PopupInfo.IsRemove)
  {
    nmi.TreeNode.Remove();
  }
}

Now, of course, in a real application, this would be backed by real functionality. In my prototype though, it simply wants the node added or removed. The node being added is of the correct node type, and therefore its context menu is specific to it.

The XML Template

The template that defines the tree consists of Node tags, backed by the Node class. A node can have three different collections:

  1. ParentPopupItems - a collection of one or more popup tags that define the menu items for the parent node.
  2. PopupItems - a collection of one or more popup tags that define the menu items for the current node.
  3. Nodes - a collection of one or more child nodes.

The root node does not have a ParentPopupItems collection.

A Node tag can have the following attributes (backed by properties in the Node class):

  • Name - the name of the node
  • Text - the text displayed in the tree node
  • IsRequired - indicates that this node is always created
  • IconFilename - the icon filename for this node type
  • RefName - indicates that this node is actually a reference to a node defined higher up in the hierarchy. This allows for recursive, infinite, tree depth.
  • IsReadOnly - indicates whether the node text can be edited

A Popup tag, backed by the Popup class, can have the following attributes:

  • Text - the menu item text
  • IsAdd - true, if selecting this menu item, adds the associated node template
  • IsRemove - true, if selecting this menu item, removes the associated node instance

Given the XML supplied in the demo, the Node and Popup classes were 90% auto-generated using my XML To Class generator.

XML Serialization

The caveat to XML serialization is that the template node name is being used as the XML tag, so it has to comply with the XML requirements - no white spaces, etc. The question really is though, should the tree be responsible for serialization? The answer is "sort of". If we look at this from an MVC perspective, the tree embodies both the view and the model. What we really want is a tree in which the model, the node hierarchy, is separated from the view. However, since the TreeView does not accept a data source (ironically, the ASP.NET TreeView in .NET 2.0 does support a data source), we're stuck with implementing a proper view-model separation ourselves. Later. And yes, the filename is hard-coded as well - this is a concept piece, after all.

To serialize, the tree is traversed:

C#
public void Serialize(string fn)
{
  XmlDocument xdoc = new XmlDocument();
  XmlDeclaration xmlDeclaration = 
      xdoc.CreateXmlDeclaration("1.0", "utf-8", null);
  xdoc.InsertBefore(xmlDeclaration, xdoc.DocumentElement);
  XmlNode xnode = xdoc.CreateElement("XTree");
  xdoc.AppendChild(xnode);

  foreach (TreeNode tn in Nodes)
  {
    WriteNode(xdoc, xnode, tn); 
  }

  xdoc.Save("tree.xml");
}
 
protected void WriteNode(XmlDocument xdoc, 
                            XmlNode xnode, TreeNode tn)
{
  XmlNode xn = xdoc.CreateElement(((Node)tn.Tag).Name);
  xn.Attributes.Append(xdoc.CreateAttribute("Text"));
  xn.Attributes.Append(xdoc.CreateAttribute("IsExpanded"));
  xn.Attributes["Text"].Value = tn.Text;
  xn.Attributes["IsExpanded"].Value = 
                         tn.IsExpanded.ToString();
  xnode.AppendChild(xn);

  foreach (TreeNode child in tn.Nodes)
  {
    WriteNode(xdoc, xn, child);
  }
}

Deserialization is the opposite process. Read in the XML nodes and construct the tree:

C#
public void Deserialize(string fn)
{
  Nodes.Clear();
  XmlDocument xdoc = new XmlDocument();
  xdoc.Load("tree.xml");
  XmlNode node = xdoc.DocumentElement;
  ReadNode(xdoc, node, Nodes);
}

protected void ReadNode(XmlDocument xdoc, XmlNode node, 
                           TreeNodeCollection nodes)
{
  foreach (XmlNode xn in node.ChildNodes)
  {
    TreeNode tn = new TreeNode();
    tn.Text = xn.Attributes["Text"].Value;
    tn.Tag = nodeList[xn.Name];
    nodes.Add(tn);

    ReadNode(xdoc, xn, tn.Nodes);

    if (Convert.ToBoolean(xn.Attributes["IsExpanded"].Value))
    {
      tn.Expand();
    }
  }
}

A typical XML file (for example, the screenshot at the beginning of the article) looks like this:

XML
<?xml version="1.0" encoding="utf-8"?>
<XTree>
  <Solution Text="Solution '$Name: $'" IsExpanded="True">
    <Project Text="$Name: $" IsExpanded="True">
      <Properties Text="Properties" IsExpanded="False" />
      <References Text="References" IsExpanded="False" />
      <Folder Text="$Folder$" IsExpanded="False" />
    </Project>
    <Project Text="$Name: $" IsExpanded="True">
      <Properties Text="Properties" IsExpanded="False" />
      <References Text="References" IsExpanded="False" />
      <File Text="$File$" IsExpanded="False" />
      <File Text="$File$" IsExpanded="False" />
    </Project>
  </Solution>
</XTree>

The Demo

I have a simple DemoForm class that demonstrates its usage (I'm a minimalist when it comes to form classes):

C#
public DemoForm()
{
  Text = "General Tree Demo";
  gTree = new XTree();
  gTree.Dock = DockStyle.Left;
  gTree.Width = 200;
  Controls.Add(gTree);
  Size=new Size(300, 400);

  XmlDocument xdoc = new XmlDocument();
  xdoc.Load("idetree.xml");
  gTree.Initialize(xdoc);
}

A Sample XML Tree Template

The sample XML creates a simple solution tree similar to VS2005. I selected the icons from the common icons rather than using the VS2005 icons (don't want to get sued, you know!). So, given this tree template:

XML
<?xml version="1.0" encoding="utf-8"?>
<Node Name="Solution" Text="Solution '$Name:  $'" IsRequired="true" 
      IconFilename="solution.ico">
  <Nodes>
    <Node Name="Project" Text="$Name:  $" IsReadOnly="true" 
          IconFilename="project.ico">
      <ParentPopupItems>
        <Popup Text="Add New Project" IsAdd="true"/>
        <Popup Text="Add Existing Project" IsAdd="true"/>
      </ParentPopupItems>
      <PopupItems>
        <Popup Text="Delete Project" IsRemove="true"/>
        <Popup Text="Remove Project" IsRemove="true"/>
      </PopupItems>
      <Nodes>
        <Node Name="File" Text="$File$" IconFilename="file.ico">
          <ParentPopupItems>
            <Popup Text="Add New File" IsAdd="true"/>
            <Popup Text="Add Existing File" IsAdd="true"/>
            <Popup Text="Link To Existing File" IsAdd="true"/>
          </ParentPopupItems>
          <PopupItems>
            <Popup Text="Delete File" IsRemove="true"/>
            <Popup Text="Remove File" IsRemove="true"/>
            <Popup Text="Exclude File" IsRemove="true"/>
          </PopupItems>
        </Node>
        <Node Name="Folder" Text="$Folder$" IconFilename="folder.ico">
          <ParentPopupItems>
            <Popup Text="Add New Folder" IsAdd="true"/>
            <Popup Text="Add Existing Folder" IsAdd="true"/>
          </ParentPopupItems>
          <PopupItems Separator="true">
            <Popup Text="Delete Folder" IsRemove="true"/>
            <Popup Text="Remove Folder" IsRemove="true"/>
          </PopupItems>
          <Nodes>
            <Node Name="refFile" RefName="File"/>
            <Node Name="refFolder" RefName="Folder"/>
          </Nodes>
        </Node>
        <Node Name="Properties" Text="Properties" IsReadOnly="true" 
              IsRequired="true" IconFilename="properties.ico"/>
        <Node Name="References" Text="References" IsReadOnly="true" 
              IsRequired="true" IconFilename="references.ico">
          <Nodes>
            <Node Name="Assembly" Text="$Assembly$" IsReadOnly="true" 
                  IconFilename="reference.ico">
              <ParentPopupItems>
                <Popup Text="Add Reference" IsAdd="true"/>
              </ParentPopupItems>
              <PopupItems>
                <Popup Text="Remove Reference" IsRemove="true"/>
              </PopupItems>
            </Node>
          </Nodes>
        </Node>
      </Nodes>
    </Node>
  </Nodes>
</Node>

If you follow on in the XML, you'll see the following.

The root node is the solution:

and right clicking on it, I get:

If I add a project, the result is:

Notice how the properties and references nodes are automatically created, because the IsRequired attribute is set to true.

From here, I can add folders, files, remove files and folders, etc. And of course, I can add multiple projects and so forth:

Conclusion

This class isn't necessarily that useful by itself. But I wanted in this article to introduce the concept of defining a tree hierarchy using an XML template. I hope this gives you some food for thought. I'd like to hear what you think about this concept and what features you think it needs to have to become a useful class. I have my own ideas, but I'd like to hear from the community!

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
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralGreat idea...but what about localization... Pin
rittjc5-Feb-08 13:32
rittjc5-Feb-08 13:32 
GeneralObjectsId in Tag Pin
martintr6-Feb-06 12:03
martintr6-Feb-06 12:03 
Generalmore features Pin
f27-Jan-06 20:43
f27-Jan-06 20:43 
i always want to minimize the amount code change, which get the code help us to do these routine work. the traditional dialog won't allow us to skip this work.
until Mr. Matthew R. Miller, his article "http://www.codeproject.com/treectrl/coptiontree.asp[^]"

the original option tree list, when the hieracy get more than 5-6 levels, the user suffer from confusion and has to do many clicks in order to reach the parameter item. last year i've enchance the option tree list with tree control on left panel to reduce the number of clicks to reach parameter items. by doing these, i display only the folder node on the left and it is auto expanded(optional). user able to reach the item within 2-3 clicks. they are now happy at least for this moment.

the next feature we going to add in, is security levels for each item. just like our windows explorer. but we want to do more, we want to make those item invisible to those un-authories user.

but then, all these how are we going to make it happen without doing much code? does xml help in this case?

by the way, is there anyway to add a screen shot in here?

from,
-= aLbert =-


-- modified at 3:01 Sunday 8th January, 2006
GeneralFew points Pin
jmw7-Jan-06 15:25
jmw7-Jan-06 15:25 
GeneralExcellent Idea Pin
Clickok7-Jan-06 13:53
Clickok7-Jan-06 13:53 

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.