Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Tri-State TreeView Control for .NET 1.1

4.75/5 (10 votes)
14 Dec 2007CPOL3 min read 1   4.8K  
Tri-state checkboxes, user-defined state images, disable checkboxes per node...

Screenshot -

Introduction

The TreeView control as implemented by Microsoft in the .NET 1.1 Framework does support checkboxes, but those checkboxes allow only two states: checked or unchecked.

This control is built on top of the TreeView control, and provides some additional functionality that might be useful.

  • Tri-state checkboxes, checked / unchecked / indeterminate state.
  • TriStateTreeView supports custom images for all three states. When not using custom images, default tri-state images are provided. Custom images are configured the same way as the "ImageIndex" and the "SelectedImageIndex" properties, so state images can be added to the ImageList bound to the TreeView using the ImageList property.
  • Checkboxes can be switched off on a per-node basis.
  • The TreeView recognizes two different types of TreeNodes: Containers and Items. They behave differently when child nodes are checked / unchecked.

Background

Some of this code, mostly the wndproc hooks, comes from another sample found on this site. It was a VB.NET sample developed by Carlos J. Quintero dealing with the node fonts: http://www.codeproject.com/KB/cpp/CustomDrawTreeview.aspx. I translated that sample and added my own stuff. Thanks Carlos for sharing your code with us.

Using the code

The sources contain two classes: TriStateTreeView and TriStateTreeNode.

The TriStateTreeNode class exposes an internal property, NodeLineType, which is read by the TriStateTreeView control when it is about to draw the node. When a checkbox has been disabled for a certain node, a dotted line should be drawn to replace that checkbox, in the way the original TreeView would do it.

Enum NodeLineType

The property NodeLineType returns a value of type NodeLineType, which can have three different values:

  • None - No line needs to be drawn
  • Straight - Only a straight line has to be drawn where normally a checkbox would be drawn
  • WithChildren - A straight line must be drawn, and a connecting line to the node's children
C#
internal enum NodeLineType
{
    None,
    Straight,
    WithChildren
}

Class TriStateTreeView

The class TriStateTreeView has been derived from System.Windows.Forms.TreeView, and exposes the additional properties listed below:

  • UseCustomImages - Design or runtime editable. Determines if the TreeView draws the images configured in the ImageList (custom), or the default images.
  • CheckedImageIndex - Index of the image to display when the node is in the Checked state (UseCustomImages must be true).
  • UncheckedImageIndex - Index of the image to display when the node is in the Unchecked state (UseCustomImages must be true).
  • IndeterminateImageIndex - Index of the image to display when the node is in the Indeterminate state (UseCustomImages must be true).

The above four properties have all been placed in a single category in the property browser, named CheckState.

Class TriStateTreeNode

The class TriStateTreeNode has been derived from System.Windows.Forms.TreeNode, and exposes the extra functionality described below:

  • It hides the original Checked property, and returns true when its CheckState property is either Checked or Indeterminate. The property returns Unchecked when CheckState is Unchecked.
  • Additional read-only property CheckState which returns either Checked/Unchecked/Indeterminate depending on the current state.
  • Additional read-write property IsContainer which determines if a TreeNode behaves as a container or as an item.
  • Additional read-write property CheckboxVisible which determines if a node exposes a checkbox.
  • Internal property NodeLineType (see previous explanation).
  • Internal method SetCheckedState( CheckState value): Provides an interface for the TriStateTreeView class.

Sample code

C#
private void ConfigureTreeView()
{
    TriStateTreeNode folderNode = null;

    TriStateTreeNode rootNode = 
      new TriStateTreeNode( "Home - \"CheckboxVisible = false\"." );
    rootNode.CheckboxVisible = false;
    rootNode.IsContainer = true;

    for( int i = 1; i < 4; i++ )
    {
        folderNode = new TriStateTreeNode( string.Format( "Foldernode {0}," + 
                         " can show 3 states, as shown here.", i), 0, 1 );
        folderNode.IsContainer = true;
        rootNode.Nodes.Add( folderNode );
    }

    folderNode = new TriStateTreeNode("Foldernode 4, " + 
                     "cannot be checked, as shown here.", 0, 1);
    folderNode.IsContainer = true;
    folderNode.CheckboxVisible = false;

    rootNode.Nodes.Add(folderNode);

    TriStateTreeNode firstFolder = rootNode.FirstNode as TriStateTreeNode;
    for(int i = 1; i < 3; i++)
    {
        TriStateTreeNode itemNode = 
          new TriStateTreeNode( string.Format( "Item node {0}", i ), 2, 2 );
        firstFolder.Nodes.Add( itemNode );
    }

    TriStateTreeNode secondFolder = firstFolder.NextNode as TriStateTreeNode;
    for(int i = 1; i < 3; i++)
    {
        TriStateTreeNode itemNode = 
          new TriStateTreeNode( string.Format( "Item node {0}", i ), 2, 2);
        secondFolder.Nodes.Add( itemNode );
    }

    TriStateTreeNode thirdFolder = secondFolder.NextNode as TriStateTreeNode;
    for(int i = 1; i < 3; i++)
    {
        TriStateTreeNode itemNode = 
          new TriStateTreeNode( string.Format( "Item node {0}", i ), 2, 2 );
        thirdFolder.Nodes.Add( itemNode );
    }

    TriStateTreeNode fourthFolder = folderNode;
    fourthFolder.CheckboxVisible = false;
    for(int i = 1; i < 3; i++)
    {
        TriStateTreeNode itemNode = 
          new TriStateTreeNode( string.Format( 
          "Item node {0} - no checkboxes", i ), 2, 2 );
        itemNode.CheckboxVisible = false;
        fourthFolder.Nodes.Add( itemNode );
    }

    this.triStateTreeView1.SuspendLayout();
    this.triStateTreeView1.Nodes.Add( rootNode );
    this.triStateTreeView1.ResumeLayout();

    secondFolder.FirstNode.Checked = true;
    thirdFolder.Checked = true;
}

Points of interest

There are still a couple of issues I want to fix, feedback about it will be appreciated.

Microsoft has a TreeNodeCollectionEditor which will let you construct a tree at design time. Normally, I think this editor is not likely to be used since most treeviews will show dynamic data which will almost never be pre-configured. However... the editor doesn't work with this treeview because it adds TreeNodes instead of TriStateTreeNodes, which disables the entire tri-state concept. I haven't quite figured out what to do about it.

I guess a solution to this problem should involve hiding the Nodes property from the TreeView control by adding our own Nodes property which is a TreeNodeCollection like collection class that stores TriStateTreeNodes. That, together with a custom TreeNodeCollectionEditor, should just about take care of the problem.

History

  • 14 December 2007 - First version.

License

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