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:
- 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. - 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:
- Make parent
unHidden
too. - We can change node’s parent to the nearest
unHidden
node in its tree hierarchy (that is, move it somewhere up).
- 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
- This can be solved simply by looking for the previous nearest
unHidden
node, and then we insert currently toggled node after this one. - 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. - 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:
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
P.Hidden(False) = value
P = P.Parent
End If
Loop
End If
If CascadeDown Then CascadeCollection(Me.Nodes, value)
If _Hidden <> value Then
_Hidden = value
If Me.InCollection Then
If _Hidden = True Then
_MyTreeNodeCollection._VisibleNodes.Remove(Me)
Else
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:
Public Delegate Function Selector(Node As cTreeNode) As Boolean
Private Sub _Filter(Filter As Selector, NDC As cTreeNodeCollection)
For Each ND In NDC._ActualNodes
If ND.Nodes._ActualNodes.Count > 0 Then _Filter(Filter, ND.Nodes)
If Filter(ND) Then
ND.Hidden(False) = False
Else
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:
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
:
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