Generic TreeView






4.17/5 (7 votes)
Populate a TreeView from your object model
Introduction
This article describes how to populate a TreeView control in a generic fashion and with very little code. On the project that I'm writing I had a TreeView control that would populate itself from the object model used in the project. The problem that I had was that the custom TreeView control was tightly coupled via interfaces to the object model. I wanted a TreeView control that did not know about the object model that I was using so I could use it in different projects that had different object models.
Solution
The solution was to have a TreeView control use reflection to pick out information from the object model using attributes on the objects. The class ProjectItem
shown below, has three properties that have the TreeNodeAttribute
applied to them:
[Serializable]
public class ProjectItem : NamedObjectItem
{
public ProjectItem(string sName)
: base(sName)
{
Assemblies = new ContainerItem<AssemblyItem>("Assemblies");
Backgrounds = new ContainerItem<IBackgroundItem>("Backgrounds");
MaterialSetsItem = new ContainerItem<ContainerItem<MaterialItem>>("Material Sets");
}
[TreeNodeAttribute]
public ContainerItem<AssemblyItem> Assemblies { get; set; }
[TreeNodeAttribute]
public ContainerItem<IBackgroundItem> Backgrounds { get; set; }
[TreeNodeAttribute( Hide = true)]
public ContainerItem<ContainerItem<MaterialItem>> MaterialSetsItem { get; set; }
}
The TreeNodeAttribute
is a very simple class and is used to identify which properties should be retrieved from the object for the TreeView control:
public class TreeNodeAttribute : Attribute
{
}
I derived an AttributeTreeView
control from the standard TreeView control, and added three methods to process objects that should be added to the TreeView control. These methods are:
public void Populate<TAttribute>(object item, string sPropertyForText)
where TAttribute : Attribute
public void Populate<TAttribute>(object item,
TreeNode tnParent, string sPropertyForText)
where TAttribute : Attribute
private TreeNode Add<TAttribute>(object item,
TreeNode treeNodeParent, string sPropertyForText)
where TAttribute : Attribute
As you can see from the methods, we need to supply some information for the AttributeTreeView
control to use. We need to pass in the attribute (TreeNodeAttribute
) that we are using to identify which properties should be added to the tree. I also needed a way to extract the text for the TreeNode
. This is passed as a string
of the name of the property that will return the text. I could have relied on the ToString
method, but it is unreliable as this often gets used for other purposes, and I may want to use already defined properties that will return a “pretty” version of the text for the TreeNode
.
To add an object to the AttributeTreeView
control, you need to Populate
:
m_ProjectTree.Populate<TreeNodeAttribute>(projectItem, "Name");
In the above example I call Populate
, specifying the attribute to check, and the name of the property to retrieve the text for the TreeNode
, as well as the object that I want to display. There are two implementations of Populate
. The Populate
method shown below will add TreeNode
s to a TreeNode
parent. The other version of Populate
calls this method, but clears existing nodes first and does not require a TreeNode
parent.
public void Populate<TAttribute>(object item, TreeNode tnParent,
string sPropertyForText)
where TAttribute : Attribute
{
BeginUpdate();
TreeNode tn = Add<TAttribute>(item, tnParent, sPropertyForText);
if (null != tn)
{
Nodes.Add(tn);
}
EndUpdate();
}
The main work is implemented in the private
method Add
, as shown below:
private TreeNode Add<TAttribute>(object item,
TreeNode treeNodeParent, string sPropertyForText)
where TAttribute : Attribute
{
if (null != item)
{
// See if we can get the property sPropertyForText from the item
PropertyInfo propertyInfoForText = item.GetType().GetProperty(sPropertyForText);
// if we can't access the property? it might not be a valid object,
// or not a valid property so return.
if (null == propertyInfoForText)
{
return null;
}
// Create a new tree node, as we know that the object item has
// a valid property on it.
TreeNode treeNode = new TreeNode(
propertyInfoForText.GetValue(item, null).ToString());
// Store a reference to the actual object as the TreeNode's Tag property
treeNode.Tag = item;
// if there's a valid parent add the new treenode (tn) to the parent
if (null != treeNodeParent)
{
treeNodeParent.Nodes.Add(treeNode);
}
// Is item an array or container of objects?
// i.e. if it implements IEnumerable we can enumerate over
// the objects to see if they can be added to the tree.
IEnumerable enumerableObject = item as IEnumerable;
if (null != enumerableObject)
{
foreach (object itemInEnumerable in enumerableObject)
{
Add<TAttribute>(itemInEnumerable, treeNode, sPropertyForText);
}
}
// Get the item’s properties and see if
// there are any that have the attribute TreeNodeAttribute assigned to it
PropertyInfo[] propertyInfos = item.GetType().GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
// Check all attribs available on the property
object[] attribs = propertyInfo.GetCustomAttributes(false);
foreach (TAttribute treeNodeAttribute in attribs)
{
// Try and add the return value of the property to the tree,
// if the property returns null it will be caught at the
// beginning of this method.
Add<TAttribute>(propertyInfo.GetValue(item, null),
treeNode, sPropertyForText);
}
}
return treeNode;
}
return null;
}
Hopefully the comments describe how the method works. This is a very generic way of adding objects to a TreeView control. I plan to extend TreeNodeAttribute
to include properties that allow you to specify the image for the TreeNode
, if it’s a checkable item, etc. The only problem is that this will then couple the AttributeTreeView
control with TreeNodeAttribute
. The where
cause on the methods will change from...
public void Populate<TAttribute>(object item, TreeNode tnParent,
string sPropertyForText)
where TAttribute : Attribute
... to:
public void Populate<TAttribute>(object item, TreeNode tnParent,
string sPropertyForText)
where TAttribute : TreeNodeAttribute
Please feel free to pass on your comments or suggestions. The attached sample is a Visual Studio 2008 project, but the code will easily port to earlier versions.
History
- 30th January, 2008: Initial version