19

I need to be able to disable some of the checkboxes in a TreeView control of a WinForms application, but there's no such functionality built-in to the standard TreeView control.

I am already using the TreeView.BeforeCheck event and cancel it if the node is disabled and that works perfectly fine.

I also change the ForeColor of the disabled nodes to GrayText.

Does anyone have a simple and robust solution?

Vassili Altynikov
  • 2,049
  • 2
  • 17
  • 24

4 Answers4

36

Since there's support in C++ we can resolve it using p/invoke.

Here's the setup for the p/invoke part, just make it available to the calling class.

    // constants used to hide a checkbox
    public const int TVIF_STATE = 0x8;
    public const int TVIS_STATEIMAGEMASK = 0xF000;
    public const int TV_FIRST = 0x1100;
    public const int TVM_SETITEM = TV_FIRST + 63;

    [DllImport("user32.dll")]
    static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam,
    IntPtr lParam); 

    // struct used to set node properties
    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;

    } 

We want to determine on a node by node basis. The easiest way to do that is on the draw node event. We have to set our tree to be set as owner drawn in order for this event, so be sure to set that to something other than the default setting.

this.tree.DrawMode = TreeViewDrawMode.OwnerDrawText;
this.tree.DrawNode += new DrawTreeNodeEventHandler(tree_DrawNode);

In your tree_DrawNode function determine if the node being drawn is supposed to have a checkbox, and hide it when approriate. Then set the Default Draw property to true since we don't want to worry about drawing all the other details.

void tree_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
    if (e.Node.Level == 1)
    {
        HideCheckBox(e.Node);
        e.DrawDefault = true;
    }
    else 
    {
        e.Graphics.DrawString(e.Node.Text, e.Node.TreeView.Font,
           Brushes.Black, e.Node.Bounds.X, e.Node.Bounds.Y);
    }
}

Lastly, the actual call to the function we defined:

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(node.TreeView.Handle, TVM_SETITEM, IntPtr.Zero, lparam);
}
Sam Trost
  • 2,173
  • 20
  • 26
  • 3
    In the HideCheckBox function, change tvi.state = 0 to be tvi.state = 1 << 12. An explanation of statemasks is provided on MSDN here: http://msdn.microsoft.com/en-us/library/bb760017(VS.85).aspx – Sam Trost May 07 '09 at 17:02
  • 2
    When I try this the checkbox ends up getting cut off on the node that actually has a checkbox. – Shane Courtrille Oct 06 '11 at 17:39
  • Are you using the default display properties for the TreeView? If you're using your own checkbox image it may be too large...you could fix this by drawing the node text yourself, moving it slightly to the right. – Sam Trost Oct 07 '11 at 15:12
  • I got confused by the first sentence in the answer and didn't look any further. I don't know why TreeNode.ShowCheckBox works fine in ASP.NET but doesn't work in Windows Forms. It's utterly annoying. I don't like adding code I barely understand to my apps, I'm mostly an Infrastructure Architect not a developer. I don't understand all the hex and where those values come from. – Nathan McKaskle Jan 30 '15 at 16:31
  • Understandable. The reason it's so ugly is because winforms doesn't support this. We're able to get around that using p/invoke (call into unmanaged code) due to the fact that the c# treeview is really just a wrapper around the c++ treeview. – Sam Trost Jan 30 '15 at 16:41
  • There is a similar solution in [WinForms: How to hide checkbox of the certain TreeNode in TreeView control](http://dotnetfollower.com/wordpress/2011/05/winforms-treeview-hide-checkbox-of-treenode/) which creates a new type of TreeNode without the CheckBox. It still relies on p/invoke. – Daniel Ballinger Apr 13 '15 at 22:05
  • #Bananamansam, are you still around? Can you explain your initial statement about setting tvi.state to 1 << 12? It looks like you are telling Windows to use state image [1], but this code doesn't show that any state images have been created ... – Betty Crokker Feb 18 '16 at 16:24
  • Also, why does tree_DrawNode() set e.DrawDefault to true when the checkbox is hidden (telling Windows to draw the node) but when the checkbox isn't hidden, the function does the drawing? Could you set e.DrawDefault to true in both cases? – Betty Crokker Feb 18 '16 at 16:29
  • 2
    For future readers: I just figured out the answer to my second question, as Shane Courtrille found back in 2011 if you set e.DrawDefault to true, for some reason Windows erases part of the checkbox when drawing the text. I actually found it easier to just call DrawString() always (never setting e.DrawDefault to true). – Betty Crokker Feb 18 '16 at 16:40
  • I don't recall, I believe it was in reference to a comment that's been removed. The MSDN link for state masks looks to have been updated so its not immediately obvious what setting bit 12 does in the state mask. – Sam Trost Feb 18 '16 at 17:48
3

This is PowerShell version, many thanks for @sam-trost for his life-savior code!

P/invoke:

$TypeDefinition = @'
using System;
using System.Runtime.InteropServices;
namespace Win32Functions {
    public class Win32TreeView {
        // constants used to hide a checkbox
        public const int TVIF_STATE = 0x8;
        public const int TVIS_STATEIMAGEMASK = 0xF000;
        public const int TV_FIRST = 0x1100;
        public const int TVM_SETITEM = TV_FIRST + 63;

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        // struct used to set node properties
        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;
        }
    }
}
'@

Add-Type -TypeDefinition $TypeDefinition -PassThru

Event handler:

$TreeView1_DrawNode = [System.Windows.Forms.DrawTreeNodeEventHandler]{
    #Event Argument: $_ = [System.Windows.Forms.DrawTreeNodeEventArgs]
    if ($null -ne $_.Node) {

        # P/invoke hack to hide Node CheckBox
        if ($_.Node.Level -eq 0) {
            Hide-NodeCheckBox($_.Node)
        }

        $_.DrawDefault = $true
    }
}

TreeView:

$TreeView1.DrawMode = [TreeViewDrawMode]::OwnerDrawText
$TreeView1.add_DrawNode($TreeView1_DrawNode)

Function:

function Hide-NodeCheckBox([TreeNode]$node) {
    # P/invoke hack to hide Node CheckBox
    if ($node.TreeView.CheckBoxes) {
        $tvi = [Win32Functions.Win32TreeView+TVITEM]::new()
        $tvi.hItem = $node.Handle
        $tvi.mask = [Win32Functions.Win32TreeView]::TVIF_STATE
        $tvi.stateMask = [Win32Functions.Win32TreeView]::TVIS_STATEIMAGEMASK
        $tvi.state = 0
        [IntPtr]$lparam = [Marshal]::AllocHGlobal([Marshal]::SizeOf($tvi))
        [Marshal]::StructureToPtr($tvi, $lparam, $false)
        [Win32Functions.Win32TreeView]::SendMessage($node.TreeView.Handle, [Win32Functions.Win32TreeView]::TVM_SETITEM, [IntPtr]::Zero, $lparam)
    }
}
ALIENQuake
  • 520
  • 3
  • 12
  • 28
0

TreeView.BeforeCheck -- register for this event, check whether the node is one where the checkboxes are allowed to be checked or not and, if it cannot be checked then you can cancel the event by setting the Cancel property on the TreeViewCancelEventArgs. That should hopefully prevent the user from checking those boxes but will not make for the best user-experience.

To remove the checkboxes for the non-checkable items, you could possibly use owner-draw to draw a solid rectangle over the check-box to remove it.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
  • This is at first sight a simple and clean solution. The only problem with it is if a user double-clicks the checkbox, it still goes to the checked state. So if he wants to use this method he'll have to build in an extra check to prevent this from happening. :) – Abbas Aug 26 '11 at 11:24
0

There is nothing inbuilt to do this. You can use the BeforeCheck event and cancel it for the desired nodes. In case the appearance of the checkbox matters, then you will need to place a image there to show the checkbox disabled.

This link might be of your interest.

danish
  • 5,550
  • 2
  • 25
  • 28