Click here to Skip to main content
16,012,107 members
Articles / Programming Languages / Visual Basic
Tip/Trick

Filtering and Hiding Tree Nodes (WinForms)

Rate me:
Please Sign up or sign in to vote.
4.06/5 (8 votes)
13 Jun 2015CPOL6 min read 33.3K   1.8K   6   1
Hiding/Showing TreeNodes and Filtering them in Winforms TreeView Control

Image 1

Introduction

The question of how to hide tree node in TreeView control has been raised many times on Internet forums and other related websites, even so many people introduced solutions on how to “mimic” this behaviour (mimic because TreeView natively doesn’t support such a behaviour even through win API). However, no real life implementation is available on the internet (freely at least), maybe because any realistic implementing to this, as I will outline below, has some nasty issues, that has no elegant solution.

In other hand, having such a property is very useful, because it allows you to “filter” TreeView and also searching in it, in the same time it will preserve the structure of found elements, and user will see to which group the founded elements belongs, the thing that in some applications is very essential.

Implementation

First obstacle that you may face in implementing this is that Microsoft made no constructors for TreeNodeCollection, thus it can’t be inherited, this of course is a solvable issue, but it makes the solution “dirtier”.

The most obvious solution, is to maintain in background two lists of tree nodes, one that will represent the visible nodes (which will be actually just native TreeNodeCollection), and a second one is a list of all nodes, and obviously, those two lists should be synchronized in their structure.

Because we need adding, removing, inserting, etc. procedures to be handled internally, we can’t expose directly actual node collection to outer user, and because we can’t inherit directly from native node collation, we will need to also implement IEnumerable to enable user to use For ...Each.

Also, no doubt that we also need to have a low performance impact on TreeView, for that we need to avoid some of the proposed solutions on the internet to re-populate TreeView each time you change visibility or you add/remove some node, thus we need to make changes only to the node under consideration, not all of them. Also, I tried to make all additions such that they will be backward compatible with native TreeView.

Because native tree nodes has properties like NextVisibleNode, and to avoid confusion, I will use to show/hide nodes in this article the word “Hidden”/”unHidden”, hope this will not cause problems.

Conceptual Issues

Even so, it may look that adding such a simple property as node visibility is harmless, a closer look will show you that it actually raises couple of issues that need to be dirty-tweaked, and maybe this is the main reason why the original TreeView doesn’t implement it:

  1. Node Position: When we make the node unHidden again, where should it appear? In this sense, there may be other “near” nodes which are hidden, and obviously we had to maintain original node sequence.
  2. Cascade Visibility: What if you turn node to be unHidden in the moment that its parent is Hidden! What should we do in this case? Generally speaking, we have two totally different options:
    1. Make parent unHidden too.
    2. We can change node’s parent to the nearest unHidden node in its tree hierarchy (that is, move it somewhere up).
  3. Reverse Cascade Visibility: If you make some node to become Hidden, and this node is the only subNode in its parent’s node collection, should we keep all parent nodes (up to its root) unHidden? Or should we make all of them Hidden? (This is similar to the behaviour of MiniTree functionality in XYplorer’s folder tree).

Solutions

  1. This can be solved simply by looking for the previous nearest unHidden node, and then we insert currently toggled node after this one.
  2. Obviously, one can solve this by adding a parameter to Hidden property (I preferred to include the parameter in this property instead of making it as a new property for TreeView, this will make it more flexible, because maybe you want to trigger chaining differently for different nodes types) that will indicate if all parent nodes (up till root node) should inherit the unHidden state. Regarding implementing the second option, I will come back to this below.
  3. To solve this one, we can use the same above introduced parameter, it will tell us if all parents should be hidden too or not, obviously, this parameter will be effective only in case the current node has no near nodes of the same level as stated before.

Coming back to changing node’s parent option, the problem with this is that such an option will force us to de-synchronize our two node lists structure, then list of visible nodes will contain not the same nodes that will be in actual list of nodes, this will make things much more complex, especially that you may need some nodes (folders for instance) always visible, so the solution highly depends on your data model, and there is no One-For-All solution for this case, thus I will not implement it here, however you can easily modify code to achieve this.

So, I added two parameters to Hiding property: CascadeUp and Cascadedown, they will push Hidden status of current node up till root or down nodes, also because one may need more control on visibility of some nodes (Folders for example), I added an event <font face="Consolas" size="2"><font face="Consolas" size="2">CascadeNode </font></font>that will be raised and will give you the opportunity to change things, below is a code of only Hidden property:

VB.NET
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

Filtering

Because implementing filtering can seem at first glance to be complicated, I added it to the code.
Major problem that you can face when you want to filter, is if you have some node say “Parent”, that has two children, “Child 1”, and “Child 2”, then we start to filter, and you find that “Parent” node is not up to your search criteria, so you hide it, then you will check “Child 1” and will find out that it should be visible, thus you chain and makes “Parent” node visible too, however then you find that “Child 2” doesn’t satisfy criteria too, so “Parent” could be hidden if we haven’t had “Child 1” in the “middle”!

So clearly, we actually need to start filtering from the deepest levels of nodes, not from root, and once we filter the deepest level, we decide if the parent should be visible too, even if it will not satisfy search criteria.

This actually can be done very easily, using recursive functions, that starting from the root, will load itself for deeper and deeper layers, and once it will reach the deepest one, it will start to filter as follows:

VB.NET
'delegate that will decide what to keep visible
 Public Delegate Function Selector(Node As cTreeNode) As Boolean
  
'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

Using the Code

Using this is straightforward, same as for usual TreeView, to use filtering, just create a delegate of Selector type, for example:

VB.NET
Public Function Selector(ND As cTreeNode) As Boolean
     If ND.Text Like String.Format("{0}{1}{0}", "*", TextBox1.Text) Then Return True
End Function

And when you typing something in FilterBox:

VB.NET
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
     Me.CTreeView1.Filter(AddressOf Selector)
End Sub

This will call the selector to decide if the Node will be hidden or not.

Points of Interest

As you know, there are a lot of overloads for Add, Insert... etc. functions in TreeView, I encapsulated just few of them, depending on what you need, you may do it for the rest.

Some other properties that I added:

  • CascadeNode event has CascadeNodeEventArgs that has the following properties that can be used when deciding what to do with cascading:
    • CascadeNode The node that control is trying to cascade.
    • TrigerNode The node that triggered cascading.
    • Handled This is to tell the control that you took care of Hiding or showing CascadeNode node.
    • CancelCascade Stops cascading procedure starting from current Node.
  • cTreeNode has the following additional properties (Friend):
    • PreviousUnHidenNode Returns the first previous node that is visible (unHidden).
    • VisibilityIndex Returns node index in among visible Nodes.
    • Index Returns node index in full node list.
    • InCollection Returns true if the node assigned to some TreeCollection.

History

  • 13.06.2015 Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionVery nice project Pin
Member 139633807-Sep-18 21:52
Member 139633807-Sep-18 21:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.