Windows TreeView with e.g. multi-line tree content

Some claim that you need a third party tool to have a TreeView with multi-line text (outside WPS).

But it is actually rather easy to do with a few additional steps with the standard TreeView.

Below is the code sample.

The whole point is using the .OwnerDrawText and calculate the size to display, and centring the text vertically. No rocket science here….

Obviously the logic can be used to display more complex stuff also, say if you have saved some interesting stuff in the Tag.
But here I have kept it simple, and use text only, and rely on simple rendering of the text with MeasureString for later print with DrawString. (No fancy RTF-like stuff, Should not be too complex to implement if needed though, see this)

It is reduced and substantially modified code based on the MS sample here. That code do the ‘magic’ on text saved in the .Tag object not the .Text itself. In this I ignore the .Tag e.g. for when used to hold some not displayed linked object.

This code has a know issue that the build in mouse-over ToolTip hoovering over a node too long to be fully displayed in the window is not handled, so the bubble is displayed in the wrong font and ignoring the LF 🙄

Not a catastrophe, but not completely satisfying either…

Unfortunately you will manually have to assure that the tree’s ItemHeight is large enough to contain any multi-line text, as well as any images in the tree.
If you try to adjust the ItemHeight inside the DrawNode sub it does not immediately throw an exception, but the app crashes severely (AccessViolationException) continuing after Form_Load

To my knowledge different branches of a TreeView tree can not have different (collapsed) heights BTW.


using System;
using System.Drawing;
using System.Windows.Forms;

namespace TreeTest {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        void treeView_DrawNode(object sender, DrawTreeNodeEventArgs e) {
            // Draw the background and node text for a selected node.
            e.DrawDefault = false;
            Rectangle eNodeBounds = NodeBounds(e.Node);
            if (eNodeBounds.X==0 && eNodeBounds.Y==0) return;

            e.Graphics.FillRectangle(SystemBrushes.Window, e.Node.Bounds); // clear any remains from system

            // Draw the background of the selected node. The NodeBounds method makes the highlight rectangle large enough for the text.
            e.Graphics.FillRectangle(((e.State & TreeNodeStates.Selected) != 0) ? SystemBrushes.Highlight : SystemBrushes.HighlightText
                                      , eNodeBounds);

            // Draw the node text using  the node font. If the node font has not been set, use the TreeView font.
            e.Graphics.DrawString(e.Node.Text, e.Node.NodeFont??e.Node.TreeView?.Font??((TreeView)sender).Font,
                                  ((e.State & TreeNodeStates.Selected) != 0) ? SystemBrushes.Window : SystemBrushes.WindowText, eNodeBounds);

            // If the node has focus, draw the focus rectangle.
            if ((e.State & TreeNodeStates.Focused) != 0) {
                using (Pen focusPen = new Pen(Color.Black)) {
                    focusPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                    e.Graphics.DrawRectangle(focusPen, new Rectangle(eNodeBounds.Location, Rectangle.Inflate(eNodeBounds, -1, -1).Size));
                }
            }
        }

        // Selects a node that is clicked on its label text.
        private void treeView_MouseDown(object sender, MouseEventArgs e) {
            TreeNode clickedNode = ((TreeView)sender).GetNodeAt(e.X, e.Y);
            if (NodeBounds(clickedNode).Contains(e.X, e.Y)) ((TreeView)sender).SelectedNode = clickedNode;
        }

        // Returns the bounds of the specified node label, Possibly multi line.
        private Rectangle NodeBounds(TreeNode node) {
            if (node?.TreeView!=null  && node?.Text!=null && (0<node.Bounds.Location.X || 0<node.Bounds.Location.Y)) {
                // Retrieve a Graphics object from the TreeView handle and use it to calculate the display size of the text.
                using (Graphics g = node.TreeView.CreateGraphics()) {
                    SizeF textSize = g.MeasureString(node.Text, node.NodeFont ?? node.TreeView.Font);
                    return Rectangle.Ceiling(new RectangleF(PointF.Add(node.Bounds.Location
                                                           , new SizeF(0, (node.TreeView.ItemHeight-textSize.Height)/2))
                                           , textSize)); //Centre Y
                }
            } else return node?.Bounds??new Rectangle(); //FallBack to the normal node bounds, and to empty 0,0.
        }

        private void Form1_Load(object sender, EventArgs e) {
            treeView1.DrawMode = TreeViewDrawMode.OwnerDrawText;
            treeView1.DrawNode += treeView_DrawNode; ;
            treeView1.MouseDown += treeView_MouseDown; ;
            treeView1.ItemHeight = 40;
            //treeView1.ImageList = new ImageList();
            //treeView1.ImageList.ImageSize = new System.Drawing.Size(32, 32);
            TreeNode root = new TreeNode("Root");
            treeView1.Nodes.Add(root);
            root.Nodes.Add(new TreeNode("Simple text"));
            root.Nodes.Add(new TreeNode("X"));
            root.Nodes.Add(new TreeNode("A text over\ntwo lines"));
            root.Nodes.Add(new TreeNode("Z"));
            root.Nodes.Add(new TreeNode("┌ 1  0 ┐ Identity matrix example\n"
                                       +"└ 0  1 ┘ Fixed pitch"){ NodeFont = new Font("Consolas", 10)});
            root.Nodes.Add(new TreeNode("        ┌ X ┐    ┌ 1 ┐    ┌  723 ┐\n"
                                      + "3DBox : | Y | in | 7 | to | 1431 |\n"
                                      + "        └ Z ┘    └ 5 ┘    └  361 ┘",new TreeNode[] { new TreeNode("blah blah") }
                                       ){ NodeFont = new Font("Consolas", 8) }
                           );
            root.Nodes.Add(new TreeNode("More text"));

            root.NodeFont=new Font(treeView1.Font.FontFamily,22f);
        }
    }
}

.