Search code examples
vb.netvisual-studio-2015treeview

CheckBox not work properly when clicked on quickly multiple times


What this code do:

  1. If parent node is checked/unchecked, also check/uncheck all child nodes.

  2. 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:

this

How to solve this, is this a problem with my PC?


Solution

  • 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
    
    • Add a new class to your project and paste this code.
    • Rebuild.
    • Drop an instance of the DoubleClickCheckTreeView or change the type of the existing default TreeView in the Designer.

    SOQ61299089


    Related


    AfterCheck and AfterSelect events in TreeView, Windows Forms (c#)