What this code do:
If parent node is checked/unchecked, also check/uncheck all child nodes.
If just one child node is checked, also check parent node.
Private Sub TreeView1_AfterCheck(sender As Object, e As TreeViewEventArgs) Handles TreeView1.AfterCheck
If updatingTreeView Then Return
updatingTreeView = True
CheckNode(e.Node, e.Node.Checked)
HasCheckedChildNode = 0
updatingTreeView = False
End Sub
Private Sub CheckNode(node As TreeNode, isChecked As Boolean)
If node.Parent IsNot Nothing Then
HasCheckedNode(node.Parent)
If Not isChecked And HasCheckedChildNode > 0 Then Return
node.Parent.Checked = isChecked
ElseIf node.Parent Is Nothing Then
For Each cn As TreeNode In node.Nodes
cn.Checked = isChecked
Next
End If
End Sub
Private Sub HasCheckedNode(node As TreeNode)
For Each cn As TreeNode In node.Nodes
If cn.Checked = True Then
HasCheckedChildNode += 1
ElseIf cn.Checked = False Then
HasCheckedChildNode -= 0
End If
Next
End Sub
This code works fine.
Problem:
When I clicks quickly some of the checkboxes are checked and some no E.g. Sometimes I checked the parent node but all child nodes still remain unchecked. Sometimes the parent node is unchecked but its child nodes still checked.
Please check the example image:
How to solve this, is this a problem with my PC?
That happens because the TreeView by default doesn't toggle the Check
property of the TreeNode objects on mouse double click over the check box area. You need to intercept the WM_LBUTTONDBLCLK
messages, get TreeViewHitTestInfo
of the double clicked point, and toggle the Check
property if the double clicked point is over the check box.
Here's a custom TreeView for that, also it solves the main issue, checking/unchecking the parent and child nodes of the branch, just enable the AutoCheckParents
and/or AutoCheckChildren
properties for that.
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
<DesignerCategory("Code")>
Public Class DoubleClickCheckTreeView
Inherits TreeView
#Region "Properties"
<Category("Behavior"),
DefaultValue(False)>
Public Property AutoCheckParents As Boolean = False
<Category("Behavior"),
DefaultValue(False)>
Public Property AutoCheckChildren As Boolean = False
#End Region
#Region "Overrides"
'Enable DoubleBuffered to reduce the flickering.
Protected Overrides Sub OnHandleCreated(e As EventArgs)
SendMessage(Handle,
TVM_SETEXTENDEDSTYLE,
IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER),
IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER))
MyBase.OnHandleCreated(e)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WM_LBUTTONDBLCLK AndAlso CheckBoxes Then
Dim x As Integer = m.LParam.ToInt32() And &HFFFF
Dim y As Integer = (m.LParam.ToInt32 >> 16) And &HFFFF
Dim ht As TreeViewHitTestInfo = HitTest(x, y)
If ht.Node IsNot Nothing AndAlso
ht.Location = TreeViewHitTestLocations.StateImage Then
OnBeforeCheck(New TreeViewCancelEventArgs(ht.Node,
False,
TreeViewAction.ByMouse))
ht.Node.Checked = Not ht.Node.Checked
OnAfterCheck(New TreeViewEventArgs(ht.Node, TreeViewAction.ByMouse))
m.Result = IntPtr.Zero
Return
End If
End If
MyBase.WndProc(m)
End Sub
Protected Overrides Sub OnAfterCheck(e As TreeViewEventArgs)
MyBase.OnAfterCheck(e)
If e.Action = TreeViewAction.Unknown OrElse
Not CheckBoxes Then Return
If AutoCheckParents Then
Dim p = e.Node.Parent
While p IsNot Nothing
p.Checked = p.Nodes.Cast(Of TreeNode).Any(Function(x) x.Checked)
p = p.Parent
End While
End If
If AutoCheckChildren Then
For Each tn As TreeNode In GetNodes(e.Node)
tn.Checked = e.Node.Checked
Next
End If
End Sub
#End Region
#Region "Private Methods"
Private Iterator Function GetNodes(node As TreeNode) As IEnumerable(Of TreeNode)
For Each n As TreeNode In node.Nodes
Yield n
For Each c As TreeNode In GetNodes(n)
Yield c
Next
Next
End Function
#End Region
#Region "API"
Private Const TVM_SETEXTENDEDSTYLE As Integer = &H1100 + 44
Private Const TVM_GETEXTENDEDSTYLE As Integer = &H1100 + 45
Private Const TVS_EX_DOUBLEBUFFER As Integer = &H4
Private Const WM_LBUTTONDBLCLK As Integer = &H203
<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr,
ByVal msg As Integer,
ByVal wp As IntPtr,
ByVal lp As IntPtr) As IntPtr
End Function
#End Region
End Class
Related
◉ AfterCheck and AfterSelect events in TreeView, Windows Forms (c#)