Using tree view in vb.net - vb.net

I have added a tree view to my form. I want to capture the values of checkboxes, which one is checked or not.
Also I am trying to get the count of nodes. There are four nodes in the tree,
Dim nodes As TreeNodeCollection = TreeView1.Nodes
MsgBox(nodes.Count)
gives 1.
Thanks

... This probably isn't the best way to do this, but it works...
The function would look something as follows:
Function GetAllCheckedNodes(ByVal tv As TreeView, Optional ByRef tn As TreeNode = Nothing) As List(Of TreeNode)
Dim RetVal As New List(Of TreeNode)
If tn Is Nothing Then
For Each nd In tv.Nodes
RetVal.AddRange(GetAllCheckedNodes(tv, nd))
Next
Else
If tn.Checked Then RetVal.Add(tn)
For Each nd In tn.Nodes
RetVal.AddRange(GetAllCheckedNodes(tv, nd))
Next
End If
Return RetVal
End Function
And your code to use it would look something like:
Dim MyList As List(Of TreeNode) = GetAllCheckedNodes(tvAccounts)
or
Dim MyList As List(Of TreeNode) = GetAllCheckedNodes(tvAccounts, nd)
Where nd is a specific node in the treeview where you want to get all the children nodes that are checked.
Hope this helps and makes sense.

Related

VB.Net Use XmlNodeList in a parallel ForEach

I have a piece of code that iterates over the nodes in an XmlNodeList and creates a different object for each one depending on the node name and adds it to a list for printing.
For Each node as XmlNode In nodeList
Select Case node.Name.ToUpper()
Case "SHAPE"
_items.Add(New ShapeTemplate(node, Me))
Case "TEXTBLOCK"
_items.Add(New TextblockTemplate(node, Me))
End Select
Next
This code works fine, but because of all the work that has to be done by the ShapeTemplate and TextblockTemplate constructors, it is VERY slow. Since the order objects are added to _items doesn't matter, I thought a good way to speed it up would be to use a parallel.ForEach loop. The problem is XmlNodeList can't be used with parallel.ForEach because it is a non-generic collection. I've been looking into ways to convert XmlNodeList to List(Of XmlNode) with no luck. The answer I keep seeing come up is
Dim nodes as New List(Of xmlNode)(nodeList.Cast(Of xmlNode)())
But when I try it, I get an error telling me that 'Cast' is not a member of XmlNodeList.
I've also tried using TryCast like this
Dim nodes as List(Of XmlNode) = TryCast(CObj(nodeList), List(Of XmlNode))
but it results in nodes being Nothing because the object can't be cast.
Does anyone know how I can use XmlNodeList in a parallel.ForEach loop?
EDIT: I'm trying to avoid using a loop for the conversion if I can
You could use Parallel LINQ instead of Parallel.ForEach, which seems like a more natural fit for this sort of transformation. This looks just like a normal LINQ query, but with .AsParallel() added.
Imports System.Linq
' _items will not be in same order as nodes in nodeList.
' Add .AsOrdered() after .AsParallel() to maintain order, if needed.
_items = (
From node In nodeList.Cast(Of XmlNode)().AsParallel()
Select item = CreateTemplate(node)
Where item IsNot Nothing
).ToList()
Function CreateTemplate(node As XmlNode) As ITemplate ' interface or base class for your types
Dim item As ITemplate
Select Case node.Name.ToUpper()
Case "SHAPE"
item = New ShapeTemplate(node, Me)
Case "TEXTBLOCK"
item = New TextblockTemplate(node, Me)
Case Else
item = Nothing
End Select
Return item
End Function
As seen here, the XmlNodeList can be converted to a generic List(Of XmlNode) by passing it to the constructor with OfType.
' I added so your code could compile.
' I assumed an interface shared by ShapeTemplate and TextblockTemplate
Dim nodeList As XmlNodeList
Dim _items As New List(Of ITemplate)
Dim _nodes As New List(Of XmlNode)(nodeList.OfType(Of XmlNode))
Now the parallel loop. Note, if you are adding to a non-threadsafe collection such as List, you will need to synchronize adding the objects to the list. So i separated the time consuming portion (Template constructor) from the fast operation (adding to the list). This should improve your performance.
Dim oLock As New Object
Parallel.ForEach(
_nodes,
Sub(node)
Dim item As ITemplate
Select Case node.Name.ToUpper()
Case "SHAPE"
item = New ShapeTemplate(node, Me)
Case "TEXTBLOCK"
item = New TextblockTemplate(node, Me)
Case Else
item = Nothing ' or, do something else?
End Select
SyncLock oLock
_items.Add(item)
End SyncLock
End Sub)

Flatten Treeview Nodes collection to List without recursion

Hopefully this is not a repeat question. I looked and found similar questions but not sufficient in this case.
I have many tree view controls, and can traverse the Nodes recursively for various reasons.
However, I often need to traverse the Nodes as if they are in a List.
I would like to make a function that creates a Generic.List(of TreeNode) from the Nodes collection, without recursion if at all possible.
(Without recursion purely for the exercise of doing it without recursion - I understand it may not be possible)
This function would save alot of time with repeated use across a massive solution, where the coders could use a simple For Each paradigm to traverse the Nodes.
I have seen a technique to 'flatten' the Nodes collection using C#, which uses LINQ and recursion, but I am not sure that the syntax can be converted to VB.NET cleanly. So if there are any clever VB functions out there can that I can mold to this task - would be very helpful.
There are many similar questions and very informative answers on SO, like this one:
Enumerating Collections that are not inherently IEnumerable?
...which highlights stack overflow errors in very deep trees using some algorithms. I hope that a method that does not use recursion will not suffer from Stack overflow errors - however, I am prepared that it might be long and clumsy and slow.
I am also prepared for the answer that 'It is not possible to do this without recursion'. However, I would like to confirm or deny this claim using the power of SO (this forum)
It's possible, and not very hard at all....
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TreeView1.ExpandAll()
For Each TN As TreeNode In TreeView1.NodesToListWithoutRecursionBecauseWhyNot(TraverseType.BreadthFirst)
Debug.Print(TN.Text)
Next
End Sub
End Class
Public Module Extensions
Public Enum TraverseType
BreadthFirst
DepthFirst
End Enum
<Runtime.CompilerServices.Extension()> _
Public Function NodesToListWithoutRecursionBecauseWhyNot(ByVal TV As TreeView, Optional ByVal Traverse As TraverseType = TraverseType.DepthFirst) As List(Of TreeNode)
Dim nodes As New List(Of TreeNode)
Select Case Traverse
Case TraverseType.BreadthFirst
Dim Q As New Queue(Of TreeNode)
For Each TN As TreeNode In TV.Nodes
Q.Enqueue(TN)
Next
While Q.Count > 0
Dim TN As TreeNode = Q.Dequeue
nodes.Add(TN)
For Each subTN As TreeNode In TN.Nodes
Q.Enqueue(subTN)
Next
End While
Case TraverseType.DepthFirst
Dim L As New List(Of TreeNode)
For Each TN As TreeNode In TV.Nodes
L.Add(TN)
Next
While L.Count > 0
Dim TN As TreeNode = L.Item(0)
L.RemoveAt(0)
nodes.Add(TN)
For i As Integer = TN.Nodes.Count - 1 To 0 Step -1
L.Insert(0, TN.Nodes(i))
Next
End While
End Select
Return nodes
End Function
End Module
Just add the nodes to the list but at the same time keep the position of the last node you processed. A node is considered process when its immediate children are added to the list.
Public Function GetAllNodes(ByVal topNode As TreeNode)
Dim allNodes As New List(Of TreeNode)
Dim lastProcessPosition As Integer = 0
allNodes.Add(topNode)
Do While lastProcessPosition < allNodes.Count
allNodes.AddRange(allNodes(lastProcessPosition).Nodes)
lastProcessPosition += 1
Loop
Return allNodes
End Function
If you don't have a top node then just substitute the parameter for a list of nodes to start with.
Public Function GetAllNodes(ByVal topNodes As TreeNodeCollection)
Dim allNodes As New List(Of TreeNode)
Dim lastProcessPosition As Integer = 0
allNodes.AddRange(topNodes)
Do While lastProcessPosition < allNodes.Count
allNodes.AddRange(allNodes(lastProcessPosition).Nodes)
lastProcessPosition += 1
Loop
Return allNodes
End Function
I'm not sure if a check for Nothing must be done on the Nodes property before using it.
Note: I was able to remove this for loop and replace it with AddRange
'For Each node As TreeNode In allNodes(lastProcessPosition).Nodes
' allNodes.Add(node)
'Next

How to find the all parent node of a child node from bottom to top in tree view control using vb.net

Here i have below Treeview as shown in image.
for child node G g i want to fetch all the parent nodes name from bottom to top and store into list. means in list should add G g, F f, D d, B b.
please suggest the solution.
thanks in advance...
I have created one function for it which will add all the parent in the list until parent is not nothing for the selected node.
Public Function AllParents() As List(of String)
Dim listOfNodeHierarchy As New List(Of String)
listOfNodeHierarchy.Add(node.ToString)
'If Child Node has parent, add the parent name of the Child node to the list
While (node.Parent IsNot Nothing)
listOfNodeHierarchy.Add(node.Parent)
node = node.Parent
End While
return listOfNodeHierarchy
End Function
This code will help you to obtain information about the parents node.
dim selectedNode as treeNode = YourTreeViewSelectedNode
dim nodePath as string= "" 'will store the full path
'You get the full Path of the node like 'Parent\Child\GrandChild
'The Procedure will fill nodePath
ObtainNodePath(selectedNode,nodePath)
msgbox(nodePath)
Private Sub ObtainNodePath(ByVal node As TreeNode, ByRef path As String)
If node.Parent IsNot Nothing Then
path = IO.Path.Combine(node.Text, path) ' Parent\Child
If node.Parent.Level = 0 Then Exit Sub
'Call again the method to check if can extract more info
ObtainNodePath(node.Parent, path)
End If
End Sub
If you just need the list of text of the parent nodes, you can simply use the FullPath property of the selected node:
For Each s As String In _
TreeView1.SelectedNode.FullPath.Split(TreeView1.PathSeparator).Reverse
If you need the list of nodes, just keep checking the parent until it's nothing:
Private Function ParentNodes(fromNode As TreeNode) As IEnumerable(Of TreeNode)
Dim result As New List(Of TreeNode)
While fromNode IsNot Nothing
result.Add(fromNode)
fromNode = fromNode.Parent
End While
Return result
End Function

Using a Dictionary in a TreeNode Tag - VB.NET

I'm writing a bit of code that I want to populate a TreeView, which it does quite successfully, but I also want to put a Dictionary in the Tag of each Level 1 child node. Once the Tag has been set to the Dictionary, is there any way I can modify the dictionary, without redeclaring the Tag.
For Each verse In Verses
Dim _verse = verse.ToString.Trim
Dim _node As TreeNode = New TreeNode(_verse.Split(vbNewLine).First & "...")
_node.ToolTipText = _verse
_node.Tag = New Dictionary(Of String, Object)
Node.Nodes.Add(_node)
Next
You can later just cast the tag of the not to Dictionary(Of String, Object) and then manipulate the dictionary as usual.
For example, assuming that currentNode is the node of interest, you can have something like the following.
Dim dictionary as Dictionary(Of String, Object) = _
CType(currentNode.Tag, Dictionary(Of String, Object))
dictionary.Add("NewKey", newObject);

How to save in my Application Settings the Checkbox' Checked in a Treeview?

I'm looking how to save the checked checkboxes in a treeview which contains a lot of folders and subfolders. Is it possible to save them in the application settings ?
You can store a Dictionary(Of String, Boolean) and use the unique Name (or FullPath) property of each TreeNode:
Dim d As New Dictionary(Of String, Boolean)
Dim stack As New Stack(Of TreeNode)
For Each n As TreeNode In Me.TreeView1.Nodes
stack.Push(n)
Next
While stack.Count > 0
Dim node As TreeNode = stack.Pop()
For Each subNode As TreeNode In node.Nodes
stack.Push(subNode)
Next
d.Add(node.Name, node.Checked)
End While
' Store d in My.Settings or somewhere else
To restore it is easy - loop over the dictionary and set the Checked values by name (or path).