BLOG.CSHARPHELPER.COM: Make a generic TreeNode class that can draw trees oriented vertically or horizontally in C#
Make a generic TreeNode class that can draw trees oriented vertically or horizontally in C#
The example Handle mouse events for a generic TreeNode class in C# shows how to build a generic node class that can draw just about anything in a tree where each node is centered over its subtree. This example modifies that one to let you draw the tree oriented vertically much as a TreeView control does.
The main differences between this program and the previous version are in the ways the node arranges and draws itself. The following code shows how a node arranges itself if its orientation is vertical.
// Arrange the subtree vertically.
public void ArrangeVertically(Graphics gr, float xmin, ref float ymin)
{
// See how big this node is.
SizeF my_size = Data.GetSize(gr, MyFont);
my_size.Width += 3 * SpotRadius;
// Set the position of this node's spot.
SpotCenter = new PointF(
xmin + SpotRadius,
ymin + (my_size.Height - 2 * SpotRadius) / 2);
// Set the position of this node's data.
DataCenter = new PointF(
SpotCenter.X + SpotRadius + my_size.Width / 2,
SpotCenter.Y);
// Allow vertical room for this node.
ymin += my_size.Height + VOffset;
// Recursively arrange our children.
foreach (TreeNode child in Children)
{
// Arrange this child's subtree.
child.ArrangeVertically(gr, xmin + Indent, ref ymin);
}
}
The code calls the node's Data object's GetSize method to see how much room the data needs to draw. It then uses that size's height to help position the spot drawn to the left of the item in the tree. The spot's X position is simply the node's minimum X position plus the spot's radius. The spots Y position is adjusted vertically so it is centered in the area needed by the data.
The code then sets the data's center. That position has the same Y position as the spot and is moved to the right by the spot's radius plus half of the data's drawn width.
The code then updates the allowed minimum Y value ymin and recursively calls the child nodes' ArrangeVertically methods to arrange the node's children.
If you compare this to the code used by the previous example to arrange the tree horizontally, you'll find that this is much simpler. (That code is also included in this example's ArrangeHorizontally method.)
The program draws a tree in two steps, first drawing links between nodes and then drawing the nodes themselves. The following code shows how the TreeNode class draws links for a vertically oriented tree.
// Draw the links for the subtree rooted at this node.
private void DrawSubtreeLinksVertical(Graphics gr)
{
foreach (TreeNode child in Children)
{
// Draw the link between this node this child.
gr.DrawLine(MyPen, SpotCenter.X, SpotCenter.Y, SpotCenter.X, child.SpotCenter.Y);
gr.DrawLine(MyPen, SpotCenter.X, child.SpotCenter.Y, child.SpotCenter.X, child.SpotCenter.Y);
// Recursively make the child draw its subtree nodes.
child.DrawSubtreeLinksVertical(gr);
}
}
For each of a node's children, this code draws a vertical line from the node's spot to a position to the left of the child's spot. It then draws a line from that position to the child's spot. The code finishes by recursively calling the child's DrawSubtreeLinksVertical method to draw the links for the child's subtree.
The following code shows how the program draws the nodes themselves.
// Draw the nodes for the subtree rooted at this node.
private void DrawSubtreeNodes(Graphics gr)
{
// Draw this node.
Data.Draw(DataCenter.X, DataCenter.Y, gr, MyPen, BgBrush, FontBrush, MyFont);
// If oriented vertically, draw the node's spot.
if (Orientation == TreeNode.Orientations.Vertical)
{
RectangleF rect = new RectangleF(
SpotCenter.X - SpotRadius, SpotCenter.Y - SpotRadius,
2 * SpotRadius, 2 * SpotRadius);
if (Children.Count > 0)
{
gr.FillEllipse(Brushes.LightBlue, rect);
}
else
{
gr.FillEllipse(Brushes.Orange, rect);
}
gr.DrawEllipse(MyPen, rect);
}
// Recursively make the child draw its subtree nodes.
foreach (TreeNode child in Children)
{
child.DrawSubtreeNodes(gr);
}
}
The code first calls the node's Data object's Draw method to make the object draw itself. Then if the node's orientation is vertical, the code draws the spot to the left of the node. It fills the spots of leaf nodes with orange and those of non-leaf nodes with light blue.
The method finishes by recursively calling itself to draw the node's subtree.
10/26/2011 10:11 AMRod Stephens wrote:
I fixed this. When you add a new child node, its orientation was defaulting to horizontal even if the tree was displayed vertically.
(Really the program should be re-written so it has a Tree class and it should have the Orientation property instead of the nodes. But that would take longer. Perhaps I'll get to it later.) Reply to this
Grate one thank you!)
Reply to this
Found.. a bug... about color dots and new node items... http://imgur.com/ARvvw
Reply to this
I fixed this. When you add a new child node, its orientation was defaulting to horizontal even if the tree was displayed vertically.
(Really the program should be re-written so it has a Tree class and it should have the Orientation property instead of the nodes. But that would take longer. Perhaps I'll get to it later.)
Reply to this