Click here to Skip to main content
Click here to Skip to main content
Go to top

Using treenodes with and without images in a TreeView

, 3 May 2006
Rate this:
Please Sign up or sign in to vote.
Ownerdrawn treeview draws the missing treelines.

Sample Image

Introduction

The TreeView allows either to show images for all nodes, or none at all. For special requirements, you might like using images only for some nodes. Using a dummy image or specifying an invalid image index, you can display a node without image. However, the blank space will look discomforting if tree lines are used. A possible remedy could use two transparent bitmaps, displaying the missing tree lines in a node's expanded and collapsed state. This works fine for varying background colors, but only for a given set of image size, ItemHeight, and Indent. The proposed solution uses an owner-drawn TreeView (.NET 2.0) to draw the missing tree lines.

Background

Deriving from TreeView (surprise, surprise), in our constructor, we immediately fix a bug with the LineColor property. As the default value, it returns Color.Black, which is obviously not true. If the LineColor was not previously specified, the underlying Win32 control returns -1 (default color). The .NET wrapper fails to interpret this as SystemColors.GrayText and translates it to Color.Black.

public class ocTreeview : TreeView
{
    public ocTreeView() : base()
    {
        base.LineColor = SystemColors.GrayText;
    }
}

TreeNodes with image indices of NOIMAGE value will be drawn with the missing tree lines.

public const int NOIMAGE = -1;

If the TreeView.DrawMode property is set to TreeViewDrawMode.OwnerDrawText|OwnerDrawAll, the DrawNode event is raised whenever a TreeNode needs painting (actually, frequent unnecessary repainting occurs). Unfortunately, the default painting is done after the DrawNode event, so erasing a part of the default drawing is not possible. Thus, the image of a imageless node must either not be drawn at all, or at least be blank and transparent. Using the custom-draw capability of the underlying Win32 control allows a more fine-grained control of painting, but involves considerably more coding.

protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
    // we only do additional drawing
    e.DrawDefault = true;

    base.OnDrawNode(e);
    
    // ...

We do additional drawing if tree lines are enabled, images are present, and the node should have no image.

if (base.ShowLines && base.ImageList != null
    && e.Node.ImageIndex == NOIMAGE)
{
    const int SPACE_IL = 3;  // space between Image and Label
    
    // Image size
    int imgW = base.ImageList.ImageSize.Width;
    int imgH = base.ImageList.ImageSize.Height;

    // Image center
    int xPos = e.Node.Bounds.Left - SPACE_IL - imgW / 2;
    int yPos = (e.Node.Bounds.Top + e.Node.Bounds.Bottom) / 2;

    // Image rect
    Rectangle imgRect = new Rectangle(xPos, yPos, 0, 0);
    imgRect.Inflate(imgW / 2, imgH / 2);
    
    // ...

Walking the geometry of the node is done best by calculating the location of the center of the various parts (present or not), and then inflating their rectangles (if present), working backwards from the left of the label to the plus-minus button.

With the image rectangle determined, here is the fun part: drawing the dotted lines. Hundreds of messages "Help: How do I draw a real dotted line?" with dozens of failing answers decompose to bits in pre-.NET group archives. Jean-Edouard Lachand-Robert provided the single solution (C++), using a pattern brush (yes, SetPixel is also a way to go). With .NET, this has become strikingly easy, the Pen class allows 1 pixel on/off dotted lines.

using (Pen p = new Pen(base.LineColor, 1))
{
    p.DashStyle = DashStyle.Dot;

    // account uneven Indent for both lines
    p.DashOffset = base.Indent % 2;

    // Horizontal treeline across width of image
    // account uneven half of delta ItemHeight & ImageHeight
    int yHor = yPos + ((base.ItemHeight - imgRect.Height) / 2) % 2;
    e.Graphics.DrawLine(p, imgRect.Left, yHor, imgRect.Right, yHor);
    
    // ...

Whether the line(s) must start with a dot or a space depends on the Indent value. Less obvious is the shifting of the y position by 1 pixel for varying ItemHeight and ImageHeight values. If root lines are disabled, root nodes need special treatment, which is not shown here. At this stage, the missing tree line is provided for collapsed nodes. Unless the TreeView uses the Checkboxes style, we must draw an additional vertical tree line for expanded nodes.

if (!base.CheckBoxes && e.Node.IsExpanded)
{
    // Vertical treeline , offspring from
    // NodeImage center to e.Node.Bounds.Bottom
    // yStartPos: account uneven Indent
    // and uneven half of delta ItemHeight & ImageHeight
    int yVer = yHor + (int)p.DashOffset;
    e.Graphics.DrawLine(p, xPos, yVer, xPos, e.Node.Bounds.Bottom);
}

Finally, we must ensure that a collapsing node is redrawn, as no DrawNode event is raised for this occasion.

protected override void OnAfterCollapse(TreeViewEventArgs e)
{
    base.OnAfterCollapse(e);

    if (!base.CheckBoxes && base.ImageList != null && 
         e.Node.ImageIndex == NOIMAGE)
    {
        base.Invalidate(e.Node.Bounds);
    }
}

Using the code

Use it like the standard TreeView, or paste the two methods into your own control. For TreeNodes without images, specify the NOIMAGE value for both the ImageIndex and SelectedImageIndex properties. Don't set their ImageKey, SelectedImageKey properties. For the normal nodes, you are free to use indices or keys.

Here comes the ugly part: the WinForms team in their loving care provided identical properties as members of the TreeView class itself, serving as default properties for nodes having unspecified (or invalid!) images. In order to prevent that imageless nodes become impregnated again, you must set these properties to a greater or equal value of the current number of images in the Imagelist. This must be redone after any change in the image count or an eventual re-assigning of the TreeView.Imagelist. Neither TreeView.ImageIndex nor TreeView.Imagelist can be overridden, thus this has to be the application's responsibility.

private void ensureDefaultImageIndex(TreeView tree)
{
    if (tree.ImageList != null)
    {
        tree.ImageIndex = tree.ImageList.Images.Count;
        tree.SelectedImageIndex = tree.ImageList.Images.Count;
    }
}

As an alternative solution, add a blank, transparent image to the Imagelist, refactor the NOIMAGE constant as a property, and set it to the actual index of the dummy image.

Points of Interest

This was coded against the Comctl6 version on WinXP systems. Version 5 (Win98/Win2K) draws a little different, like a 2 pixel offset of the first root node for varying ItemHeight and ImageHeight values. Since I currently do not own a Win2K/.NET installation, I cannot test this, and therefore be interested in your observations and improvements.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

OrlandoCurioso

Germany Germany
No Biography provided

Comments and Discussions

 
GeneralExample from ms Pinmemberziade21-Aug-09 9:06 
QuestionHow to show more bitmaps ? PinmemberVaclavPe31-Jul-08 3:36 
AnswerRe: How to show more bitmaps ? PinmemberOrlandoCurioso31-Jul-08 23:51 
GeneralControl the plusminus displaying on th treeview Pinmemberlilo23200723-Oct-07 1:42 
Questionhow to add background image for the whole treeview Pinmemberkumar_avin24-Jul-07 19:59 
GeneralTreeView refreshing too much - flickering Pinmemberkennym_dk9-May-07 4:07 
AnswerRe: Kudos PinmemberOrlandoCurioso9-Jun-07 13:22 
QuestionIs there any way to stop having a massive gap if there is no image to display? Pinmembermartinsli25-Sep-06 5:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 3 May 2006
Article Copyright 2006 by OrlandoCurioso
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid