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?
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);
}
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With