A recent post on a MS group regarding persisting TreeView data
to file and apparent lack of VB.Net example lead me to write a solution as it
was one of the first problems I faced as a developer and thought it would make
for an interesting article for beginners. Additionally, the utilisation of
serialisation to persist objects as Xml provides an introduction to a great
�free� way of storing data.
The TreeView and TreeNode types have no intrinsic
methods for persisting their data, neither are they serializable, the solution
therefore is to write classes that can meditate between TreeView and file.
Firstly we need a couple of structures, one to represent the TreeView
and another to represent a TreeNode. These structures need
to duplicate the properties within these types that need to be persisted and
provide methods to convert from the source objects and these structures.
This structure is used to represent the TreeView type and its
properties in their simplest form:
<Serializable()>Public Structure TreeViewData
Public Nodes() As TreeNodeData
Public Sub New(ByVal treeview As TreeView)
'Check to see if there are any root nodes in the TreeView
If treeview.Nodes.Count = 0 Then Exit Sub
'Populate the Nodes array with child nodes
ReDim Nodes(treeview.Nodes.Count - 1)
For i As Integer = 0 To treeview.Nodes.Count - 1
Nodes(i) = New TreeNodeData(treeview.Nodes(i))
Next
End Sub
Public Sub PopulateTree(ByVal treeView As TreeView)
'Check to see if there are any root nodes in the TreeViewData
If Me.Nodes Is Nothing OrElse Me.Nodes.Length = 0 Then Exit Sub
'Populate the TreeView with child nodes
treeView.BeginUpdate()
For i As Integer = 0 To Me.Nodes.Length - 1
treeView.Nodes.Add(Me.Nodes(i).ToTreeNode)
Next
treeView.EndUpdate()
End Sub
End Structure
The only property within the TreeView that is represented by
this structure is the Nodes collection as this is the only property
that really needs to be persisted. Notice the constructer has a TreeView
parameter, this allows the TreeViewData structure to be
populated from the source TreeView in a single line of code:
Dim treeViewData1 as New TreeViewData(MyTreeView)
Providing the TreeView Nodes collection contains
one or more TreeNode object the constructer will adjust the size of
the Nodes() array to match the size of the TreeView.Nodes
collection and populate it with TreeNodeData representation
of the root TreeNodes. The TreeNodeData structure will
take care of populating its own hierarchy (see
below).
The PopulateTree method performs the reverse action to the
constructor, by populating the specified TreeView with the contents
of the TreeViewData instance. Notice in how the For
loop is contained within BeginUpdate and EndUpdate
statements, this prevents the TreeView from being redrawn
each time a new Node is added, providing a smoother visual
experience for the user, particularly on larger node hierarchies.
This structure is used to represent the TreeNode class and its
properties in their simplest form:
<Serializable()> Public Structure TreeNodeData
Public Text As String
Public ImageIndex As Integer
Public SelectedImageIndex As Integer
Public Checked As Boolean
Public Expanded As Boolean
Public Tag As Object
Public Nodes() As TreeNodeData
Public Sub New(ByVal node As TreeNode)
'Set the basic TreeNode properties
Me.Text = node.Text
Me.ImageIndex = node.ImageIndex
Me.SelectedImageIndex = node.SelectedImageIndex
Me.Checked = node.Checked
Me.Expanded = node.IsExpanded
'See if there is an object in the tag property
'and if it is serializable
If (Not node.Tag Is Nothing) AndAlso
node.Tag.GetType.IsSerializable Then Me.Tag = node.Tag
'Check to see if there are any child nodes
If node.Nodes.Count = 0 Then Exit Sub
'Recurse through child nodes and add to Nodes array
ReDim Nodes(node.Nodes.Count - 1)
For i As Integer = 0 To node.Nodes.Count - 1
Nodes(i) = New TreeNodeData(node.Nodes(i))
Next
End Sub
Public Function ToTreeNode() As TreeNode
'Create TreeNode based on instance of
'TreeNodeData and set basic properties
ToTreeNode = New TreeNode(Me.Text, Me.ImageIndex,
Me.SelectedImageIndex)
ToTreeNode.Checked = Me.Checked
ToTreeNode.Tag = Me.Tag
If Me.Expanded Then ToTreeNode.Expand()
'Recurse through child nodes adding to Nodes collection
If Me.Nodes Is Nothing OrElse Me.Nodes.Length = 0 Then Exit Function
For i As Integer = 0 To Me.Nodes.Length - 1
ToTreeNode.Nodes.Add(Me.Nodes(i).ToTreeNode)
Next
End Function
End Structure
The TreeNode has a few more properties that need to be persisted
compared to the TreeView, again the structure of the TreeNodeData
instance is generated from the source object by passing it as a parameter
in the constructor:
Dim treeNodeData1 as New TreeNodeData(MyTreeNode)
Properties to be persisted are directly mapped from the source
TreeNode to the TreeNodeData instance with the
exception of the Tag property:If (Not node.Tag Is Nothing) AndAlso node.Tag.GetType.IsSerializable
Then Me.Tag = node.Tag
Because the Tag property can be any object, and this solution
uses serialization (see below) to persist
data we need to check that the object in the Tag can also be
serialized, therefore we only map the Tag property if it is of a
serializable type.
Finally the structure contains the ToTreeNode function that
returns a TreeNode object with its hierarchy populated from the
TreeNodeData structure.
These structures are all very well, but we�ve still not got a way of storing
the TreeView to file. To achieve this goal we�ll use serialisation
which basically converts objects that support it to and from structured Xml.
Notice that both the TreeViewData and TreeNodeData
structures have the attribute <Serializable()>
preceding their declaration, indicating the class can be serialized. All
we have to do is provide a couple of methods to serialize and desterilize the
structures, the first saves the structure to file:
Public Shared Sub SaveTreeViewData(ByVal treeView As TreeView,
ByVal path As String)
'Create a serializer and file to save TreeViewData
Dim ser As New System.Xml.Serialization.XmlSerializer(
GetType(TreeViewData))
Dim file As New System.IO.FileStream(path, IO.FileMode.Create)
Dim writer As New System.Xml.XmlTextWriter(file, Nothing)
'Generate TreeViewData from TreeView and serialize the file.
ser.Serialize(writer, New TreeViewData(treeView))
'Tidy up
writer.Close()
file.Close()
file = Nothing
End Sub
This method accepts a couple of parameters; the TreeView to
persist and the path to store the data. Notice the method is declared as a
Shared, this means that an instance of its containing class does not need to be
initiated before the method can be used (see
Bringing it all Together below).
Firstly we create an XmlSerializer that expects to be dealing
with object of TreeViewData type, we create a file to save the Xml
and an XmlTextWriter to write the serialized object to the
file.
The line that does all the work:
ser.Serialize(writer, New TreeViewData(treeView))
This takes the specified TreeView object, creates and
populates a new instance of the TreeViewData class and writes it
all to file. All we need to do then tidy up by closing the XmlWriter
and file.
Secondly we need to load previously stored TreeViewData
structures:
Public Shared Sub LoadTreeViewData(ByVal treeView As TreeView,
ByVal path As String)
'Create as serializer and get the file to deserialize
Dim ser As New System.Xml.Serialization.XmlSerializer(
GetType(TreeViewData))
Dim file As New System.IO.FileStream(path, IO.FileMode.Open)
Dim reader As New System.Xml.XmlTextReader(file)
'Deserialize the file and populate the treeview
Dim treeData As TreeViewData = CType(ser.Deserialize(reader),
TreeViewData)
treeData.PopulateTree(treeView)
'Tidy up
reader.Close()
file.Close()
file = Nothing
End Sub
This method performs the reverse to the SaveTreeViewData, again
creating an XmlSerializer to do all the work, only this time it
reads the file using an XmlTextReader:
Dim treeData As TreeViewData = CType(ser.Deserialize(reader), TreeViewData)
treeData.PopulateTree(treeView)Here we create an instance of the
TreeViewData structure and populate it from the desterilized Xml
file, finally we call the PopulateTreeView method of the
TreeViewData object to populate the specified
TreeView.
Finally all this code can be brought into one containing class that can be
used to persist the data of any TreeView, exposing the two Shared
methods for simple reusability throughout your code. This
TreeViewDataAccess class simply contains all the code listed above,
and can be downloaded from the links at the top of this article.
To use the class anywhere in your code all you need is one line to save the
TreeView data:
TreeViewDataAccess.SaveTreeViewData(TreeView1, �C:\MyFolder\MyTreeView.xml�)
And another to load it:
TreeViewDataAccess.LoadTreeViewData(TreeView1, �C:\MyFolder\MyTreeView.xml�)
The demo project also available for download at the top of this article shows an example of this class in action.
I hope this article provides an interesting solution to persisting TreeView controls in VB. It should also introduce newcomers to the joys of serialization and shared methods.