WinForms: How to hide checkbox of the certain TreeNode in TreeView control
Recently I needed to hide the checkboxes of some nodes in a TreeView-control, i.e. some nodes should be checkable, but the rest shouldn’t.

Standard TreeView doesn’t allow to do that directly, but Windows API will help us out. The main idea is to catch the moment when a node is just added, then to examine whether checkbox of the node must be hidden and, if yes, to send a certain TVM_SETITEM message to the control to hide checkbox.
Unfortunately, TreeView-control doesn’t provide with an event, which could be fired after a node is added. That is why we have to work with WinAPI messages directly inside WndProc.
By means of Reflector I’ve found out that when a node is being added to Nodes-collection of a TreeView or another node the following code is running (these lines from the internal method Realize of TreeNode class):
this.handle = UnsafeNativeMethods.SendMessage(new HandleRef(this.TreeView, this.TreeView.Handle), NativeMethods.TVM_INSERTITEM, 0, ref lParam); treeView.nodeTable[this.handle] = this; this.UpdateNode(4);
After the first line passed the parent TreeView-control receives TVM_INSERTITEM message. We would catch this message inside WndProc of TreeView-control and get the handle of added node, however on this stage we wouldn’t be able to find the TreeNode-object, which corresponds to the handle. But we need TreeNode-object in order to examine whether its checkbox must be hidden.
Just after the second line the hashtable nodeTable of TreeView-control already contains information which TreeNode-object is linked to the added node handle. But TVM_INSERTITEM has already been processed. That means we have to catch another type of messages.
Luckily, inside the method UpdateNode from the third line the TVM_SETITEM message is sent to the parent TreeView-control. I decided to catch this kind of messages to have ability to hide checkbox of just added node, if it’s required.
The descendants of TreeView-control and TreeNode, those embody this approach, are presented below
/// <summary>
/// Allows to detect nodes, the checkbox of those must be hidden
/// </summary>
public class HiddenCheckBoxTreeNode : TreeNode
{
internal bool CheckboxHidden { set; get; }
public HiddenCheckBoxTreeNode() {}
public HiddenCheckBoxTreeNode(string text) : base(text) {}
public HiddenCheckBoxTreeNode(string text, TreeNode[] children) : base(text, children) {}
public HiddenCheckBoxTreeNode(string text, int imageIndex, int selectedImageIndex) : base(text, imageIndex, selectedImageIndex) {}
public HiddenCheckBoxTreeNode(string text, int imageIndex, int selectedImageIndex, TreeNode[] children) : base(text, imageIndex, selectedImageIndex, children) {}
protected HiddenCheckBoxTreeNode(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { }
}
public class MixedCheckBoxesTreeView : TreeView
{
/// <summary>
/// Specifies or receives attributes of a node
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TV_ITEM
{
public int Mask;
public IntPtr ItemHandle;
public int State;
public int StateMask;
public IntPtr TextPtr;
public int TextMax;
public int Image;
public int SelectedImage;
public int Children;
public IntPtr LParam;
}
public const int TVIF_STATE = 0x8;
public const int TVIS_STATEIMAGEMASK = 0xF000;
public const int TVM_SETITEMA = 0x110d;
public const int TVM_SETITEM = 0x110d;
public const int TVM_SETITEMW = 0x113f;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref TV_ITEM lParam);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
// trap TVM_SETITEM message
if (m.Msg == TVM_SETITEM || m.Msg == TVM_SETITEMA || m.Msg == TVM_SETITEMW)
// check if CheckBoxes are turned on
if (CheckBoxes)
{
// get information about the node
TV_ITEM tv_item = (TV_ITEM)m.GetLParam(typeof(TV_ITEM));
HideCheckBox(tv_item);
}
}
protected void HideCheckBox(TV_ITEM tv_item)
{
if (tv_item.ItemHandle != IntPtr.Zero)
{
// get TreeNode-object, that corresponds to TV_ITEM-object
TreeNode currentTN = TreeNode.FromHandle(this, tv_item.ItemHandle);
HiddenCheckBoxTreeNode hiddenCheckBoxTreeNode = currentTN as HiddenCheckBoxTreeNode;
// check if it's HiddenCheckBoxTreeNode and
// if its checkbox already has been hidden
if (hiddenCheckBoxTreeNode != null && !hiddenCheckBoxTreeNode.CheckboxHidden)
{
// to evade repeat hiding, we set CheckboxHidden to true
hiddenCheckBoxTreeNode.CheckboxHidden = true;
// specify attributes to update
TV_ITEM updatedTvItem = new TV_ITEM();
updatedTvItem.ItemHandle = tv_item.ItemHandle;
updatedTvItem.Mask = TVIF_STATE;
updatedTvItem.StateMask = TVIS_STATEIMAGEMASK;
updatedTvItem.State = 0;
// send TVM_SETITEM message
SendMessage(new HandleRef(this, Handle), TVM_SETITEM, 0, ref updatedTvItem);
}
}
}
protected override void OnBeforeCheck(TreeViewCancelEventArgs e)
{
base.OnBeforeCheck(e);
// prevent checking/unchecking of HiddenCheckBoxTreeNode,
// otherwise, we will have to repeat checkbox hiding
if (e.Node is HiddenCheckBoxTreeNode)
e.Cancel = true;
}
}
Also we need to ban an ability to change the property Checked of TreeNode-object, because if it has happened, we would have to hide checkbox again. Method OnBeforeCheck does it.
Below the sample of usage of these classes:
// put the MixedCheckBoxesTreeView on the form and override OnLoad method of the form
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
mixedCheckBoxesTreeView1.CheckBoxes = true;
mixedCheckBoxesTreeView1.Nodes.Clear();
HiddenCheckBoxTreeNode tn = new HiddenCheckBoxTreeNode("Root Node 1");
mixedCheckBoxesTreeView1.Nodes.Add(tn);
TreeNode tn2 = new TreeNode("Child Node A");
tn.Nodes.Add(tn2);
HiddenCheckBoxTreeNode tn3 = new HiddenCheckBoxTreeNode("Child Node B");
tn3.Checked = true; // just to test that nothing happens here
tn.Nodes.Add(tn3);
HiddenCheckBoxTreeNode tn4 = new HiddenCheckBoxTreeNode("Child Node C");
tn.Nodes.Add(tn4);
TreeNode tn5 = new TreeNode("Child Node D");
tn.Nodes.Add(tn5);
TreeNode tn6 = new TreeNode("Root Node 2");
mixedCheckBoxesTreeView1.Nodes.Add(tn6);
TreeNode tn7 = new TreeNode("Root Node 3");
mixedCheckBoxesTreeView1.Nodes.Add(tn7);
HiddenCheckBoxTreeNode tn8 = new HiddenCheckBoxTreeNode("Child Node A");
tn7.Nodes.Add(tn8);
mixedCheckBoxesTreeView1.ExpandAll();
}

BION I’m imerpssed! Cool post!
Excellent! In WndProc(ref Message m) Message is imported from what Namespace?
@Rick Powell
Hi, Rick!
Message is from System.Windows.Forms namespace.
Thank you!
There is a setting somewhere that cause the checkbox to only check or uncheck on Double Click. It is not consistent. On one tree the root nodes respond to Dbl Click, but the child node Single Click. On another all of them. Have any ideas?
Ignore my last comment, I finally understand now. If I want a Hidden Check Box I use the HiddenCheckBoxTreeNode, but if I want one that I can check/uncheck I use a Treenode. Thanks
@Rick Powell
Ok
Yeh, I needed some of the HiddenCheckBoxTreeNode to be visible and check-able in other parts of the tree, so I modified base.OnBeforeCheck(e); I appended “iChk” to their names and if the e.Node.Name doesn’t end in “iChk” then e.Cancel = true;
@Rick Powell
If it works as you expected, that’s great
Thank You! Just what I was looking for and it works perfectly. If anyone is using this with vb.net, add:
Imports System.Runtime.Serialization
Imports System.Runtime.InteropServices
I’m trying to use this with vb.net. I’m not really familiar with c#, so I ran your class through a c# to vb translator at http://www.developerfusion.com/tools/convert/csharp-to-vb/
I’ve had to import the two classes that Rob G mentioned, but I’m still getting check boxes on every node in the treeview control. I’m going to try stepping through the example a little more closely, but is there anything obvious that I might be missing with vb?
Hello!
I’d suggest to compile as C# project and then, using .Net Reflector, decompile and extract VB code
Thanks for the suggestion. I’ll give that a try when I get a chance.
I did as you suggested, and extracted the VB code from .Net Reflector. I was still getting check boxes on every node, and with a little digging, realized that I hadn’t replaced the regular TreeView box with a MixedCheckBoxesTreeView box…
I’m using the proper box now, and I feel much closer to the solution!
However, when I try to load up an example box with the nodes listed above, I get an unhandled exception of type ‘System.StackOverflowException’ occurring in System.Windows.Forms.dll. This is happening in the WndProc sub.
If I get this sorted out, I’ll post an update here. Otherwise, if anyone has any insights or suggestions, I’d be happy to hear them.
Does the C# version of your application throw the same exception?
No, I get everything running perfectly in C#. When I try running with the VB code from .Net Reflector, I get the error on the WndProc sub. There must be something I’m overlooking, as Rob G seemed to get it running in VB.
For now, my work around is to compile the C# classes, and add a reference to the .dll in my VB project. It would be nice to have it working in VB, but this will work for me.
Thanks.
Yes, that’s the quite good and admissible variant as well.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace SampleWork
{ public partial class FrmSettings : MDIParent1
{
int i = 0;
int j = 0;
TreeNode[] node;
public const int TVIF_STATE = 0×8;
public const int TVIS_STATEIMAGEMASK = 0xF000;
public const int TV_FIRST = 0×1100;
public const int TVM_SETITEM = TV_FIRST + 63;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam,
IntPtr lParam);
public FrmSettings()
{
InitializeComponent();
}
public struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
private void HideCheckBox(TreeNode node)
{
TVITEM tvi = new TVITEM();
tvi.hItem = node.Handle;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
IntPtr lparam = Marshal.AllocHGlobal(Marshal.SizeOf(tvi));
Marshal.StructureToPtr(tvi, lparam, false);
SendMessage(this.treeView1 .Handle, TVM_SETITEM, IntPtr.Zero, lparam);
}
public void GetAllNodeByCode()
{
TreeNode treenode;
treenode = new TreeNode(“Select From the following”);
treeView1.Nodes.Add(treenode);
HideCheckBox(treenode);
foreach (ToolStripMenuItem mainMenu in menuStrip.Items)
{
int count = mainMenu.DropDownItems.Count;
node = new TreeNode[count];
i = 0;
foreach (ToolStripItem subMenu in mainMenu.DropDownItems)
{
node[i] = new TreeNode(subMenu.ToString());
i++;
}
treenode = new TreeNode(mainMenu.ToString (), node);
treeView1.Nodes.Add(treenode);
HideCheckBox(treenode);
}
}
private void FrmSettings_Load(object sender, EventArgs e)
{
treeView1.CheckBoxes = true;
GetAllNodeByCode();
}
@Shamseena VM
Excellent example. I took this code and refactored it slightly to expose the HideCheckBox method as an Extension Method on TreeNode. Works beautifully. The only minor issue I am running into is that if you hit while on a node with a hidden checkbox, the checkbox reappears. Apparently toggles the checked value, and toggling it on when hidden makes it reappear. Other than that one issue, this is a very clean approach. Thanks for posting. If you have any suggestions on the reappearing problem, any help would be appreciated.
Just what I was looking for. Thanks
—Liz