28

I want remove CheckBoxes where the Node.Type is 5 or 6. I use this code:

private void TvOne_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
    int type = (e.Node as Node).typ;
    if (type == 5 || type == 6)
    {
        Color backColor, foreColor;
        if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
        {
            backColor = SystemColors.Highlight;
            foreColor = SystemColors.HighlightText;
        }
        else if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
        {
            backColor = SystemColors.HotTrack;
            foreColor = SystemColors.HighlightText;
        }
        else
        {
            backColor = e.Node.BackColor;
            foreColor = e.Node.ForeColor;
        }
        using (SolidBrush brush = new SolidBrush(backColor))
        {
            e.Graphics.FillRectangle(brush, e.Node.Bounds);
        }
        TextRenderer.DrawText(e.Graphics, e.Node.Text, this.TvOne.Font,
            e.Node.Bounds, foreColor, backColor);

        if ((e.State & TreeNodeStates.Focused) == TreeNodeStates.Focused)
        {
            ControlPaint.DrawFocusRectangle(e.Graphics, e.Node.Bounds,
                foreColor, backColor);
        }
        e.DrawDefault = false;
    }
    else
    {
        e.DrawDefault = true;
    }
}

The Problem is that then the Image and the Line to the Root Node is not there. How can Remove the Checkbox and let the Image and the Line there?

This is wrong!

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Werewolve
  • 2,448
  • 5
  • 24
  • 38
  • possible duplicate of [I am looking for a good resource on ownerdrawn treeviews](http://stackoverflow.com/questions/4179522/i-am-looking-for-a-good-resource-on-ownerdrawn-treeviews) – Hans Passant Jan 28 '11 at 09:44
  • I agree with Hans; owner-draw is generally prohibitively difficult. If you insist, I found a more complete sample of an owner-drawn treeview given as an answer to [this question](http://stackoverflow.com/questions/1003459/c-treeview-owner-drawing-with-ownerdrawtext-and-the-weird-black-highlighting-wh/1004087), complete with the node lines. – Cody Gray - on strike Jan 28 '11 at 09:54

3 Answers3

74

In the code you've shown, you are handling the drawing yourself for all of the nodes whose type is either 5 or 6. For the rest of the types, you're simply allowing the system to draw the nodes in the default way. That's why they all have the lines as expected, but the ones you're owner-drawing do not: You forgot to draw in the lines! You see, when you say e.DrawDefault = false; it's assumed that you really do mean it. None of the regular drawing is done, including the standard lines.

You'll either need to draw in those lines yourself, or figure out how to get by without owner-drawing at all.

From the code you have now, it looks like you're trying to simulate the system's native drawing style as much as possible in your owner-draw code, so it's not clear to me what exactly you accomplish by owner-drawing in the first place. If you're just trying to keep checkboxes from showing up for type 5 and 6 nodes (which, like the lines, are simply not getting drawn because you aren't drawing them!), there's a simpler way to do that without involving owner drawing.


So, you ask, what is that simpler way to hide the checkboxes for individual nodes? Well, it turns out that the TreeView control itself actually supports this, but that functionality is not exposed in the .NET Framework. You need to P/Invoke and call the Windows API to get at it. Add the following code to your form class (make sure you've added a using declaration for System.Runtime.InteropServices):

private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_SETITEM = TV_FIRST + 63;

[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
private 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;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
                                         ref TVITEM lParam);

/// <summary>
/// Hides the checkbox for the specified node on a TreeView control.
/// </summary>
private void HideCheckBox(TreeView tvw, TreeNode node)
{
    TVITEM tvi = new TVITEM();
    tvi.hItem = node.Handle;
    tvi.mask = TVIF_STATE;
    tvi.stateMask = TVIS_STATEIMAGEMASK;
    tvi.state = 0;
    SendMessage(tvw.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
}

All of the messy stuff at the top are your P/Invoke declarations. You need a handful of constants, the TVITEM structure that describes the attributes of a treeview item, and the SendMessage function. At the bottom is the function you'll actually call to do the deed (HideCheckBox). You simply pass in the TreeView control and the particular TreeNode item from which you want to remove the checkmark.

So you can remove the checkmarks from each of the child nodes to get something that looks like this:

   TreeView with checkmarks hidden for child nodes

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 1
    @Werewolve: Well, perhaps I misspoke on it being "simpler". I personally think it's simpler because I'm more familiar with it and because owner drawing is so difficult to get right. Basically, the `TreeView` control itself supports hiding the checkboxes for individual nodes, but that functionality is not exposed in the .NET Framework. You need to P/Invoke to get at it. I'll update my answer with the codez, if you want. – Cody Gray - on strike Jan 28 '11 at 10:16
  • @Charith: Yes, beyond owner-draw (which you definitely don't want to do, both because of the work involved and because it will never look like/behave exactly like the native TreeView control), this is the only way to do it. It looks kind of strange and complicated because you're using P/Invoke to call functions from the native Win32 API, but that's the only strange part about it. It's exactly how a C or C++ programmer would do it, they just wouldn't need the strange .NET conventions to make things happen. But you shouldn't worry about using P/Invoke--it's built into the framework for a reason. – Cody Gray - on strike Aug 26 '11 at 16:33
  • 8
    Check this answer for a solution to a failure mode of this code: http://stackoverflow.com/questions/7308238/treeview-problem-with-treenode-handle-not-returning/7308950#7308950 – Hans Passant Sep 05 '11 at 13:44
  • I'm getting a NullReferenceExeption on node.Handle. Object reference not set to an instace of an object.. Any tips? – George Norberg Sep 26 '14 at 08:23
  • 1
    @George The Node object that you're passing into the `HideCheckBox` function must be null. The function doesn't do anything special, it just uses it. If you pass it an invalid Node, it'll blow up. [Related reading](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Cody Gray - on strike Sep 26 '14 at 17:07
  • I don't understand any of the hex involved here. What's 0xF000, 0x8 and 0x1100? Why + 63? Why not +65? Where did you get all that from? It looks like C++, I really only know C#. – Nathan McKaskle Jan 29 '15 at 01:53
  • Also why do I find a whole page on TreeNode.ShowCheckBox yet it doesn't come up in intellisense nor compile with .NET 4.5? There are people out there claiming to use it. – Nathan McKaskle Jan 29 '15 at 02:44
  • 1
    @NathanMcKaskle: Although years have passed: The reason TreeNode.ShowCheckBox is not relevant is because it is a `System.Web.UI.WebControls` element, not a `System.Windows.Forms` element (the O.P.s question is about `System.Windows.Forms`) – VA systems engineer Oct 17 '18 at 17:32
19

Using TreeViewExtensions.

Usage sample:

private void MyForm_Load(object sender, EventArgs e)
{
     this.treeview1.DrawMode = TreeViewDrawMode.OwnerDrawText;
     this.treeview1.DrawNode += new DrawTreeNodeEventHandler(arbolDependencias_DrawNode);
}

void treeview1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
    if (e.Node.Level == 1) e.Node.HideCheckBox();
    e.DrawDefault = true;
}

Here is the answer's code as an Extension method, using this you can do:

public static class TreeViewExtensions
{
    private const int TVIF_STATE = 0x8;
    private const int TVIS_STATEIMAGEMASK = 0xF000;
    private const int TV_FIRST = 0x1100;
    private const int TVM_SETITEM = TV_FIRST + 63;

    [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
    private 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;
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
                                             ref TVITEM lParam);

    /// <summary>
    /// Hides the checkbox for the specified node on a TreeView control.
    /// </summary>
    public static void HideCheckBox(this TreeNode node)
    {
        TVITEM tvi = new TVITEM();
        tvi.hItem = node.Handle;
        tvi.mask = TVIF_STATE;
        tvi.stateMask = TVIS_STATEIMAGEMASK;
        tvi.state = 0;
        SendMessage(node.TreeView.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
    }
}
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
user1404096
  • 191
  • 1
  • 2
  • A word of caution on this: in my tests calling HideCheckBox triggered a repaint of the node and the application sits in an infinite loop constantly repainting the same node. – John Hall Nov 10 '22 at 17:15
6

This is very good! The only modification I'd make is to pass only the TreeNode and not the TreeView to the HideCheckBox method. The TreeView can be retrieved from the TreeNode itself:

TreeView tvw = node.TreeView;
Anne
  • 26,765
  • 9
  • 65
  • 71
Subodh Singh
  • 329
  • 3
  • 4