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)
{
PropertyInfo propertyInfoForText = item.GetType().GetProperty(sPropertyForText);
if (null == propertyInfoForText)
{
return null;
}
TreeNode treeNode = new TreeNode(
propertyInfoForText.GetValue(item, null).ToString());
treeNode.Tag = item;
if (null != treeNodeParent)
{
treeNodeParent.Nodes.Add(treeNode);
}
IEnumerable enumerableObject = item as IEnumerable;
if (null != enumerableObject)
{
foreach (object itemInEnumerable in enumerableObject)
{
Add<TAttribute>(itemInEnumerable, treeNode, sPropertyForText);
}
}
PropertyInfo[] propertyInfos = item.GetType().GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
object[] attribs = propertyInfo.GetCustomAttributes(false);
foreach (TAttribute treeNodeAttribute in attribs)
{
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.