Click here to Skip to main content
Click here to Skip to main content

Tri-State TreeView Control

, 2 Nov 2005 CPL
Rate this:
Please Sign up or sign in to vote.
A TreeView control with tri-state checkboxes.

Introduction

Microsoft provides a TreeView control in the .NET framework, but as usual it doesn't expose all the functionality that the underlying common control provides. It has the possibility to display check-boxes but they can't be tri-state check boxes.

This control adds tri-state check boxes and the necessary handling methods to check/uncheck all sub-items if the user clicks on a node, as well as displays a grey-checked check box for the parent if the siblings have differing check/uncheck states.

Background

A tri-state TreeView is a tree view control that displays check boxes next to the tree nodes. If you click on a check box, it checks (or unchecks) the check box and all sub-nodes.

If the nodes on the same level as the clicked node have differing check states the parent node's check box is grey-checked as well as its parent's and so on.

If you click on a grey-checked check box it unchecks that node and all its subnodes.

How to use the control

Add TriStateTreeView.cs to your project and then simply drop the TriStateTreeView control on a form and use it as a regular TreeView control. The only difference is when you want to access the checked state of a node. Instead of using the treeNode.Checked property you call triStateTreeView.GetChecked(treeNode) resp. triStateTreeView.SetChecked(treeNode, checkState). Instead of a bool these methods take a CheckState.

For example, the following code snippet checks the state of all the top level nodes:

foreach (TreeNode node in m_treeView.Nodes)
{
  if (m_treeView.GetChecked(node) == 
         TriStateTreeView.CheckState.Checked)
    DoFoo(node);
}

How does it work?

The control derives from TreeView and sets the ImageList and SelectedImageList properties for the base control. It overrides the OnClick and OnKeyDown methods.

In the OnKeyDown method, we check if the pressed key is a space, and if it is we change the state of the selected node.

The OnClick method is a little trickier. We have to use the TVM_HITTEST Win32 API message to determine if the user clicked on the icon or on the item. If the user clicked on the icon we change the state of the selected node.

Limitations

This control doesn't support displaying both check boxes and icons. If you need this functionality you have to call additional Win32 API methods. The common control tree view control supports having multiple image lists, but this functionality isn't exposed in .NET.

Remarks

The control makes use of the Skybound.VisualStyle assembly from www.skybound.ca. By commenting out the lines that reference that assembly it will easily work without it. Another way of making the control aware of visual styles is described here.

The demo project consists of an assembly that contains the TriStateTreeView, a demo project and a NUnit test assembly with some tests for the control.

History

  • 26 March 2004 - First version.
  • 28 October 2005 - Added support for visual styles and Before/AfterCheck.

License

This article, along with any associated source code and files, is licensed under The Common Public License Version 1.0 (CPL)

Share

About the Author

Ebse
Software Developer (Senior)
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionHow to add multiple images on same tree node? Pinmembersushantya6-Mar-07 19:39 
GeneralTri-State TreeView in Pocket Project PinmemberSilas Pereira27-Feb-07 1:08 
QuestionHow do I get what is checked? PinmemberJoppeG26-Feb-07 21:33 
AnswerArrayList Bug? PinmemberJoppeG28-Feb-07 0:55 
GeneralSolved problem PinmemberJoppeG28-Feb-07 4:07 
GeneralRe: Solved problem Pinmemberjonmach18-Nov-07 7:38 
QuestionRight-To-Left Problem Pinmembermorali2k2-Sep-06 2:58 
GeneralSimplified tree-state TreeView PinmemberLaurent Muller22-Aug-06 4:35 
Hello
 
I used a more simple code to mimics a three-state treeview. I use the StateImageList property instead. See the code below:
 

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
 
namespace Sample
{
public class ThreeStateTreeView : TreeView
{
private const int STATE_UNCHECKED = 0; //unchecked state
private const int STATE_CHECKED = 1; //checked state
private const int STATE_MIXED = 2; //mixed state (indeterminate)
private ImageList _InternalStateImageList; //state image list
 
//create a new ThreeStateTreeView
public ThreeStateTreeView()
: base()
{
}
 
//initialize all nodes state image
public void InitializeNodesState(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
node.StateImageIndex = 0;
if (node.Nodes.Count != 0)
{
InitializeNodesState(node.Nodes);
}
}
}
 
//update children state image with the parent value
public void UpdateChildren(TreeNode parent)
{
int state = parent.StateImageIndex;
foreach (TreeNode node in parent.Nodes)
{
node.StateImageIndex = state;
if (node.Nodes.Count != 0)
{
UpdateChildren(node);
}
}
}
 
//update parent state image base on the children state
public void UpdateParent(TreeNode child)
{
TreeNode parent = child.Parent;
 
if (parent == null)
{
return;
}
 
if (child.StateImageIndex == STATE_MIXED)
{
parent.StateImageIndex = STATE_MIXED;
}
else if (IsChildrenChecked(parent))
{
parent.StateImageIndex = STATE_CHECKED;
}
else if (IsChildrenUnchecked(parent))
{
parent.StateImageIndex = STATE_UNCHECKED;
}
else
{
parent.StateImageIndex = STATE_MIXED;
}
UpdateParent(parent);
}
 
//returns a value indicating if all children are checked
public static bool IsChildrenChecked(TreeNode parent)
{
return IsAllChildrenSame(parent, STATE_CHECKED);
}
 
//returns a value indicating if all children are unchecked
public static bool IsChildrenUnchecked(TreeNode parent)
{
return IsAllChildrenSame(parent, STATE_UNCHECKED);
}
 
//returns a value indicating if all children are in the same state
public static bool IsAllChildrenSame(TreeNode parent, int state)
{
foreach (TreeNode node in parent.Nodes)
{
if (node.StateImageIndex != state)
{
return false;
}
if (node.Nodes.Count != 0 && !IsAllChildrenSame(node, state))
{
return false;
}
 
}
return true;
}
 
//build the checked, unchecked and indeterminate images
private static Image GetStateImage(CheckBoxState state, Size imageSize)
{
Bitmap bmp = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(bmp))
{
Point pt = new Point((16 - imageSize.Width) / 2, (16 - imageSize.Height) / 2);
CheckBoxRenderer.DrawCheckBox(g, pt, state);
}
return bmp;
}
 

protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (_InternalStateImageList == null)
{
_InternalStateImageList = new ImageList();
using (Graphics g = base.CreateGraphics())
{
Size glyphSize = CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.UncheckedNormal);
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.UncheckedNormal, glyphSize));
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.CheckedNormal, glyphSize));
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.MixedNormal, glyphSize));
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.UncheckedDisabled, glyphSize));
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.CheckedDisabled, glyphSize));
_InternalStateImageList.Images.Add(GetStateImage(CheckBoxState.MixedDisabled, glyphSize));
}
}
base.StateImageList = _InternalStateImageList;
 
InitializeNodesState(base.Nodes);
}
 
//check if user click on the state image
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (e.Button == MouseButtons.Left)
{
TreeViewHitTestInfo info = base.HitTest(e.Location);
if (info.Node != null && info.Location == TreeViewHitTestLocations.StateImage)
{
TreeNode node = info.Node;
switch (node.StateImageIndex)
{
case STATE_UNCHECKED:
case STATE_MIXED:
node.StateImageIndex = STATE_CHECKED;
break;
case STATE_CHECKED:
node.StateImageIndex = STATE_UNCHECKED;
break;
}
UpdateChildren(node);
UpdateParent(node);
}
}
}
 
//check if user press the space key
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Space)
{
if (base.SelectedNode != null)
{
TreeNode node = base.SelectedNode;
switch (node.StateImageIndex)
{
case STATE_UNCHECKED:
case STATE_MIXED:
node.StateImageIndex = STATE_CHECKED;
break;
case STATE_CHECKED:
node.StateImageIndex = STATE_UNCHECKED;
break;
}
UpdateChildren(node);
UpdateParent(node);
}
}
}
 
//swap between enabled and disabled images
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
 

for (int i = 0; i < 3; i++)
{
Image img = _InternalStateImageList.Images[0];
_InternalStateImageList.Images.RemoveAt(0);
_InternalStateImageList.Images.Add(img);
 
}
}
}
}


GeneralRe: Simplified tree-state TreeView PinmemberUncleRedz1-Nov-06 4:18 
GeneralRe: Simplified tree-state TreeView Pinmemberbarclay19-Dec-06 11:44 
GeneralRe: Simplified tree-state TreeView Pinmemberhamlet_13-Feb-07 0:38 
GeneralRe: Simplified tree-state TreeView PinmemberMartin Welker4-Jun-07 12:17 
GeneralRe: Simplified tree-state TreeView Pinmembercwalex24-Jul-07 11:26 
GeneralRe: Simplified tree-state TreeView PinmemberMAEI28-Sep-07 8:25 
GeneralRe: Simplified tree-state TreeView PinmemberLaurent Muller1-Oct-07 4:08 
GeneralRe: Simplified tree-state TreeView (do not set Checkboxes to True) PinmemberDial@Technet30-Oct-07 4:30 
GeneralRe: Simplified tree-state TreeView Pinmemberjcachat16-Nov-07 6:55 
GeneralRe: Simplified tree-state TreeView Pinmemberluisxvarg11-Jan-10 23:38 
GeneralSuggestion about using StateImageList Pinmemberlukasz_sw21-Feb-06 12:06 
GeneralImplementing an icon Pinmemberdevnet24730-Jan-06 19:54 
GeneralHelp to use this control in a form! Pinmembersrf23-Jan-06 20:39 
GeneralRe: Help to use this control in a form! PinmemberEbse24-Jan-06 3:16 
GeneralRe: Help to use this control in a form! Pinmembersrf25-Jan-06 4:41 
JokeSuggestion about CheckNodeByTag method. Pinmemberlovvver17-Jan-06 17:10 
GeneralRe: Suggestion about CheckNodeByTag method. PinmemberEbse18-Jan-06 3:42 

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
Web04 | 2.8.141015.1 | Last Updated 2 Nov 2005
Article Copyright 2004 by Ebse
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid