Introduction
This a custom tree control that tries to solve the problem of vertical display of hierarchical data.
Background
I use Vista, and although I don't like many things about it, I adore the control that in Windows Explorer that helps parse the folder structure of a disk.
When the user wants to select something from the hidden tree, a custom listbox appears like this:
As you can see, for the explorer implementation, the control represents folders or special folders like My Documents, each with its custom icon. Extra graphics are also used, which I tried to mimic, but due to my lack of artistic skills, I could not exactly copy it; so if anyone has any suggestions, I would be very interested.
Control Dissection
Inner Data Representation
The inner representation of the hierarchical data is a tree like collection of NodeBase
objects. NodeBase
is abstract, and these are its valid implementations:
Node
is used for data. It holds the DisplayedText
, the related Image
, and a Tag
property that holds the reference to the actual object for the node. This object is used to manipulate the activation event of the node. At this point, only one special node class is provided, the NodeSeperator
. Special node classes will never appear in the bar. Their place is only in the selectors which display the sub nodes of each node.
Visual Representation
The visual representation of the above data is accomplished by displaying a sequence of controls. Each control basically derives from NodeBaseControl
, which is abstract and has two separate implementations. NodeBaseControl
ultimately holds a NodeBase
object.
NodeLinksControl
is used only once in the bar, that is the tree, and it is the left most visible object always. It has an image which displays the image of the current active node, and can show a selection of various nodes, which may not be part of the hierarchical data, for example, a link.
NodeControl
is used to represent every other node. The root node is a NodeControl
. It displays a text and an image to show the selector. If there are no subnodes, then there is no need for a selector, so no image is displayed.
When many nodes are added, the horizontal tree hides like in the Vista's Explorer, closest to the root, and adds them to the NodeLinksControl
selection. It also changes the image to show the fact that the nodes are hidden.
At any single point, when no node is selected, just by clicking on it or selecting it from a selector, the node becomes the last visible node, gets activated, and any subnode is removed from the visual tree. The Selector
is a custom ListBox
that displays the subnodes of a node. A separator node cannot be selected or highlighted. When a selector is requested, the tree is in:
SelectionMode==true
until a node is selected, or the mouse is clicked outside a selector or a NodeBaseControl
. In order to accomplish this, the selector is added to the Controls
of the ParentForm
and brought to the front. It also captures the mouse and redirects the required messages.
NodeBaseControl Dissection
Each NodeBaseControl
is displayed by two side by side PartBase
controls. PartBase
is abstract, and its children are:
Each part effectively knows the other part, and basically has a State
that drives its appearance. Each of these part controls handles all the input functionality from the mouse, taking in regard the tree status or its other parts' State
s.
Using the Code
You can drag and drop the control into a form (in my demo, it is named horizontalTreeControl1
). Full design time support is not implemented because I do not have the time to create it and because I'm not interested in learning how, because I believe the future lies with WPF. In the demo project, a typed dataset is built to simulate a folder structure similar to the one found in the disk. To fill the LinksControl
, you use something like this:
this.horizontalTreeControl1.LinksNode.Suspend();
this.horizontalTreeControl1.LinksNode.AddNode("Link1",
Properties.Resources.Link1, null);
this.horizontalTreeControl1.LinksNode.AddSeperator();
this.horizontalTreeControl1.LinksNode.AddNode("Link2",
Properties.Resources.Link2, null);
Node n = new Node();
this.horizontalTreeControl1.LinksNode.Resume();
Suspend
and Resume
are used in order to suppress the Paint event. Each node has a:
- Displayed text
- Image
- An object reference that basically is used as the Tag property in Windows Forms controls
To implement the RootNode
of the bar, use something like this:
n = new Node();
n.DisplayText = ds.Folder[0].Name;
n.Tag = ds.Folder[0];
n.Image = Properties.Resources.Root;
this.horizontalTreeControl1.RootNode = n;
Now, there are two ways to add the subnodes in each node. You either use the AddNode
in each node like above for the LinksNode
, or capture the NodeActivated
event to dynamically add the nodes at the specified time. This event is called for a node when it is activated. This is very useful when there is no reason to add the entire data to the tree, or the data changes over a period of time. Capture the event like this:
this.horizontalTreeControl1.NodeActivated +=
new Sarafian.Framework.ClientSide.UI.Resources.HorizontalTree.
DelegateEnum.DelegateNodeActivated(horizontalTreeControl1_NodeActivated);
...and implement it like this:
void horizontalTreeControl1_NodeActivated(Node node)
{
if (node.Tag is DataRow)
{
node.ClearNodes();
DsTest.FolderRow row = (DsTest.FolderRow)node.Tag;
node.Suspend();
foreach (DsTest.FolderRow subRow in
row.GetChildRows(this.ds.Folder.ChildRelations[0]))
{
node.AddNode(subRow.Name, Properties.Resources.Folder, subRow);
}
node.Resume();
}
else
{
}
}
As you can see, AddNode
is again used. This event is also used to know when a node is activated. For example, if the node was a folder and you wanted its files. The Tag
property holds the actual data of the node, which can be any object.
Points of Interest
This control does not fully implement the path selector of Windows Vista Explorer. Another control with a TextBox
and an auto-fill which will be activated in front of the horizontal tree control will be needed. This control can display any type of hierarchical data.
At development time, many mistakes where made from my part that led to several refactoring attempts, so the code is not perfect. There are some files that have been added to the class library from other class libraries, I imported them here to make the solution simpler. These files are:
- Percent.cs
- Rectangle.cs
- Point.cs
More info is available at my post in my blog.
History