Contents
Introduction
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 Problem
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.
TreeView Structures
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.
TreeViewData Structure
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)
If treeview.Nodes.Count = 0 Then Exit Sub
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)
If Me.Nodes Is Nothing OrElse Me.Nodes.Length = 0 Then Exit Sub
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 (
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.
TreeNodeData Structure
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)
Me.Text = node.Text
Me.ImageIndex = node.ImageIndex
Me.SelectedImageIndex = node.SelectedImageIndex
Me.Checked = node.Checked
Me.Expanded = node.IsExpanded
If (Not node.Tag Is Nothing) AndAlso
node.Tag.GetType.IsSerializable Then Me.Tag = node.Tag
If node.Nodes.Count = 0 Then Exit Sub
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
ToTreeNode = New TreeNode(Me.Text, Me.ImageIndex,
Me.SelectedImageIndex)
ToTreeNode.Checked = Me.Checked
ToTreeNode.Tag = Me.Tag
If Me.Expanded Then ToTreeNode.Expand()
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)
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 (
Finally the structure contains the ToTreeNode
function that returns a TreeNode
object with its hierarchy populated from the TreeNodeData
structure.
Properties to be persisted are directly mapped from the source
TreeNode
to the
TreeNodeData
instance with the exception of the
Tag
property:
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.
see below).
Storing and Retrieving
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)
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)
ser.Serialize(writer, New TreeViewData(treeView))
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 (
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))
Secondly we need to load previously stored TreeViewData
structures:
Public Shared Sub LoadTreeViewData(ByVal treeView As TreeView,
ByVal path As String)
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)
Dim treeData As TreeViewData = CType(ser.Deserialize(reader),
TreeViewData)
treeData.PopulateTree(treeView)
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
. 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.
see Bringing it all Together below).
Bringing it all Together
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.
Conclusion
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.