TreeView Remove CheckBox by some Nodes
Asked Answered
A

4

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!

Audryaudrye answered 28/1, 2011 at 9:17 Comment(2)
possible duplicate of I am looking for a good resource on ownerdrawn treeviewsOlympe
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, complete with the node lines.Cordovan
C
75

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

Cordovan answered 28/1, 2011 at 9:42 Comment(8)
@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.Cordovan
@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.Cordovan
Check this answer for a solution to a failure mode of this code: #7308738Olympe
I'm getting a NullReferenceExeption on node.Handle. Object reference not set to an instace of an object.. Any tips?Lanky
@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 readingCordovan
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#.Wellington
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.Wellington
@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)Entwine
U
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);
    }
}
Uintathere answered 6/3, 2014 at 16:30 Comment(1)
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.Garratt
I
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;
Illogicality answered 18/9, 2012 at 19:6 Comment(0)
E
0

One thing that isn't mentioned in any of the answers is that it's possible for the checkbox to reappear when pressing space on the selected node. This means we need to handle the TreeView.BeforeCheck event in order to cancel the check before the state changes, and the checkbox is re-added.

If we know exactly what nodes have the checkbox hidden, then we can just add the event and be done. The e.Action condition here is only important if we're using IsCheckBoxHidden() to avoid unecessary P/Invoke when you're mass-checking tree view nodes. If we're not using P/Invoke, then the logic is simple enough that it's not worth checking.

private void MyForm_Load(object sender, EventArgs e)
{
    this.treeview1.BeforeCheck += new TreeViewCancelEventHandler(treeview1_BeforeCheck);
}

private void treeview1_BeforeCheck(object sender, TreeViewCancelEventArgs e)
{
    // ByKeyboard is used, since its the one action we don't have control over,
    // We can also handle TreeViewAction.Unknown (for when programmatically checking)
    if (e.Action == TreeViewAction.ByKeyboard)
    {
        // If we know exactly what has its checkbox hidden
        //if (e.Node.Level == 1)
        if (e.Node.IsCheckBoxHidden())
        {
            // Prevent the node from being checked and the checkbox from reappearing
            e.Cancel = true;
        }
    }
}

If you can't immediately tell which node has its checkbox hidden, then we can find that out with more P/Invoke.

private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_GETITEM = TV_FIRST + 62;

[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>
/// Returns true if the checkbox is hidden for the specified node on a TreeView control.
/// </summary>
public static bool IsCheckBoxHidden(this TreeNode node)
{
    TVITEM tvi = new TVITEM();
    tvi.hItem = node.Handle;
    tvi.mask = TVIF_STATE;
    tvi.stateMask = TVIS_STATEIMAGEMASK;
    SendMessage(node.TreeView.Handle, TVM_GETITEM, IntPtr.Zero, ref tvi);
    // State image values are shifted by 12. 0 = no checkbox, 1 = unchecked, 2 = checked
    var stateImage = tvi.state >> 12;
    return stateImage == 0;
}

Alternatively, instead of TVM_GETITEM, you can use TVM_GETITEMSTATE. Both get the job done, but the later requires another overload for SendMessage.

private const int TVM_GETITEMSTATE = TV_FIRST + 39;

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

/// <summary>
/// Returns true if the checkbox is hidden for the specified node on a TreeView control.
/// </summary>
public static bool IsCheckBoxHidden(this TreeNode node)
{
    var stateMask = new IntPtr(TVIS_STATEIMAGEMASK);
    var state = SendMessage(node.TreeView.Handle, TVM_GETITEMSTATE, node.Handle, stateMask).ToInt32();
    // State image values are shifted by 12. 0 = no checkbox, 1 = unchecked, 2 = checked
    var stateImage = state >> 12;
    return stateImage == 0;
}
Eldwon answered 4/10, 2023 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.