Click here to Skip to main content
Licence 
First Posted 27 Apr 2004
Views 121,778
Bookmarked 37 times

TriStateTreeview in VB.NET

By | 19 Jan 2006 | Article
A VB.NET implementation of a treeview with 3-state checkboxes using state image list

Introduction

The TreeView with checkboxes implemented in the .NET Framework (property TreeView.CheckBoxes = True) allows only 2 states (TreeNode.Checked = True or False). The implementation presented here:

  • Allows to use the state image list that is not directly available in the .NET Framework implementation.

  • Allows 3 states (4 including the state none) for a node: checked, unchecked, indeterminate.

  • Implements the logic to set the proper state of parents and children when the user changes the state of a node: it selects all children when the parent is selected, and set the state of the parent(s) to checked, unchecked or indeterminate depending on the number of children selected (all, none, etc.).

I have seen other TriState implementations in the C# section but they use the image list, not the state image list, for checkboxes and therefore doesn't allow to have icons and checkboxes for nodes at the same time.

Using the code

I have derived a TriStateTreeView class from the base TreeView class. This new class needs an additional state image list with icons for the 4 states: none, unchecked, checked, indeterminate (in this order).

The state image list is passed in the constructor:

  m_ctlTriStateTreeView = New TriStateTreeView(Me.StateImageList)

A new enum type is provided to specify the state of a node:

Friend Enum CheckBoxState
   None = 0
   Unchecked = 1
   Checked = 2
   Indeterminate = 3
End Enum

To add nodes to the treeview, you can use the AddTreeNode helper function of the TriStateTreeView class:


 objTreeNodeRoot = m_ctlTriStateTreeView.AddTreeNode( _
    m_ctlTriStateTreeView.Nodes, "My Computer", _
    IMG_COMPUTER, CheckBoxState.None)

To get the state of a node you use the GetTreeNodeState function of the TriStateTreeView class:


 Friend Function GetTreeNodeState(ByVal objTreeNode As _
    TreeNode) As CheckBoxState

To set the state of a node you use the SetTreeNodeState function of the TriStateTreeView class:


Friend Sub SetTreeNodeState(ByVal objTreeNode As _
    TreeNode, ByVal eCheckBoxState As CheckBoxState)

Points of Interest

The code intercepts the MouseUp and KeyUp events to change the state of a node (when clicking with the mouse or pressing space). Also, the code prevents expanding or collapsing a node double-clicking on the state image.

History

  • 27-April-2004: Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Carlos J. Quintero



Spain Spain

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralOnBeforeExpand & OnBeforeCollapse prevents expand collapse too much Pinmemberjsytniak9:55 2 Mar '09  
Generalcheckbox and afterchange event Pinmemberiwrschenk3:18 3 Dec '08  
AnswerRe: checkbox and afterchange event Pinmemberjsytniak10:23 26 Dec '08  
GeneralProblem with SetTreeNodeState method PinmemberOmar Vargas5:50 11 Nov '08  
NewsC# Version Pinmemberjsytniak3:37 7 May '08  
Generalnode tooltip Pinmemberkalany6:21 16 Apr '08  
GeneralRe: node tooltip - bug with state icons Pinmemberjsytniak3:40 7 May '08  
GeneralC# version with some minor enhancements Pinmemberalverdal6:16 20 Mar '08  
using System;
using System.Collections.Generic;
using System.Text;
 
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
 
namespace Pdb.BIPortal.UserAdmin.Controls.ImageTriStateTreeView
{
 
public enum CheckBoxState
{
None = 0,
Unchecked = 1,
Checked = 2,
Indeterminate = 3
}
 
public class ImageTriStateTreeView : System.Windows.Forms.TreeView
{
 
[StructLayout(LayoutKind.Sequential)]
private struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
public int pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public int lParam;
}
 
[StructLayout(LayoutKind.Sequential)]
private struct POINTAPI
{
public int x;
public int y;
}
 
[StructLayout(LayoutKind.Sequential)]
private struct TVHITTESTINFO
{
public POINTAPI pt;
public int flags;
public IntPtr hItem;
}
 
// Messages
private const int TV_FIRST = 4352;
private const int TVM_SETIMAGELIST = TV_FIRST + 9;
private const int TVM_GETITEM = TV_FIRST + 12;
private const int TVM_SETITEM = TV_FIRST + 13;
private const int TVM_HITTEST = TV_FIRST + 17;
 
// TVM_SETIMAGELIST image list kind
private const int TVSIL_STATE = 2;
 
//TVITEM.mask flags
private const int TVIF_STATE = 8;
private const int TVIF_HANDLE = 16;
 
//TVITEM.state flags
public const int TVIS_STATEIMAGEMASK = 61440;
 
//TVHITTESTINFO.flags flags
public const int TVHT_ONITEMSTATEICON = 64;
 
// ImageList Images Indexes
private const int m_IMG_CHECKBOX_NONE = 0;
private const int m_IMG_CHECKBOX_UNCHECKED = 1;
private const int m_IMG_CHECKBOX_CHECKED = 2;
private const int m_IMG_CHECKBOX_INDETERMINATE = 3;
[DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, IntPtr lParam);
[DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, ref TVITEM lParam);
[DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, ref TVHITTESTINFO lParam);

 
public ImageTriStateTreeView(ImageList ctlStateImageList)
{
 
int iResult;
 
// Set the state image list
iResult = SendMessage(this.Handle, TVM_SETIMAGELIST, TVSIL_STATE, ctlStateImageList.Handle);
 
}
 
private void SetTreeNodeAndChildrenStateRecursively(TreeNode objTreeNode, CheckBoxState eCheckBoxState)
{


if ((objTreeNode != null)) {

SetTreeNodeState(objTreeNode, eCheckBoxState);

foreach (TreeNode objChildTreeNode in objTreeNode.Nodes)
{
SetTreeNodeAndChildrenStateRecursively(objChildTreeNode, eCheckBoxState);
}

}

}
 
private void SetParentTreeNodeStateRecursively(TreeNode objParentTreeNode)
{

CheckBoxState eCheckBoxState;
bool bAllChildrenChecked = true;
bool bAllChildrenUnchecked = true;


if ((objParentTreeNode != null)) {

if (GetTreeNodeState(objParentTreeNode) != CheckBoxState.None) {
 
foreach (TreeNode objTreeNode in objParentTreeNode.Nodes)
{

eCheckBoxState = GetTreeNodeState(objTreeNode);

switch (eCheckBoxState) {

case CheckBoxState.Checked:
bAllChildrenUnchecked = false;
break;

case CheckBoxState.Indeterminate:
bAllChildrenUnchecked = false;
bAllChildrenChecked = false;
break;

case CheckBoxState.Unchecked:
bAllChildrenChecked = false;
break;

}

if (bAllChildrenChecked == false & bAllChildrenUnchecked == false) {
// This is an optimization
break; // TODO: might not be correct. Was : Exit For
}

}

if (bAllChildrenChecked) {
SetTreeNodeState(objParentTreeNode, CheckBoxState.Checked);
}
else if (bAllChildrenUnchecked) {
SetTreeNodeState(objParentTreeNode, CheckBoxState.Unchecked);
}
else {
SetTreeNodeState(objParentTreeNode, CheckBoxState.Indeterminate);
}

// Enter in recursion
if ((objParentTreeNode.Parent != null)) {
SetParentTreeNodeStateRecursively(objParentTreeNode.Parent);
}

}

}

}
 
internal CheckBoxState GetTreeNodeState(TreeNode objTreeNode)
{
 
CheckBoxState eCheckBoxState = CheckBoxState.None;
 
TVITEM tTVITEM = new TVITEM();
int iState;
int iResult;
 
tTVITEM.mask = TVIF_HANDLE | TVIF_STATE;
tTVITEM.hItem = objTreeNode.Handle;
tTVITEM.stateMask = TVIS_STATEIMAGEMASK;
tTVITEM.state = 0;
 
iResult = SendMessage(this.Handle, TVM_GETITEM, 0, ref tTVITEM);
 
if (iResult != 0)
{
 
iState = tTVITEM.state;
 
// State image index in bits 12..15
iState = iState / 4095;
 
switch (iState)
{
 
case m_IMG_CHECKBOX_NONE:
eCheckBoxState = CheckBoxState.None;
break;
 
case m_IMG_CHECKBOX_UNCHECKED:
eCheckBoxState = CheckBoxState.Unchecked;
break;
 
case m_IMG_CHECKBOX_CHECKED:
eCheckBoxState = CheckBoxState.Checked;
break;
 
case m_IMG_CHECKBOX_INDETERMINATE:
eCheckBoxState = CheckBoxState.Indeterminate;
break;
 
}
 
}
 
return eCheckBoxState;
 
}
 
internal void SetTreeNodeState(TreeNode objTreeNode, CheckBoxState eCheckBoxState)
{
 
int iImageIndex=0;
TVITEM tTVITEM = new TVITEM();
int iState;
int iResult;
 
if ((objTreeNode != null))
{
 
switch (eCheckBoxState)
{
 
case CheckBoxState.None:
iImageIndex = m_IMG_CHECKBOX_NONE;
break;
 
case CheckBoxState.Unchecked:
iImageIndex = m_IMG_CHECKBOX_UNCHECKED;
break;
 
case CheckBoxState.Checked:
iImageIndex = m_IMG_CHECKBOX_CHECKED;
break;
 
case CheckBoxState.Indeterminate:
iImageIndex = m_IMG_CHECKBOX_INDETERMINATE;
break;
 
}
 
tTVITEM.mask = TVIF_HANDLE | TVIF_STATE;
tTVITEM.hItem = objTreeNode.Handle;
tTVITEM.stateMask = TVIS_STATEIMAGEMASK;
// State image index in bits 12..15
tTVITEM.state = iImageIndex * 4096;
 
iResult = SendMessage(this.Handle, TVM_SETITEM, 0, ref tTVITEM);
 
}
 
}
 
public void ToggleTreeNodeState(TreeNode objTreeNode)
{
 
CheckBoxState eCheckBoxState;
 
eCheckBoxState = GetTreeNodeState(objTreeNode);
 
this.BeginUpdate();
 
switch (eCheckBoxState)
{
 
case CheckBoxState.Unchecked:
 
SetTreeNodeAndChildrenStateRecursively(objTreeNode, CheckBoxState.Checked);
SetParentTreeNodeStateRecursively(objTreeNode.Parent);
break;
 
case CheckBoxState.Checked:
case CheckBoxState.Indeterminate:
 
SetTreeNodeAndChildrenStateRecursively(objTreeNode, CheckBoxState.Unchecked);
SetParentTreeNodeStateRecursively(objTreeNode.Parent);
break;
 
}
 
this.EndUpdate();
 
}
 
private TreeNode GetTreeNodeHitAtCheckBoxByScreenPosition(int iXScreenPos, int iYScreenPos)
{
 
Point objClientPoint;
TreeNode objTreeNode;
 
objClientPoint = this.PointToClient(new Point(iXScreenPos, iYScreenPos));
 
objTreeNode = GetTreeNodeHitAtCheckBoxByClientPosition(objClientPoint.X, objClientPoint.Y);
 
return objTreeNode;
 
}
 
private TreeNode GetTreeNodeHitAtCheckBoxByClientPosition(int iXClientPos, int iYClientPos)
{
 
TreeNode objTreeNode = null;
int iTreeNodeHandle;
TVHITTESTINFO tTVHITTESTINFO = new TVHITTESTINFO();
 
// Get the hit info
tTVHITTESTINFO.pt.x = iXClientPos;
tTVHITTESTINFO.pt.y = iYClientPos;
iTreeNodeHandle = SendMessage(this.Handle, TVM_HITTEST, 0, ref tTVHITTESTINFO);
 
// Check if it has clicked on an item
if (iTreeNodeHandle != 0)
{
 
// Check if it has clicked on the state image of the item
if ((tTVHITTESTINFO.flags & TVHT_ONITEMSTATEICON) != 0)
{
 
objTreeNode = TreeNode.FromHandle(this, new IntPtr(iTreeNodeHandle));
 
}
 
}
 
return objTreeNode;
 
}
 
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
{
 
TreeNode objTreeNode;
 
base.OnMouseUp(e);
 
objTreeNode = GetTreeNodeHitAtCheckBoxByClientPosition(e.X, e.Y);
if ((objTreeNode != null))
{
 
ToggleTreeNodeState(objTreeNode);
 
}
 
}
 
protected override void OnKeyUp(System.Windows.Forms.KeyEventArgs e)
{
 
base.OnKeyUp(e);
 
if (e.KeyCode == Keys.Space)
{
 
if ((this.SelectedNode != null))
{
ToggleTreeNodeState(this.SelectedNode);
}
 
}
 
}
 
protected override void OnBeforeExpand(System.Windows.Forms.TreeViewCancelEventArgs e)
{

// PATCH: if the node is being expanded by a double click at the state image, cancel it
if ((GetTreeNodeHitAtCheckBoxByScreenPosition(MousePosition.X, MousePosition.Y) != null))
{
e.Cancel = true;
}
 
}
 
protected override void OnBeforeCollapse(System.Windows.Forms.TreeViewCancelEventArgs e)
{
 
// PATCH: if the node is being collapsed by a double click at the state image, cancel it
if ((GetTreeNodeHitAtCheckBoxByScreenPosition(MousePosition.X, MousePosition.Y) != null))
{
e.Cancel = true;
}
 
}
 
public TreeNode AddTreeNode(TreeNodeCollection colNodes, string sNodeText, int iImageIndex, CheckBoxState eCheckBoxState)
{
TreeNode objTreeNode;
 
objTreeNode = new TreeNode(sNodeText);
 
objTreeNode.ImageIndex = iImageIndex;
objTreeNode.SelectedImageIndex = iImageIndex;
 
colNodes.Add(objTreeNode);
 
this.SetTreeNodeState(objTreeNode, eCheckBoxState);
 
return objTreeNode;
 
}

 
public TreeNode AddTreeNode(TreeNodeCollection colNodes, TreeNode objTreeNode, CheckBoxState eCheckBoxState)
{
colNodes.Add(objTreeNode);
this.SetTreeNodeState(objTreeNode, eCheckBoxState);
return objTreeNode;
}
 
/// <summary>
/// Return a list of the tag data for all of the checked items in the tree
/// </summary>
/// <returns></returns>
public ArrayList GetCheckedTagData()
{
ArrayList list = new ArrayList();
 
foreach (TreeNode node in Nodes)
BuildTagDataList(node, list);
return list;
}
 
/// <summary>
/// Gets all checked nodes as a generic list.
/// </summary>
/// <typeparam name="T">Generic type</typeparam>
/// <returns>Generic list</returns>
public List<T> GetCheckedTagData<T>()
{
List<T> list = new List<T>();
 
foreach (TreeNode node in Nodes)
BuildTagDataList(node, list);
return list;

}
 
/// <summary>
/// Gets the checked state of a node
/// </summary>
/// <param name="node">Node</param>
/// <returns>The checked state</returns>
public ArrayList GetCheckedNodes()
{
ArrayList list = new ArrayList();
 
foreach (TreeNode node in Nodes)
{
BuildNodeDataList(node, list);
}
 
return list;
}
 

 
/// <summary>
/// Build a list of all of the checked nodes in the tree.
/// </summary>
/// <param name="node"></param>
/// <param name="list"></param>
private void BuildNodeDataList(TreeNode node, ArrayList list)
{
if (GetTreeNodeState(node) == CheckBoxState.Checked)
list.Add(node);
 
foreach (TreeNode child in node.Nodes)
BuildNodeDataList(child, list);
}
 

/// <summary>
/// Build a list of all of the tag data for checked items in the tree.
/// </summary>
/// <param name="node"></param>
/// <param name="list"></param>
private void BuildTagDataList(TreeNode node, ArrayList list)
{
if (GetTreeNodeState(node) == CheckBoxState.Checked && node.Tag != null)
list.Add(node.Tag);
 
foreach (TreeNode child in node.Nodes)
BuildTagDataList(child, list);
}
 
/// <summary>
/// Build a list of all of the tag data for checked items in the tree.
/// </summary>
/// <typeparam name="T">The type of the list</typeparam>
/// <param name="node">Current node</param>
/// <param name="list">List to fill with tags</param>
private void BuildTagDataList<T>(TreeNode node, List<T> list)
{
if (GetTreeNodeState(node) == CheckBoxState.Checked && node.Tag != null)
list.Add((T)node.Tag);

 
foreach (TreeNode child in node.Nodes)
BuildTagDataList(child, list);
}
 

}
 
}
GeneralRe: C# version with some minor enhancements PinmemberMember 283438922:33 5 Oct '08  
QuestionCheck Boxes not showing up in my project Pinmembercrazytom10:03 25 Jan '08  
GeneralRe: Check Boxes not showing up in my project Pinmembercrazytom8:27 27 Jan '08  
GeneralRe: Check Boxes not showing up in my project Pinmemberkhendric3:32 5 Mar '08  
QuestionProblems with this class Pinmemberjonmach1:05 17 Nov '07  
AnswerRe: Problems with this class Pinmemberjonmach8:18 17 Nov '07  
AnswerRe: Problems with this class Pinmembermadmax823:02 4 Aug '10  
Generalpocket pc project Pinmemberrabosa23:58 20 Jun '07  
GeneralBeforeCheck Issue Pinmemberthierry_anthony7:30 13 Apr '07  
GeneralPopule windows root [modified] Pinmembermpdesign13:48 11 Feb '07  
QuestionTreeview state problem PinmemberAchintya Jha5:49 25 Oct '06  
AnswerRe: Treeview state problem Pinmemberpriman_p21:31 27 Sep '09  
GeneralLoadOnDemand does not work Pinmemberdevnet24723:35 18 Feb '06  
GeneralVery nice however... Pinmembervbinfo4:56 31 Jan '06  
GeneralRe: Very nice however... PinmemberCarlos J. Quintero5:17 31 Jan '06  
GeneralRe: Very nice however... Pinmemberdevnet24712:31 13 Feb '06  
GeneralRe: Very nice however... PinmemberNewton Burgos16:54 5 Aug '06  

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.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120529.1 | Last Updated 19 Jan 2006
Article Copyright 2004 by Carlos J. Quintero
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid