Click here to Skip to main content
Click here to Skip to main content

Using Serialization to Persist TreeView Control (VB.NET)

By , 27 Oct 2004
 

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)
        '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 (

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)
        '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)

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)
    '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 (

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)

    '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. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Tom John
Software Developer (Senior) RedFrog.com Limited
United Kingdom United Kingdom
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSerializing & persistine TreeView control By Tom John GREAT CODE !memberJustAnotherDebugger13 Dec '09 - 16:11 
I just can't find enough gratitude for Tom freely giving this code away! I'm a nubie and it would be years b4 I could accomplish what Tom John gives us here. THANK-YOU TOM! Smile | :) Laugh | :laugh: Big Grin | :-D Smile | :)
GeneralRe: Serializing & persistine TreeView control By Tom John GREAT CODE !memberTom John14 Dec '09 - 3:10 
Thanks for you comments - i am glad the article is still of use!
QuestionMe too :) And a short question/suggestion...memberHJBomanns27 May '09 - 0:08 
Hi Tom,
 
let me also add a big 'thanks' for this solution - great stuff!
 
I have a treeview displaying drives, folders and files with the drives at the top level. For some reasons i dont want to load the complete tree, but just only a single drive into a treeview control. Can you give us some hints on how to implement such a function? Thanks!
 
ATH, Bomi

AnswerRe: Me too :) And a short question/suggestion...memberTom John27 May '09 - 10:53 
Thanks for the comment... just noticed all the other questions that have been hanging around - seems i had an old email address registered. Sorry guys - I'll put some answers up asap.
 
On this question, are you wanting to simple display a list of drives, then populate the contents when the user expands the drive with the folders?
 
Cheers
 
Tom
AnswerRe: Me too :) And a short question/suggestion...memberHJBomanns2 Jun '09 - 17:53 
Hi Tom,
 
an example: I have a TreeView where the users should choose a folder to save documents. The top level represents network drives, each having an individual static structure:
 
Department 1
- Folders
 
Department 2
- Folders
 
Department 3
- Folders
 
Department 4
- Folders
.....
Department n
- Folders
 
On some machines the users should have access to all folders, others should have access only to one or two folders. My idea was to save the whole structure to a file and then be able to load, merge or save indivdiual parts:
 
LoadTreeView(MyTreeView, "Department 2", "C:\tv.dat") 'Will only load the "Department 2" tree
SaveTreeView(MyTreeView, "Department 7", "C:\tv.dat") 'Will only save/update the current "Department 7" tree
LoadTreeView(MyTreeView, "Department 3", "C:\tv.dat") 'Will only load the "Department 3" tree
MergeTreeView(MyTreeView, "Department 5", "C:\tv.dat") 'Will merge/add the "Department 5" tree add the end
 
ATH, Bomi
GeneralExcellent!!!!!memberMember 176140614 Feb '09 - 15:15 
Hi Tom,
 
This is amazing code, simple and elegant my 5 Stars.
 

Nitin
GeneralVery Well Done :)memberdnpro8 Feb '09 - 22:15 
This is indeed very good utility class. I was nagging with treeview for implementing same scenario. Although I got on solution from MS Group forum, but It was written in C#.
 
Again Thanks for sharing Smile | :) Thumbs Up | :thumbsup:
 
5/5
 
Smile | :)
 

GeneralThe assembly with display name 'TreeViewDataAccessDemo.XmlSerializers' failed to load in the 'LoadFrom'memberMember 578656918 Dec '08 - 3:50 
I'm using Visual Studio 2008, and this error occurs on the demo project. If you go to the Debug menu then Exceptions and check them all, run the project and hit the load button you should get the error aswell.
 
Any idea how to stop this error?
GeneralSome of the public properties like depth, valuepath could not be serializedmemberRain Man Alex4 Sep '08 - 4:56 
Hi
 
Good work Tom.
 
Some of the public properties like depth, valuepath could not be serialized. Its got no set property in it so i assume they are private fields and so not set but i need these values as i populate the treeview. I hold data in the valuepath which needs to be stored inthe database. Any ideas on this one
 
Thanks
 
RainManALex
QuestionHow I can save the serialized result into a MemoryStream or Stream?memberLuiggye17 Aug '08 - 15:23 
Hi,
 
Excellent code, I am using without any problem.
 
At this moment, I want to return the serialized result from a web service to the clients.
 
So, I need to save the serialized result into a MemoryStream or Stream or string, or...????
in order to use in a web service function.
 
The serialization will run in a web service and the deserialization will run in the a client function.
 
Obviously, I want to deserialize from a MemoryStream or Stream or string, or...??? and load into the client TreeView again.
 
I was tried the following code to serialize / deserialize.
 
The DeSerializeTvwFromMemory function return the error: Cannot access a closed Stream.
 
The SerializeTvwToMemory function do not return errors, but I am not sure that works fine.
 
Anybody can show me, how I can do it please?
 
Thanks in advance,
 

Public Shared Function SerializeTvwToMemory(ByVal treeView As TreeView) As MemoryStream
 
'Create as serializer and MemoryStream to save TreeViewData
Dim ser As New System.Xml.Serialization.XmlSerializer(GetType(TreeViewData))
Dim MyStream As New System.IO.MemoryStream()
Dim writer As New System.Xml.XmlTextWriter(MyStream, Nothing)
 
'Generate TreeViewData from TreeView and serialize the file.
ser.Serialize(writer, New TreeViewData(treeView))
 
'Tidy up
writer.Close()
MyStream.Close()
 
Return MyStream
 
End Function
 

Public Shared Function DeSerializeTvwFromMemory(ByVal treeView As TreeView, ByVal MySerie As MemoryStream) as boolean
 
'Create as serializer and get the MemoryStrean to deserialize
Dim ser As New System.Xml.Serialization.XmlSerializer(GetType(TreeViewData))
Dim MyStream As New System.IO.MemoryStream()
Dim reader As New System.Xml.XmlTextReader(MyStream)
 
'Deserialize the file and populate the treeview
Dim treeData As TreeViewData = CType(ser.Deserialize(reader), TreeViewData)
treeData.PopulateTree(treeView)
 
'Tidy up
reader.Close()
MyStream.Close()
MyStream = Nothing
 
Return true
 
End Sub
QuestionHow do I serialize and arraylist stored in the Tag property?memberAegisSailor6 Jul '08 - 10:01 
First, let me say that this project is awesome, it was a tremendous help to me and learned a lot from it.
 
The one other thing I'd like to do is to serialize an array list (of strings) that I store in the TAG property of each node. When I call the SaveTreeViewData method I get an XML file error. Does anybody have any ideas on how I can resolve this?
 
Roger
GeneralBest Codemembertribalstone22 Jun '08 - 19:02 
I have to thank you. this is very powerful code and worked like a charm. This is by far the best code example I have seen in a very long time...
 
Thank you!!!! Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;) Wink | ;)
 
Also works with the binary formatters.
GeneralRe: Best CodememberTom John22 Jun '08 - 23:50 
Good stuff - I am glad it is still of use... it could probably be modified now to selectively serialize the TreeNode directly using the DataMemberAttribute - .Net 3.0
 
Cheers
 
Tom
GeneralJust adding my thanks...memberAnthonyEllis7 May '08 - 18:41 
Great Job Tom!
I found out the hard way I couldn't pass TreeNodes via .NET Remoting... this let me get around that limitation. A nicy tidy little implementation - worked first time!
And hey! You also showed me a new VB.NET operator this old VB6 programmer hadn't learned yet - "AndAlso"!! That's going to come in handy!!
 
Thanks again!
GeneralRe: Just adding my thanks...memberTom John12 May '08 - 21:36 
Good stuff, thanks for your comments. I assume you've now discovered "OrElse" too!
 
Cheers
 
Tom
GeneralBrilliantmemberdavton25 Apr '08 - 10:43 
I spent about three hours trying to convert some c# code to vb.net to get this to work, and was going backwards rather than forwards.
 
As a last resort I went back to google and found this. In 20 minutes (ie virtually first time, once I resolved the bugs I had added myself) it was working.
 
I opened the downloaded code from VS2005 , and it did all the conversions for me.
 
Just great. Thanks very much
GeneralRe: BrilliantmemberTom John25 Apr '08 - 11:18 
Great, pleased to hear it.
 
Cheers
 
Tom
GeneralSerializing a non-trivial Treeview node Tagmemberjason_thornbrugh1 Nov '07 - 12:31 
This code worked great until I tried to use a non-trivial Treeview node tag.
 
I need to extend the Treenode view with custom data. If I use the following, for example, the XML serializer chokes.
 
...
TreeView1.SelectedNode.Tag = New Appconfig
...
_
Public Class Appconfig
Public Username as String
Public Password as String
End Class
 
The error is:
System.InvalidOperationException was unhandled
Message="There was an error generating the XML document."
Source="System.Xml"
StackTrace:
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o)
at WindowsApplication1.TreeViewDataAccess.SaveTreeViewData(TreeView treeView, String path) in C:\temp\test\Form1.vb:line 459
...
 

If I set the tag to a simple data type such as string, it works fine:
 
...
TreeView1.SelectedNode.Tag = "string"
...
 
Any thoughts?

GeneralRe: Serializing a non-trivial Treeview node Tagmemberjason_thornbrugh1 Nov '07 - 12:37 
FYI, the error is:
"The type WindowsApplication1.Appconfig was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

GeneralRe: Serializing a non-trivial Treeview node TagmemberTom John1 Nov '07 - 12:53 
Add the Appconfig type to the extra types of the serializer object, it's a constructor overload. And make sure the class is marked as serializable.
 
Hope this helps
 
Tom
GeneralRe: Serializing a non-trivial Treeview node Tagmemberjason_thornbrugh1 Nov '07 - 13:39 
Wow!   It worked great.   For anyone else interested, this is the updated code:
 
      'Create a serializer and file to save TreeViewData
      Dim extraTypes(0) As Type
      extraTypes(0) = GetType(Appconfig)
      Dim ser As New System.Xml.Serialization.XmlSerializer(GetType(TreeViewData), extraTypes)
 
Now I need to figure out how to get the tag to reload.   Any ideas?

GeneralRe: Serializing a non-trivial Treeview node TagmemberTom John2 Nov '07 - 0:23 
The ToTreeNode method should take care of that for you, there's a line in there that sets the treenode tag to your object:
 
ToTreeNode.Tag = Me.Tag
 
Boy... I really have to rewrite this article in 2.0... have not looked at the code in ages, half of it's redundant now!
 
Hope this helps
 
Tom
GeneralRe: Serializing a non-trivial Treeview node Tagmemberjason_thornbrugh2 Nov '07 - 5:07 
I inserted some debug code to look at the tag.
 
When the tag is created by the application, it is type "WindowsApplication1.Appconfig". When I the app loads the XML, the tag is created as type "System.Xml.XmlNode[]".
 
If the tag is created as type "string", the tag gets loaded as type "string".

GeneralRe: Serializing a non-trivial Treeview node Tagmemberjason_thornbrugh2 Nov '07 - 5:22 
My mistake. I forgot to add the overload to ToTreeNode as well as SaveTreeViewData.
 
The code works great in VB 2005!

GeneralRe: Serializing a non-trivial Treeview node TagmemberReadonlyMan13 Feb '12 - 1:27 
i use a costume procedure to convert class objects to string and vice versa.
 
just search for .tag in the main class and you'll see where you got to put this code.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 27 Oct 2004
Article Copyright 2004 by Tom John
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid