Introduction
This code build a TreeView
with three state checkboxes.
Using the code
Build a "component class" in your project and insert this code, and use this class to build a TreeView.
Imports System.Runtime.InteropServices
Friend Enum CheckBoxState
None = 0
Unchecked = 1
Checked = 2
Indeterminate = 3
Path = 4
End Enum
Friend Class TriStateTreeView
Inherits System.Windows.Forms.TreeView
Private m_SelectPath As Boolean
Private m_SelectChildren As Boolean
<StructLayout(LayoutKind.Sequential)> _
Private Structure TVITEM
Public mask As Integer
Public hItem As IntPtr
Public state As Integer
Public stateMask As Integer
Public pszText As Integer
Public cchTextMax As Integer
Public iImage As Integer
Public iSelectedImage As Integer
Public cChildren As Integer
Public lParam As Integer
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure POINTAPI
Public x As Integer
Public y As Integer
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure TVHITTESTINFO
Public pt As POINTAPI
Public flags As Integer
Public hItem As IntPtr
End Structure
Private Const TV_FIRST As Integer = &H1100
Private Const TVM_SETIMAGELIST As Integer = TV_FIRST + 9
Private Const TVM_GETITEM As Integer = TV_FIRST + 12
Private Const TVM_SETITEM As Integer = TV_FIRST + 13
Private Const TVM_HITTEST As Integer = TV_FIRST + 17
Private Const TVSIL_STATE As Integer = 2
Private Const TVIF_STATE As Integer = &H8
Private Const TVIF_HANDLE As Integer = &H10
Public Const TVIS_STATEIMAGEMASK As Integer = &HF000
Public Const TVHT_ONITEMSTATEICON As Integer = &H40
Private Const m_IMG_CHECKBOX_NONE As Integer = 0
Private Const m_IMG_CHECKBOX_UNCHECKED As Integer = 1
Private Const m_IMG_CHECKBOX_CHECKED As Integer = 2
Private Const m_IMG_CHECKBOX_INDETERMINATE As Integer = 3
Private Const m_IMG_CHECKBOX_PATH As Integer = 4
Private Overloads Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer
Private Overloads Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByRef lParam As TVITEM) As Integer
Private Overloads Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByRef lParam As TVHITTESTINFO) As Integer
Friend Sub New(ByVal ctlStateImageList As ImageList, ByVal selectPath As Boolean, _
ByVal selectChildren As Boolean)
m_SelectPath = selectPath
m_SelectChildren = selectChildren
Dim iResult As Integer
iResult = SendMessage(Me.Handle, TVM_SETIMAGELIST, _
TVSIL_STATE, ctlStateImageList.Handle)
End Sub
Private Sub SetTreeNodeAndChildrenStateRecursively(ByVal objTreeNode _
As TreeNode, ByVal eCheckBoxState As CheckBoxState)
Dim objChildTreeNode As TreeNode
If Not (objTreeNode Is Nothing) Then
SetTreeNodeState(objTreeNode, eCheckBoxState)
If m_SelectChildren Then
For Each objChildTreeNode In objTreeNode.Nodes
SetTreeNodeAndChildrenStateRecursively(objChildTreeNode, eCheckBoxState)
Next
End If
End If
End Sub
Private Sub SetParentTreeNodeStateRecursively(ByVal objParentTreeNode As TreeNode)
Dim objTreeNode As TreeNode
Dim eCheckBoxState As CheckBoxState
Dim bAllChildrenChecked As Boolean = True
Dim bAllChildrenUnchecked As Boolean = True
If Not (objParentTreeNode Is Nothing) Then
If GetTreeNodeState(objParentTreeNode) <> CheckBoxState.None Then
For Each objTreeNode In objParentTreeNode.Nodes
eCheckBoxState = GetTreeNodeState(objTreeNode)
Select Case eCheckBoxState
Case CheckBoxState.Checked
bAllChildrenUnchecked = False
Case CheckBoxState.Indeterminate
bAllChildrenUnchecked = False
bAllChildrenChecked = False
Case CheckBoxState.Unchecked
bAllChildrenChecked = False
Case CheckBoxState.Path
bAllChildrenUnchecked = False
End Select
If bAllChildrenChecked = False And bAllChildrenUnchecked = False Then
Exit For
End If
Next
If bAllChildrenChecked Then
SetTreeNodeState(objParentTreeNode, CheckBoxState.Path)
ElseIf bAllChildrenUnchecked Then
SetTreeNodeState(objParentTreeNode, CheckBoxState.Unchecked)
Else
SetTreeNodeState(objParentTreeNode, CheckBoxState.Path)
End If
If Not (objParentTreeNode.Parent Is Nothing) Then
SetParentTreeNodeStateRecursively(objParentTreeNode.Parent)
End If
End If
End If
End Sub
Friend Function GetTreeNodeState(ByVal objTreeNode As TreeNode) As CheckBoxState
Dim eCheckBoxState As CheckBoxState = CheckBoxState.None
Dim tTVITEM As TVITEM
Dim iState As Integer
Dim iResult As Integer
tTVITEM.mask = TVIF_HANDLE Or TVIF_STATE
tTVITEM.hItem = objTreeNode.Handle
tTVITEM.stateMask = TVIS_STATEIMAGEMASK
tTVITEM.state = 0
iResult = SendMessage(Me.Handle, TVM_GETITEM, 0, tTVITEM)
If iResult <> 0 Then
iState = tTVITEM.state
iState = iState \ &HFFF
Select Case iState
Case m_IMG_CHECKBOX_NONE
eCheckBoxState = CheckBoxState.None
Case m_IMG_CHECKBOX_UNCHECKED
eCheckBoxState = CheckBoxState.Unchecked
Case m_IMG_CHECKBOX_CHECKED
eCheckBoxState = CheckBoxState.Checked
Case m_IMG_CHECKBOX_INDETERMINATE
eCheckBoxState = CheckBoxState.Indeterminate
Case m_IMG_CHECKBOX_PATH
eCheckBoxState = CheckBoxState.Path
End Select
End If
Return eCheckBoxState
End Function
Friend Sub SetTreeNodeState(ByVal objTreeNode As TreeNode, _
ByVal eCheckBoxState As CheckBoxState)
Dim iImageIndex As Integer
Dim tTVITEM As TVITEM
Dim iResult As Integer
If Not (objTreeNode Is Nothing) Then
Select Case eCheckBoxState
Case CheckBoxState.None
iImageIndex = m_IMG_CHECKBOX_NONE
Case CheckBoxState.Unchecked
iImageIndex = m_IMG_CHECKBOX_UNCHECKED
Case CheckBoxState.Checked
iImageIndex = m_IMG_CHECKBOX_CHECKED
Case CheckBoxState.Indeterminate
iImageIndex = m_IMG_CHECKBOX_INDETERMINATE
Case CheckBoxState.Path
iImageIndex = m_IMG_CHECKBOX_PATH
End Select
tTVITEM.mask = TVIF_HANDLE Or TVIF_STATE
tTVITEM.hItem = objTreeNode.Handle
tTVITEM.stateMask = TVIS_STATEIMAGEMASK
tTVITEM.state = iImageIndex * &H1000
iResult = SendMessage(Me.Handle, TVM_SETITEM, 0, tTVITEM)
End If
End Sub
Private Sub ToggleTreeNodeState(ByVal objTreeNode As TreeNode)
Dim eCheckBoxState As CheckBoxState
eCheckBoxState = GetTreeNodeState(objTreeNode)
Me.BeginUpdate()
Select Case eCheckBoxState
Case CheckBoxState.Unchecked
SetTreeNodeAndChildrenStateRecursively(objTreeNode, CheckBoxState.Checked)
If m_SelectPath Then
SetParentTreeNodeStateRecursively(objTreeNode.Parent)
End If
Case CheckBoxState.Checked
SetTreeNodeAndChildrenStateRecursively(objTreeNode, _
CheckBoxState.Indeterminate)
If m_SelectPath Then
SetParentTreeNodeStateRecursively(objTreeNode.Parent)
End If
Case CheckBoxState.Indeterminate
SetTreeNodeAndChildrenStateRecursively(objTreeNode, CheckBoxState.Unchecked)
If m_SelectPath Then
SetParentTreeNodeStateRecursively(objTreeNode.Parent)
End If
End Select
Me.EndUpdate()
End Sub
Private Function GetTreeNodeHitAtCheckBoxByScreenPosition(ByVal iXScreenPos _
As Integer, ByVal iYScreenPos As Integer) As TreeNode
Dim objClientPoint As Point
Dim objTreeNode As TreeNode
objClientPoint = Me.PointToClient(New Point(iXScreenPos, iYScreenPos))
objTreeNode = GetTreeNodeHitAtCheckBoxByClientPosition(objClientPoint.X, _
objClientPoint.Y)
Return objTreeNode
End Function
Private Function GetTreeNodeHitAtCheckBoxByClientPosition(ByVal iXClientPos _
As Integer, ByVal iYClientPos As Integer) As TreeNode
Dim objTreeNode As TreeNode = Nothing
Dim iTreeNodeHandle As Integer
Dim tTVHITTESTINFO As TVHITTESTINFO
tTVHITTESTINFO.pt.x = iXClientPos
tTVHITTESTINFO.pt.y = iYClientPos
iTreeNodeHandle = SendMessage(Me.Handle, TVM_HITTEST, 0, tTVHITTESTINFO)
If iTreeNodeHandle <> 0 Then
If (tTVHITTESTINFO.flags And TVHT_ONITEMSTATEICON) <> 0 Then
objTreeNode = TreeNode.FromHandle(Me, New IntPtr(iTreeNodeHandle))
End If
End If
Return objTreeNode
End Function
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
Dim objTreeNode As TreeNode
MyBase.OnMouseUp(e)
objTreeNode = GetTreeNodeHitAtCheckBoxByClientPosition(e.X, e.Y)
If Not (objTreeNode Is Nothing) Then
ToggleTreeNodeState(objTreeNode)
End If
End Sub
Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs)
MyBase.OnKeyUp(e)
If e.KeyCode = Keys.Space Then
If Not (Me.SelectedNode Is Nothing) Then
ToggleTreeNodeState(Me.SelectedNode)
End If
End If
End Sub
Protected Overrides Sub OnBeforeExpand(ByVal e As _
System.Windows.Forms.TreeViewCancelEventArgs)
If Not (GetTreeNodeHitAtCheckBoxByScreenPosition(Control.MousePosition.X, _
Control.MousePosition.Y) Is Nothing) Then
e.Cancel = True
End If
End Sub
Protected Overrides Sub OnBeforeCollapse(ByVal e As _
System.Windows.Forms.TreeViewCancelEventArgs)
If Not (GetTreeNodeHitAtCheckBoxByScreenPosition(Control.MousePosition.X, _
Control.MousePosition.Y) Is Nothing) Then
e.Cancel = True
End If
End Sub
Friend Function AddTreeNode(ByVal colNodes As TreeNodeCollection, _
ByVal sNodeText As String, ByVal iImageIndex As Integer, _
ByVal eCheckBoxState As CheckBoxState) As TreeNode
Dim objTreeNode As TreeNode
objTreeNode = New TreeNode(sNodeText)
objTreeNode.ImageIndex = iImageIndex
objTreeNode.SelectedImageIndex = iImageIndex
colNodes.Add(objTreeNode)
Me.SetTreeNodeState(objTreeNode, eCheckBoxState)
Return objTreeNode
End Function
Friend Function AddTreeNode(ByVal colNodes As TreeNodeCollection, _
ByVal sNodeText As String, ByVal eCheckBoxState As CheckBoxState) As TreeNode
Dim objTreeNode As TreeNode
objTreeNode = New TreeNode(sNodeText)
colNodes.Add(objTreeNode)
Me.SetTreeNodeState(objTreeNode, eCheckBoxState)
Return objTreeNode
End Function
End Class