Question Filter treeview on textBox change event

PD1991

Member
Joined
Nov 17, 2021
Messages
18
Programming Experience
1-3
Hello,

How can I filter treeview in winforms on textBox change event using C# ? I want to check hide or remove nodes from treeview If filter string not matched. In my case, treeview may have multiple levels so filter should work at multiple levels.
I found on the solution which matches my requirement but Its in VB.Net (Sign in - CodeProject) and I am finding it hard to convert it into C#.
Any help please.

Code Block 1:
Public Class cTreeNode
    Inherits TreeNode

    Friend _MyTreeNodeCollection As cTreeNodeCollection

    Public Shadows Nodes As New cTreeNodeCollection(MyBase.Nodes)

    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(Text As String)
        MyBase.New(Text)
    End Sub

    Public Sub New(Text As String, Children() As cTreeNode)
        MyBase.New(Text)
        For Each N In Children
            Me.Nodes.Add(N)
        Next
    End Sub

    Public Shadows ReadOnly Property TreeView() As cTreeView
        Get
            Return MyBase.TreeView
        End Get
    End Property


    Private _Hidden As Boolean
    Public Property Hidden(Optional CascadeUp As Boolean = True, Optional CascadeDown As Boolean = False) As Boolean
        Get
            Return _Hidden
        End Get
        Set(ByVal value As Boolean)

            If CascadeUp Then
                Dim P As cTreeNode = MyBase.Parent


                Do While P IsNot Nothing
                    Dim Res = TreeView.CascadeNodeEventRaiser(Me, P)
                    If Res.CancelCascade Then Exit Do
                    If Res.Handled = False Then
                        'we set CascadeUp to false and cycle through all parents manually to be able to pass in CascadeNodeEventRaiser the real originating node of those callings
                        P.Hidden(False) = value
                        P = P.Parent
                    End If
                Loop
                'End If
            End If

            If CascadeDown Then CascadeCollection(Me.Nodes, value)

            'Do nothing if value didn't really changed, to increase performance.
            If _Hidden <> value Then
                _Hidden = value

                If Me.InCollection Then

                    If _Hidden = True Then
                        _MyTreeNodeCollection._VisibleNodes.Remove(Me)
                    Else
                        'if we making the node visible, put it after closer visible node
                        If Me.PreviousUnHidenNode Is Nothing Then
                            _MyTreeNodeCollection._VisibleNodes.Insert(0, Me)
                        Else
                            _MyTreeNodeCollection._VisibleNodes.Insert(Me.PreviousUnHidenNode.VisibilityIndex + 1, Me)
                        End If
                    End If
                End If

            End If

        End Set
    End Property

    'This is a recruceve procedure that will cascade down all nodes
    Private Sub CascadeCollection(NDC As cTreeNodeCollection, IsHidden As Boolean)
        For Each N As cTreeNode In NDC
            Dim Res = TreeView.CascadeNodeEventRaiser(Me, N)
            If Res.CancelCascade Then Exit For
            If Res.Handled = False Then
                N.Hidden(False, False) = IsHidden
                If N.Nodes.Count > 0 Then CascadeCollection(N.Nodes, IsHidden)
            End If
        Next
    End Sub


    'This will return the closest previous unHidden node
    Friend ReadOnly Property PreviousUnHidenNode() As cTreeNode
        Get
            If Me.Index = 0 Then
                Return Nothing
            Else
                If Me.InCollection = False Then
                    Throw New Exception("This node is not assigned to a TreeNodeCollection yet.")
                Else
                    For i = Me.Index - 1 To 0 Step -1
                        If _MyTreeNodeCollection(i).Hidden = False Then Return _MyTreeNodeCollection(i)
                    Next
                End If
            End If
        End Get
    End Property

    'This will return node index as it visible on TreeView
    Public ReadOnly Property VisibilityIndex As Integer
        Get
            Return MyBase.Index
        End Get
    End Property


    'This will return node index as it is in the Actual node list
    Public Shadows ReadOnly Property Index As Integer
        Get
            If Me.InCollection Then
                Return _MyTreeNodeCollection._ActualNodes.IndexOf(Me)
            Else
                Throw New Exception("This node is not assigned to a TreeNodeCollection yet.")
            End If
        End Get
    End Property


    'just a help property to check if tree node assigned to some tree collection
    Friend ReadOnly Property InCollection As Boolean
        Get
            Return _MyTreeNodeCollection IsNot Nothing
        End Get
    End Property

End Class

Code Block 2:
Public Class cTreeNodeCollection
    'Make class usable with For...Each
    Implements IEnumerable

    Friend _VisibleNodes As TreeNodeCollection
    Friend _ActualNodes As New List(Of cTreeNode)

    Friend Sub New(TreeNodeCollection As TreeNodeCollection)
        _VisibleNodes = TreeNodeCollection
    End Sub

    'This is to mimic original TreeNodeCollection behavior
    Default Public ReadOnly Property Items(Index As Integer) As cTreeNode
        Get
            Return _ActualNodes(Index)
        End Get
    End Property

    Public Sub Add(Node As cTreeNode)
        Node._MyTreeNodeCollection = Me
        _ActualNodes.Add(Node)
        'if the node is Hidden, there is no need to add it to TreeNodeCollection
        If Node.Hidden = False Then _VisibleNodes.Add(Node)
    End Sub

    Public Sub AddRange(Nodes() As cTreeNode)
        For Each N In Nodes
            Add(N)
        Next
    End Sub

    Public Sub Remove(Node As cTreeNode)
        Node._MyTreeNodeCollection = Nothing
        _ActualNodes.Remove(Node)
        _VisibleNodes.Remove(Node)
    End Sub

    Public ReadOnly Property Count As Integer
        Get
            Return _ActualNodes.Count
        End Get
    End Property

    Public ReadOnly Property VisibleCount As Integer
        Get
            Return _VisibleNodes.Count
        End Get
    End Property

    Public Sub Insert(Node As cTreeNode, Index As Integer)
        _ActualNodes.Insert(Index, Node)
        If Node.Hidden = False Then
            'If there is no unHidden nodes at same level, add it just at the beginning
            If Node.PreviousUnHidenNode Is Nothing Then
                _VisibleNodes.Insert(0, Node)
            Else
                _VisibleNodes.Insert(Node.PreviousUnHidenNode.VisibilityIndex, Node)
            End If
        End If
    End Sub

#Region "Implementing IEnumerable"

    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return New cMyEnumerator(_ActualNodes)
    End Function

    Private Class cMyEnumerator
        Implements IEnumerator

        Public Nodes As List(Of cTreeNode)
        Private position As Integer = -1

        'constructor
        Public Sub New(ByVal ActualNodes As List(Of cTreeNode))
            Nodes = ActualNodes
        End Sub

        Private Function getEnumerator() As IEnumerator
            Return DirectCast(Me, IEnumerator)
        End Function

        'IEnumerator
        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            position += 1
            Return (position < Nodes.Count)
        End Function

        'IEnumerator
        Public Sub Reset() Implements IEnumerator.Reset
            position = -1
        End Sub

        'IEnumerator
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                Try
                    Return Nodes(position)

                Catch e1 As IndexOutOfRangeException
                    Throw New InvalidOperationException()
                End Try
            End Get
        End Property
    End Class 'end nested class

#End Region

End Class
Code Block 3:
Public Class cTreeView
    Inherits Windows.Forms.TreeView

#Region "Cascade Event"

    Public Event CascadeNode(sender As Object, e As EventArgs)

    Friend Function CascadeNodeEventRaiser(NDSource As cTreeNode, NDCurrent As cTreeNode) As CascadeNodeEventArgs
        Dim EA = New CascadeNodeEventArgs(NDSource, NDCurrent)
        RaiseEvent CascadeNode(Me, EA)
        Return EA
    End Function

    Public Class CascadeNodeEventArgs
        Inherits EventArgs

        Public Sub New(NDSource As cTreeNode, NDCurrent As cTreeNode)
            _CascadeNode = NDCurrent
            _TrigerNode = NDSource
        End Sub

        'If this set to True, the whole cascading operation will be canceled.
        Private _CancelCascade As Boolean
        Public Property CancelCascade() As Boolean
            Get
                Return _CancelCascade
            End Get
            Set(ByVal value As Boolean)
                _CancelCascade = value
            End Set
        End Property

        Private _CascadeNode As cTreeNode
        Public ReadOnly Property CascadeNode() As cTreeNode
            Get
                Return _CascadeNode
            End Get
        End Property

        Private _TrigerNode As cTreeNode
        Public ReadOnly Property TrigerNode() As cTreeNode
            Get
                Return _TrigerNode
            End Get
        End Property

        Private _Handled As Boolean
        Public Property Handled() As Boolean
            Get
                Return _Handled
            End Get
            Set(ByVal value As Boolean)
                _Handled = value
            End Set
        End Property


    End Class

#End Region

    'Redefining native Node collection
    Public Shadows ReadOnly Nodes As New cTreeNodeCollection(MyBase.Nodes)

#Region "Filtering"

    'delegate that will decide what to keep visible
    Public Delegate Function Selector(Node As cTreeNode) As Boolean


    Public Shadows Property SelectedNode() As cTreeNode
        Get
            Return MyBase.SelectedNode
        End Get
        Set(ByVal value As cTreeNode)
            MyBase.SelectedNode = value
        End Set
    End Property


    Public Sub Filter(Filter As Selector)
        _Filter(Filter, Me.Nodes)
    End Sub


    'The recursive Filtering procedure
    Private Sub _Filter(Filter As Selector, NDC As cTreeNodeCollection)
        For Each ND In NDC._ActualNodes
            'Before anything else, if we have subNodes, go and filter them first
            If ND.Nodes._ActualNodes.Count > 0 Then _Filter(Filter, ND.Nodes)

            If Filter(ND) Then
                ND.Hidden(False) = False
            Else
                'if current node has at least one visible node after filtering, make the parent visible too
                ND.Hidden(False) = ND.Nodes._VisibleNodes.Count = 0
            End If

        Next
    End Sub

#End Region

End Class
 
Last edited:

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
5,948
Location
Chesapeake, VA
Programming Experience
10+
Post the specific bit of the VB.NET code that you need help translating. Please post the code as text in code tags -- not as a screenshot.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
5,948
Location
Chesapeake, VA
Programming Experience
10+
*sigh* Now my post #2 looks like I'm on drugs because you changed your original post by adding in the code that I requested.

Anyway, what specific bit there are you having issues with translating? What VB.NET concept do you not understand that you need to convert to C#?

As an aside, both VB.NET and C# compile to the same IL and are interoperable. If you can use the VB.NET control as-is, why even translate to C#?
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
4,715
Location
Sydney, Australia
Programming Experience
10+
I am finding it hard to convert it into C#.
How EXACTLY are you finding it difficult? As always, explain exactly what you've done and exactly what you're having trouble with. If you don't want to do it all by hand, there are plenty of online code converters that will get you at least part of the way, plus you can install Instant C# from Tangible Software Solutions and do the conversion locally with even greater accuracy. Just dumping a huge wad of code and effectively saying "convert this for me" is not what this site is for.
 

PD1991

Member
Joined
Nov 17, 2021
Messages
18
Programming Experience
1-3
I am now using the VB.Net version in my application. But I am having around 70000 nested nodes which on filter taking 3 to 4 minutes to filter the treeview.
Is their any issue with _Filter code in code block 3 which results into this much time ?
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
5,948
Location
Chesapeake, VA
Programming Experience
10+
Converting to C# won't make the code any faster. As I previously mentioned the code will compile to the same IL.

Anyway, recursively going through 70,000 nodes is going to take time. Even worse, it's going to take time if the UI is updated each time a node's visibility state is changed.

Personally, I feel that you have the wrong UI paradigm if you are trying to get the user to filter through a hierarchical data that contains 70,0000 items. It may make more sense to have the user filter through a list of 70,000 items, and then as a secondary step, present that data into a tree, instead of starting with a tree, and then knocking nodes out of that tree.
 
Top Bottom