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