MSDN has an article on the same topic which uses recursion as base for iterating through nodes of a treeview. However a quick glance at tree traversal algorithms shows that an iterative approach is entirely possible by implementing a class with IEnumerator interface for the TreeNode object. So here it is -
''' TreeNodesEnumerator - Can be used to iterate through treeview
''' nodes collection. The nodes are visited in depth first manner
''' i.e. all children of a node are visited before visiting siblings.
''' </summary>
Public Class TreeNodesEnumerator
Implements IEnumerable(Of TreeNode)
Implements IEnumerator(Of TreeNode)
''' <summary>
''' Holds the first item of the TreeNodeCollection passed
''' to the constructor. Set to Nothing if collection is empty.
''' </summary>
Private RootNode As TreeNode
''' <summary>
''' Holds the current node.
''' </summary>
Private CurrentNode As TreeNode
''' <summary>
''' Tree traversal requires some kind of stack for holding the
''' state. In recursive algorithms, system stack is used. Here
''' we have to create our own stack.
''' </summary>
''' <remarks></remarks>
Private NodeStack As Stack(Of TreeNode)
''' <summary>
''' Initializes class to start iteration over the Nodes collection
''' of supplied <see cref="TreeView"/> object.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="tree_view"/><c>Is Nothing</c>.
''' </exception>
''' <param name="tree_view">TreeView object to iterate.</param>
Public Sub New(ByVal tree_view As TreeView)
If tree_view Is Nothing Then
Throw New ArgumentNullException("tree")
End If
Me.InitClass(tree_view.Nodes)
End Sub
''' <summary>
''' Initializes class to start iteration over the supplied
''' <see cref="TreeNodeCollection"/> object.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="nodes_collection"/><c>Is Nothing</c>.
''' </exception>
''' <param name="nodes_collection">
''' <see cref="TreeNodeCollection"/> object to iterate.
''' </param>
Public Sub New(ByVal nodes_collection As TreeNodeCollection)
If nodes_collection Is Nothing Then
Throw New ArgumentNullException("nodes")
End If
Me.InitClass(nodes_collection)
End Sub
''' <summary>
''' Initializes all the variables and calls <see cref="Reset"/>.
''' </summary>
''' <param name="nodes_collection">
''' <see cref="TreeNodeCollection"/> object to iterate.
''' </param>
Private Sub InitClass(ByVal nodes_collection As TreeNodeCollection)
Me.NodeStack = New Stack(Of TreeNode)
' If the collection is empty, assign Nothing to the RootNode
If nodes_collection.Count > 0 Then
Me.RootNode = nodes_collection(0)
Else
Me.RootNode = Nothing
End If
Me.Reset()
End Sub
''' <summary>
''' Implements <see cref="Generic.IEnumerator.Current"/> interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>().
''' </exception>
''' <value>
''' Current node in the <see cref="TreeNodeCollection"/>
''' </value>
''' <returns>
''' Current node in the <see cref="TreeNodeCollection"/>
''' </returns>
Public ReadOnly Property Current() As TreeNode _
Implements Generic.IEnumerator(Of TreeNode).Current
Get
If Me.NodeStack Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
Return Me.CurrentNode
End Get
End Property
''' <summary>
''' Implements <see cref="Collections.IEnumerator.Current"/> interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>().
''' </exception>
''' <value>
''' Current node in the <see cref="TreeNodeCollection"/>
''' </value>
''' <returns>
''' Current node in the <see cref="TreeNodeCollection"/>
''' </returns>
Public ReadOnly Property CurrentObject() As Object _
Implements IEnumerator.Current
Get
If Me.NodeStack Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
Return Me.CurrentNode
End Get
End Property
''' <summary>
''' Implements <see cref="Collections.IEnumerator.MoveNext"/>() interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>().
''' </exception>
''' <returns>
''' <c>True</c> if there are nodes remaining in the <see cref="TreeNodeCollection"/>
''' </returns>
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
If Me.NodeStack Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
' When all nodes are visited, the stack becomes empty.
If Me.NodeStack.Count = 0 Then Return False
' Get next node from stack
Me.CurrentNode = Me.NodeStack.Pop()
' First push the sibling (if any) of the node on the stack.
If Me.CurrentNode.NextNode IsNot Nothing Then
Me.NodeStack.Push(Me.CurrentNode.NextNode)
End If
' Then push the first child (if any) of the node on the stack.
If Me.CurrentNode.FirstNode IsNot Nothing Then
Me.NodeStack.Push(Me.CurrentNode.FirstNode)
End If
' As stack is a FIFO, we shall get the child in next call to
' MoveNext. NOTE: If the breadth first iteration needs to be done
' (i.e. all siblings of a node are visited before children) then
' sequence of above two code lines should be reversed.
Return True
End Function
''' <summary>
''' Resets the iterator to start at beginning.
''' </summary>
Public Sub Reset() Implements IEnumerator.Reset
If Me.NodeStack Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
' Clear current status
Me.NodeStack.Clear()
' Push the first node on the stack
If Me.RootNode IsNot Nothing Then
Me.NodeStack.Push(Me.RootNode)
End If
' Set CurrentNode to Nothing indicating
' iterator has not started yet.
Me.CurrentNode = Nothing
End Sub
''' <summary>
''' Implements <see cref="IDisposable.Dispose"/>() interface.
''' Only the internal stack is released. This function is not
''' really necessary but needs to be implemented as part of the
''' <see cref="Generic.IEnumerator"/> interface.
''' </summary>
''' <remarks></remarks>
Public Sub Dispose() Implements IDisposable.Dispose
' Check if the object is already disposed.
If Me.NodeStack IsNot Nothing Then
' Clear the stack & set it to nothing
Me.NodeStack.Clear()
Me.NodeStack = Nothing
' Set all other variables to nothing
Me.RootNode = Nothing
Me.CurrentNode = Nothing
End If
GC.SuppressFinalize(Me)
End Sub
''' <summary>
''' Implements <see cref="Generic.IEnumerable.GetEnumerator"/>() interface.
''' </summary>
''' <returns>Current object is returned.</returns>
Public Function GetEnumerator() As Generic.IEnumerator(Of TreeNode) _
Implements Generic.IEnumerable(Of TreeNode).GetEnumerator
Return Me
End Function
''' <summary>
''' Implements <see cref="Collections.IEnumerable.GetEnumerator"/>() interface.
''' </summary>
''' <returns>Current object is returned.</returns>
Public Function GetEnumeratorObject() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return Me
End Function
End Class
This class reduces the process to a simple For loop like this -
'Do Stuff
Next n
Some features of this class are -
- The
IEnumerableinterface is implemented so that class can easily be used in a For loop. - Direction of traversal is 'Depth First' (i. e. All children of current node before its siblings) which can be changed to 'Breadth First' by simply swapping two lines in
MoveNext()function. - Bad documentation. Well I am still learning to properly document the code :)
Implementing a search iterator
We can use this class as base for iterators of more exotic kind i. e. one which searches for certain nodes in a TreeView. Following class implements a RegEx searcher -
''' <summary>
''' NodeSearcher - Can be used to search text of all nodes
''' in a <see cref="TreeNodeCollection"/> using regular expression.
''' </summary>
Public Class NodeSearcher
Implements IEnumerable(Of TreeNode)
Implements IEnumerator(Of TreeNode)
''' <summary>
''' Our private guide to traverse the entire tree.
''' </summary>
Private NodeEnumerator As TreeNodesEnumerator
''' <summary>
''' Holds the regular expression used for searching the tree
''' </summary>
Private SearchPattern As Regex
''' <summary>
''' Initializes class to search the supplied <see cref="TreeNodeCollection"/>
''' object. Using the supplied <see cref="Regex"/> pattern.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="tree"/><c>Is Nothing</c>.
''' </exception>
''' <param name="nodes"><see cref="TreeNodeCollection"/> object to search.</param>
''' <param name="search_pattern"><see cref="String"/> as
''' <see cref="Regex"/> pattern for searching.</param>
Public Sub New(ByVal nodes As TreeNodeCollection, ByVal search_pattern As String)
Me.InitClass(nodes, New Regex(search_pattern))
End Sub
''' <summary>
''' Initializes class to search the supplied <see cref="TreeNodeCollection"/>
''' object. Using the supplied <see cref="Regex"/> pattern.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="nodes"/><c>Is Nothing</c> or
''' <paramref name="search_pattern"/><c>Is Nothing</c>.
''' </exception>
''' <param name="nodes"><see cref="TreeNodeCollection"/> object to search.</param>
''' <param name="search_pattern"><see cref="Regex"/> pattern for searching.</param>
Public Sub New(ByVal nodes As TreeNodeCollection, ByVal search_pattern As Regex)
Me.InitClass(nodes, search_pattern)
End Sub
''' <summary>
''' Initializes class to search the <see cref="TreeView.Nodes"/>
''' collection of supplied <see cref="TreeView"/> object. Using the
''' supplied <see cref="String"/> as <see cref="Regex"/> pattern.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="tree"/><c>Is Nothing</c>.
''' </exception>
''' <param name="tree"><see cref="TreeView"/> object to search.</param>
''' <param name="search_pattern"><see cref="String"/> as
''' <see cref="Regex"/> pattern for searching.</param>
Public Sub New(ByVal tree As TreeView, ByVal search_pattern As String)
If tree Is Nothing Then Throw New ArgumentNullException("tree")
Me.InitClass(tree.Nodes, New Regex(search_pattern))
End Sub
''' <summary>
''' Initializes class to search the <see cref="TreeView.Nodes"/>
''' collection of supplied <see cref="TreeView"/> object. Using the
''' supplied <see cref="Regex"/> pattern.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="tree"/><c>Is Nothing</c> or
''' <paramref name="search_pattern"/><c>Is Nothing</c>.
''' </exception>
''' <param name="tree"><see cref="TreeView"/> object to iterate.</param>
''' <param name="search_pattern"><see cref="Regex"/> pattern for searching.</param>
Public Sub New(ByVal tree As TreeView, ByVal search_pattern As Regex)
If tree Is Nothing Then Throw New ArgumentNullException("tree")
Me.InitClass(tree.Nodes, search_pattern)
End Sub
''' <summary>
''' Initializes all the variables.
''' </summary>
''' <exception cref="ArgumentNullException">
''' Thrown when <paramref name="nodes"/><c>Is Nothing</c> or
''' <paramref name="search_pattern"/><c>Is Nothing</c>.
''' </exception>
''' <param name="nodes"><see cref="TreeNodeCollection"/> object
''' to search.</param>
''' <param name="search_pattern"><see cref="Regex"/> for
''' searching</param>
Private Sub InitClass(ByVal nodes As TreeNodeCollection, ByVal search_pattern As Regex)
If nodes Is Nothing Then Throw New ArgumentNullException("nodes")
If search_pattern Is Nothing Then Throw New ArgumentNullException("search_pattern")
Me.NodeEnumerator = New TreeNodesEnumerator(nodes)
Me.SearchPattern = search_pattern
End Sub
''' <summary>
''' Implements <see cref="Generic.IEnumerator.Current"/> interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>.
''' </exception>
''' <value>
''' Current node matching the search criteria in <see cref="TreeNodeCollection"/>.
''' <c>Nothing</c> if search has not started or no matching nodes are found.
''' </value>
''' <returns>
''' Current searched node in the <see cref="TreeNodeCollection"/>.
''' </returns>
Public ReadOnly Property Current() As TreeNode Implements _
Generic.IEnumerator(Of TreeNode).Current
Get
If Me.NodeEnumerator Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
Return Me.NodeEnumerator.Current
End Get
End Property
''' <summary>
''' Implements <see cref="Collections.IEnumerator.Current"/> interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>.
''' </exception>
''' <value>
''' Current node matching the search criteria in <see cref="TreeNodeCollection"/>.
''' <c>Nothing</c> if search has not started or no matching nodes are found.
''' </value>
''' <returns>
''' Current searched node in the <see cref="TreeNodeCollection"/>.
''' </returns>
Public ReadOnly Property CurrentObject() As Object Implements IEnumerator.Current
Get
If Me.NodeEnumerator Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
Return Me.NodeEnumerator.CurrentObject
End Get
End Property
''' <summary>
''' Implements <see cref="Generic.IEnumerator.MoveNext"/> interface.
''' </summary>
''' <exception cref="ObjectDisposedException">
''' Thrown if this method is called after calling <see cref="Dispose"/>.
''' </exception>
''' <returns>
''' <c>True</c> if there are nodes to be searched.
''' <c>False</c> if all nodes have been searched.
''' </returns>
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
If Me.NodeEnumerator Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
' Go through the treenodecollection by using the iterator
Do While Me.NodeEnumerator.MoveNext()
' If current node matches search pattern then halt search
' and indicate that a match is found.
If Me.SearchPattern.IsMatch(Me.NodeEnumerator.Current.Text) Then
Return True
End If
Loop
' Entire collection has been searched so return false
Return False
End Function
''' <summary>
''' Implements <see cref="Collections.IEnumerator.Reset"/> interface.
''' </summary>
Public Sub Reset() Implements IEnumerator.Reset
If Me.NodeEnumerator Is Nothing Then
Throw New ObjectDisposedException("TreeNodesEnumerator")
End If
' Call reset of underlying iterator.
Me.NodeEnumerator.Reset()
End Sub
''' <summary>
''' Implements <see cref="Generic.IEnumerator.Dispose"/> interface.
''' </summary>
''' <remarks></remarks>
Public Sub Dispose() Implements IDisposable.Dispose
' If iterator is not already disposed then
If Me.NodeEnumerator IsNot Nothing Then
' Dispose the iterator.
Me.NodeEnumerator.Dispose()
' Clear up variables.
Me.NodeEnumerator = Nothing
Me.SearchPattern = Nothing
GC.SuppressFinalize(Me)
End If
End Sub
''' <summary>
''' Implements <see cref="Generic.IEnumerable.GetEnumerator"/> interface.
''' </summary>
''' <returns>Current object is returned.</returns>
Public Function GetEnumerator() As Generic.IEnumerator(Of TreeNode) _
Implements Generic.IEnumerable(Of TreeNode).GetEnumerator
Return Me
End Function
''' <summary>
''' Implements <see cref="Collections.IEnumerable.GetEnumerator"/> interface.
''' </summary>
''' <returns>Current object is returned.</returns>
Public Function GetEnumeratorObject() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return Me
End Function
End Class
Well this is a very simple class to search through nodes collection using a For loop like this -
For Each n As TreeNode In searcher
'Do Stuff
Next n
End Using
This class can be extended to have MovePrevious() function by adding a List(Of TreeNode) for storing old results and having an index variable to keep track of position (index < 0 is before start, index >= size of list is search for next node in treeview). Then it can be used as search tracker in applications where user can search through treeview.
No comments:
Post a Comment