Click here to Skip to main content
Email Password   helpLost your password?

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralOnBeforeExpand & OnBeforeCollapse prevents expand collapse too much
jsytniak
10:55 2 Mar '09  
I find that in some instances the code in these overrides will cancel the event too often. I discovered that it would cancel when clicking on the plus next to a node, but not all of the time, and only when the form containing the tree is maximized. Odd behavior. Removing the overrides did not seem to have any adverse effects.

   Protected Overrides Sub OnBeforeExpand(ByVal e As System.Windows.Forms.TreeViewCancelEventArgs)

' PATCH: if the node is being expanded by a double click at the state image, cancel it
If Not (GetTreeNodeHitAtCheckBoxByScreenPosition(Me.MousePosition.X, Me.MousePosition.Y) Is Nothing) Then
e.Cancel = True
End If

End Sub

Protected Overrides Sub OnBeforeCollapse(ByVal e As System.Windows.Forms.TreeViewCancelEventArgs)

'
PATCH: if the node is being collapsed by a double click at the state image, cancel it
If Not (GetTreeNodeHitAtCheckBoxByScreenPosition(Me.MousePosition.X, Me.MousePosition.Y) Is Nothing) Then
e.Cancel = True
End If

End Sub


Joe

Generalcheckbox and afterchange event
iwrschenk
4:18 3 Dec '08  
An Click on the checkbox don't fire the afterchange event!?
any ideas?

regards achim
AnswerRe: checkbox and afterchange event
jsytniak
11:23 26 Dec '08  
Try using the AfterCheck and the NodeStateChange events.

Joe

GeneralProblem with SetTreeNodeState method
Omar Vargas
6:50 11 Nov '08  
Hello everyone.

When I call the SetTreeNodeState method, the state of the parent node does not work correctly.

For example: If I have a parent node which children are checked, but I set the state of any one of those children to unchecked (calling SetTreeNodeState) the state of the parent node does not go to Indeterminate.

I think that we has to make a call to SetParentTreeNodeStateRecursively(objTreeNode.Parent) at the end of SetTreeNodeState method.

I have made this modification and it works correctly.

Bye
NewsC# Version
jsytniak
4:37 7 May '08  
I have converted the VB.NET version to a C# version.

While I am at it, I may as well mention a nice little code conversion tool provided by the folks at Telerik that I found on Google's iGoogle home page as a widget. Works like a champ. The only things it did not handle were the API definitions and usage instances (datatypes).

I'd like to post the C# code here but I do not see a way to do so.

Joe

Generalnode tooltip
kalany
7:21 16 Apr '08  
if you set the treeview.ShowNodeToolTips = True the checkboxes disappear. Is this a bug??
GeneralRe: node tooltip - bug with state icons
jsytniak
4:40 7 May '08  
It sure looks like a bug to me.

Allow me to provide a more complete description of the anomalous behavior.

When ShowNodeTooltips is set to true, the tooltips do indeed show, but the state icons do not. When ShowNodeTooltips is set to the node tooltips do not show and the state icons also show as expected.

Joe

GeneralC# version with some minor enhancements
alverdal
7: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
Member 2834389
23:33 5 Oct '08  
Thanks for porting this to c#!

Much Appreciated.
QuestionCheck Boxes not showing up in my project
crazytom
11:03 25 Jan '08  
Hi, thanks for the great, helpful and needed class, but...

I have converted the vbcode into c# with SharpDeveloper. I added the converted classes FormTreeView and TriStateTreeView into a brand new c# windows application solution and replaced MainForm with FormTreeView. A few minor code modifications regarding compiler differences and the c# tree view runs perfectly as the original vb tree view. But...

Now I added the TriStateTreeView class into my project and the CheckBoxes don't show. I'm doing it exactly as in the example in FormTreeView. As far as I can tell I have tried everything. I'm not new to programming but I am new to c# and .NET. Can anyone confirm this behaviour or maybe even have a solution? I would be so very greatful!

Kind Regards

Tom
GeneralRe: Check Boxes not showing up in my project
crazytom
9:27 27 Jan '08  
Hi again,
I found the couse. Nothing important.

If someone is interested:
I populate and expand my node in the nodeMouseDoubleClick event. When I double click the check box the node is populated. It dousn't get expanded because the overridden onBeforeExpand prevents it. But to prevent the node from being populated I added and override for onNodeMouseDoubleClick to do nothing in case of GetTreeNodeHitAtCheckBoxByScreenPosition.

     protected override void OnNodeMouseDoubleClick(System.Windows.Forms.TreeNodeMouseClickEventArgs e) {
          if ((GetTreeNodeHitAtCheckBoxByScreenPosition(MousePosition.X, MousePosition.Y) != null))
          {
          } else
               base.OnNodeMouseDoubleClick(e);
     }


Thanks again for that very nice tree view.
GeneralRe: Check Boxes not showing up in my project
khendric
4:32 5 Mar '08  
Would you be interested in posting your C# version of the code? If so, thanks very much in advance! Smile

Kim

QuestionProblems with this class
jonmach
2:05 17 Nov '07  
Hi,

I really like this tristate class, but I have found a number of issues that I cannot work around.

1) If you call the form containing the class more than once, the checkboxes disappear. I tried this with the sample application by simply calling the FormTreeView.vb form twice, and it happened there as well.

2) If I set up two or three levels of items and set the items at the lowest levels to be checked. Unfortunately, this does not affect FOLDER level items above them to be partially or fully checked. Is this normal?

Any ideas?


AnswerRe: Problems with this class
jonmach
9:18 17 Nov '07  
OK - I've managed to work around point issue above.

You need to set if to unchecked, and then toggle it. This cannot take place within the class definition for some reason, but does work in the form manipulating the control as follows:

m_ctlTriStateTreeView.SetTreeNodeState(tnNode, CheckBoxState.Unchecked)
m_ctlTriStateTreeView.ToggleTreeNodeState(tnNode)

Issue 2 still remains, and I can't understand why it should.

Generalpocket pc project
rabosa
0:58 21 Jun '07  
how can i add it into a smartdevice project for pocket pc in vb.net 2003????

GeneralBeforeCheck Issue
thierry_anthony
8:30 13 Apr '07  
The BeforeCheck Event is never raised and So we can't Cancel any state.

GeneralPopule windows root [modified]
mpdesign
14:48 11 Feb '07  
Hi, nice work!!!

I’m newbie, and I have been trying to populate this treeview with my windows route:

C:\
C:\windows
C:\documents and settings\[user profile]\.....
C:\program files\app 01\xx.txt

Butte I don’t have success….

Is it possible to populate windows route in this treeview, and on close form get the folders / files paths from checked boxes?

Regards

Mpires – mpires@astralpool.pt



-- modified at 18:52 Monday 19th February, 2007

mpires

QuestionTreeview state problem
Achintya Jha
6:49 25 Oct '06  
Hi,

I am using your treeview control after converting it in C#.
It worked fine for an year when I used it in .NET 2003.
Now I have shifted to .NET 2005 and I have one problem.
If I want to get the state of the checkbox, it always returns false even if it is checked.
After running it in both 2003 and 2005, I found the difference in the line
iResult = SendMessage(this.Handle, TVM_SETITEM, 0, ref tTVITEM);
in the method SetTreeNodeState(TreeNode objTreeNode, CheckBoxState eCheckBoxState).

I put a watch on the objTreeNode.Checked expression and in 2003 it turns to "true" after the code excution passes the SendMessage line. In 2005, nothing happens.

Can you help me get rid of this problem ?

Thanks in advance
Achintya
AnswerRe: Treeview state problem
priman_p
22:31 27 Sep '09  
I also faced to this problem. I got soulution for it. The problem was 'GetCheck' method not work after converting.
There for I used 'GetCheckedNodes()' method on class. It used in my form like this


arrayList Checked = new ArrayList();
Checked = m_ctlTriStateTreeView.GetCheckedNodes();
foreach (TreeNode n in Checked)..................
GeneralLoadOnDemand does not work
devnet247
0:35 19 Feb '06  
Hi,
Again thanks for your code.It really does what I was looking for.
I have noticed that you have overriden the beforeExpand Event and collapse event.
This was to prevent expansion if doubleClick on the icon.
However if you want to load lots of nodes on demand by using the expand even you cant.Any reason why not i should not comment it out?




thanks a lot
GeneralVery nice however...
vbinfo
5:56 31 Jan '06  
Hi,thanks for your control.
I am trying to drag and drop your control from the toolbox to the form but i get an error.
It would make life a lot easier.Are you meant to drag and drop the control onto a form?



Thanks A lot
vbinfo@devnet247.com
GeneralRe: Very nice however...
Carlos J. Quintero
6:17 31 Jan '06  
Hi,

No, it is a class, not a control for the toolbox. Read the article to see the usage. You can transform it into a control if you want.

Carlos J. Quintero
GeneralRe: Very nice however...
devnet247
13:31 13 Feb '06  
Hi there,
Tried to transform into a control but having problem with apicall.
Basically I am included the stateImageList into the componect and tried moving the call to sendMessage to Public new but i get an error.
Any advice ?



thanks a lot
GeneralRe: Very nice however...
Newton Burgos
17:54 5 Aug '06  
I need this too please need help
GeneralGreat Job
computerguru92382
17:54 24 Jan '06  
Carlos,

Great job with the control. I've been interested in something like this.

Cheers,

Paul


Last Updated 19 Jan 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010