Home > WinForms > WinForms: How to hide checkbox of the certain TreeNode in TreeView control

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();
}
Related posts:
 
  1. June 15th, 2011 at 16:23 | #1

    BION I’m imerpssed! Cool post!

  2. Rick Powell
    September 23rd, 2011 at 22:42 | #2

    Excellent! In WndProc(ref Message m) Message is imported from what Namespace?

  3. Administrator
    September 24th, 2011 at 00:37 | #3

    @Rick Powell
    Hi, Rick!
    Message is from System.Windows.Forms namespace.
    Thank you!

  4. Rick Powell
    September 28th, 2011 at 14:15 | #4

    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?

  5. Rick Powell
    September 28th, 2011 at 14:35 | #5

    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

  6. Administrator
    September 28th, 2011 at 14:49 | #6
  7. Rick Powell
    September 28th, 2011 at 15:29 | #7

    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;

  8. Administrator
    September 28th, 2011 at 18:28 | #8

    @Rick Powell
    If it works as you expected, that’s great

  9. Rob G
    October 26th, 2011 at 21:36 | #9

    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

  10. Alex
    November 29th, 2011 at 07:57 | #10

    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?

    • Administrator
      November 30th, 2011 at 16:09 | #11

      Hello!
      I’d suggest to compile as C# project and then, using .Net Reflector, decompile and extract VB code

  11. Alex
    December 2nd, 2011 at 10:01 | #12

    Thanks for the suggestion. I’ll give that a try when I get a chance.

  12. Alex
    December 2nd, 2011 at 14:22 | #13

    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.

    • Administrator
      December 2nd, 2011 at 14:46 | #14

      Does the C# version of your application throw the same exception?

  13. Alex
    December 4th, 2011 at 19:52 | #15

    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.

  14. Alex
    December 4th, 2011 at 20:25 | #16

    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.

    • Administrator
      December 4th, 2011 at 21:42 | #17

      Yes, that’s the quite good and admissible variant as well.

  15. Shamseena VM
    January 31st, 2012 at 03:30 | #18

    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();
    }

  16. Louis
    March 29th, 2012 at 02:02 | #19

    @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.

  17. Liz
    April 26th, 2012 at 09:14 | #20

    Just what I was looking for. Thanks
    —Liz

  1. No trackbacks yet.