Draw trees vertically or horizontally in C#

Datetime:2016-08-22 22:10:01          Topic: C#           Share

The example Handle generic TreeNode mouse events in C# shows how to build a generic node class that can draw trees with nodes that contain just about anything. In that example, each each node is centered over its subtree.

This example modifies that one to let you draw trees 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<T> 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 spot’s 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.)

When the program draws trees, it uses two steps. First it draws links between nodes and then it draws 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<T> 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>T>.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<T> 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 the 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.





About List