Tree View
TreeView is a control for navigating hierarchical objects. It comes in two forms TreeView
and TreeView<T>
.
Using TreeView
The basic non generic TreeView class is populated by ITreeNode
objects. The simplest tree you can make would look something like:
var tree = new TreeView()
{
X = 0,
Y = 0,
Width = 40,
Height = 20
};
var root1 = new TreeNode("Root1");
root1.Children.Add(new TreeNode("Child1.1"));
root1.Children.Add(new TreeNode("Child1.2"));
var root2 = new TreeNode("Root2");
root2.Children.Add(new TreeNode("Child2.1"));
root2.Children.Add(new TreeNode("Child2.2"));
tree.AddObject(root1);
tree.AddObject(root2);
Having to create a bunch of TreeNode objects can be a pain especially if you already have your own objects e.g. House
, Room
etc. There are two ways to use your own classes without having to create nodes manually. Firstly you can implement the ITreeNode
interface:
// Your data class
private class House : TreeNode {
// Your properties
public string Address {get;set;}
public List<Room> Rooms {get;set;}
// ITreeNode member:
public override IList<ITreeNode> Children => Rooms.Cast<ITreeNode>().ToList();
public override string Text { get => Address; set => Address = value; }
}
// Your other data class
private class Room : TreeNode{
public string Name {get;set;}
public override string Text{get=>Name;set{Name=value;}}
}
After implementing the interface you can add your objects directly to the tree
var myHouse = new House()
{
Address = "23 Nowhere Street",
Rooms = new List<Room>{
new Room(){Name = "Ballroom"},
new Room(){Name = "Bedroom 1"},
new Room(){Name = "Bedroom 2"}
}
};
var tree = new TreeView()
{
X = 0,
Y = 0,
Width = 40,
Height = 20
};
tree.AddObject(myHouse);
Alternatively you can simply tell the tree how the objects relate to one another by implementing ITreeBuilder<T>
. This is a good option if you don't have control of the data objects you are working with.
TreeView<T>
The generic Treeview<T>
allows you to store any object hierarchy where nodes implement Type T. For example if you are working with DirectoryInfo
and FileInfo
objects then you could create a TreeView<FileSystemInfo>
. If you don't have a shared interface/base class for all nodes you can still declare a TreeView<object>
.
In order to use TreeView<T>
you need to tell the tree how objects relate to one another (who are children of who). To do this you must provide an ITreeBuilder<T>
.
Implementing ITreeBuilder<T>
Consider a simple data model that already exists in your program:
private abstract class GameObject
{
}
private class Army : GameObject
{
public string Designation {get;set;}
public List<Unit> Units {get;set;}
public override string ToString ()
{
return Designation;
}
}
private class Unit : GameObject
{
public string Name {get;set;}
public override string ToString ()
{
return Name;
}
}
An ITreeBuilder<T>
for these classes might look like:
private class GameObjectTreeBuilder : ITreeBuilder<GameObject> {
public bool SupportsCanExpand => true;
public bool CanExpand (GameObject model)
{
return model is Army;
}
public IEnumerable<GameObject> GetChildren (GameObject model)
{
if(model is Army a)
return a.Units;
return Enumerable.Empty<GameObject>();
}
}
To use the builder in a tree you would use:
var army1 = new Army()
{
Designation = "3rd Infantry",
Units = new List<Unit>{
new Unit(){Name = "Orc"},
new Unit(){Name = "Troll"},
new Unit(){Name = "Goblin"},
}
};
var tree = new TreeView<GameObject>()
{
X = 0,
Y = 0,
Width = 40,
Height = 20,
TreeBuilder = new GameObjectTreeBuilder()
};
tree.AddObject(army1);
Alternatively you can use DelegateTreeBuilder<T>
instead of implementing your own ITreeBuilder<T>
. For example:
tree.TreeBuilder = new DelegateTreeBuilder<GameObject>(
(o)=>o is Army a ? a.Units
: Enumerable.Empty<GameObject>());
Node Text and ToString
The default behavior of TreeView is to use the ToString
method on the objects for rendering. You can customise this by changing the AspectGetter
. For example:
treeViewFiles.AspectGetter = (f)=>f.FullName;