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

Save and Load the whole content of a ListView Control

, 23 Feb 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Sometimes you have to save the content of a ListView to the user's filesystem. That's very easy to manage with the XmlText-Writer and Reader from the .Net Framework 2.0.

Introduction

If you want to save the content of a ListView control there are many possibilities. You can serialize all items, you can loop through all items and save them to an .ini-File, or you decide to write an Xml (Extensible Markup Language)-File.

To my mind, the last option is the best. You have the control about all operations and so the function is very flexible and modifiable.

Background

Once I needed to save the headers, the groups, and all their items contained in my ListView. Till then, I always used a serialization function because of the simple usage. But this only saved the items, and not the groups including their items. So I wrote my own method for saving and loading the whole content of a ListView control.

The logic behind the Code

Well, we decided to write an Xml-configuration-file.
That’s quite easy, we’re going to loop through all headers, groups and their items.

That means:
There are several Xml-Nodes:

  • the document starts with <ListView>
  • then a Node named <Columns> contains one “Single-Line-Node” for each ColumnHeader; they save their most important data (the text, the width and the text alignment
  • the tag <Groups> contains a node for each group
  • within the <Group> tag containing the name and the header alignment, there is for each item another node with its subitems

Fair enough, but what does that mean exactly?
When you save a ListView like this…

sample_lsv.png

…the output configuration file looks as follows:

xml_scheme.png

ListViews with groups aren’t the same as ListViews without groups!
Now, knowing how my function works, you must make differences between ListViews which use groups and those that don’t.

Why?

Perhaps you have noticed that the function first loops through each group, and then through its items. However, what’s going to happen if a ListView has no groups? Because there is no group to search through its items, there cannot be found any. So the configuration file would be empty.

For this reason, we have to write four differently working functions (two for saving; two for loading). They differ in that the functions for ListViews without groups come straight to the items and skip the groups, because they don’t exist.

Finally, we are talking about the code.

The ListViewContent-Class

Now you know the logic behind my function and that there are significant differences between a ListView that uses groups and one that doesn’t. So we can consider how the file is written.

There are two Classes we could use from the System.Xml-Namespace:

  • XmlDocument
  • XmlTextWriter

I chose the XmlTextWriter because I think it’s a little bit easier to handle than the XmlDocument.

Let’s start with the function for saving the content of ListViews with groups.

ListViewWithGroupsSave

The code is commented, but I want to add something. It’s not difficult to understand. This function uses an XmlTextWriter from the System.Xml-Namespace. Then it simply loops through all objects (headers, groups, items, etc.) we want to be saved.

Arguments:

  • lsv: That’s the ListView control that’s going to be saved
  • destinationPath: The path you want to save the config file to.
    Important: You have to specify a file extension, such as .xml, .txt, .cfg or whatever you want.

Return Value:
If no errors occur the function returns an empty String, otherwise the Exception's Message.

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()


                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1
                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Groups> will contain all Groups of The ListView
                .WriteStartElement("Groups")

                For j As Integer = 0 To lsv.Groups.Count - 1
                    ' <Group
                    .WriteStartElement("Group")
                    .WriteAttributeString("name", lsv.Groups(j).Header)
                    ' name="xyz"
                    .WriteAttributeString("headerAlignment", lsv.Groups(j).HeaderAlignment.ToString)
                    ' headerAlignment="xyz">

                    ' Loop through the Items of the given ListView...
                    For k As Integer = 0 To lsv.Groups(j).Items.Count - 1

                        .WriteStartElement("item") ' <item
                        .WriteAttributeString("text", lsv.Groups(j).Items(k).Text) ' text="xyz"

                        ' ...and SubItems to write them to the Document
                        For l As Integer = 0 To lsv.Groups(j).Items(k).SubItems.Count - 2

                            .WriteStartElement("subItem") ' <subItem
                            .WriteAttributeString("text", lsv.Groups(j).Items(k).SubItems(l + 1).Text) ' text="xyz"

                            .WriteEndElement() ' />

                        Next

                        ' At the end of each passage in this loop, the Item-Tag will be ended to start with a new item 
                        .WriteEndElement() ' </item>
                    Next

                    ' At the end of each passage in this loop, the Group-Tag will be ended to start with a new group
                    .WriteEndElement() ' </Group>
                Next

                ' Complete the Groups-Tag to signalize that all groups have been written to the document
                .WriteEndElement() '</Groups>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

ListViewWithGroupsLoad

In the function for loading, we use an XmlTextReader. It isn’t much more complicated than the saving function, but I’m going to explain it in short. There is a loop that goes on until the Reader has come to the end of the saved document. In each pass is queried what’s the current element. Thus, step-by-step, all values are read in and added to the ListView.

Arguments:

  • lsv: That’s the ListView control that’s going to be loaded
  • loadedPath: The path to the configuration file on the user’s filesystem
  • append: "True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before loading
  • loadColumns: Indicates if you want the HeaderColumn to be loaded.
  • addInvisibleColumn: Do you want that an "invisible" column will be added to the ListView? Perhaps you have already noticed that it isn’t possible to align the first ColumnHeader’s text. So this option is very helpful. With an “invisible” first column with a width of 0 px, you can do that. But don’t forget to “lock” its width by setting it to 0 px in the ListView.ColumnWidthChanging-Event

Return Value:
If no errors occur the function returns an empty String, otherwise the Exception's Message.

    ''' <summary>
    ''' Loads the whole content of a ListView with groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
     Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim grp As New ListViewGroup
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If

                    End If

                    ' The tag <Group> was found; it contains the name of the group and its text alignment
                    If .Name = "Group" Then

                        'If the Tag is a StartElement (<Group>, and not </Group>), the ListView is added all the saved Groups
                        If .IsStartElement Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("headerAlignment").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Set the required properties of the ListViewGroup (name and text alignment)
                            grp = New ListViewGroup(.GetAttribute("name"), align)

                            ' Add the group to the ListView
                            lsv.Groups.Add(grp)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            listItem.Group = grp
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then
                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function  

ListViewWithNoGroupsSave

The function hasn’t to be explained, too, because it’s quite the same as ListViewWithNoGroupsSave. It’s different that here’s just no loop for the groups; we directly go to the items.

The arguments and the return value are equal to those of the other function.

    ''' <summary>
    ''' Saves the content of a ListView control which has groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()

                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1

                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Items> will contain all Items of the ListView
                .WriteStartElement("Items")    ' <Items>

                ' Loop through the given ListView's items...
                For k As Integer = 0 To lsv.Items.Count - 1

                    .WriteStartElement("item") ' <item
                    .WriteAttributeString("text", lsv.Items(k).Text) ' text="xyz">

                    ' ...and SubItems to write them to the Document
                    For l As Integer = 0 To lsv.Items(k).SubItems.Count - 2

                        .WriteStartElement("subItem") ' <subItem
                        .WriteAttributeString("text", lsv.Items(k).SubItems(l + 1).Text)    ' text="xyz"

                        .WriteEndElement()    ' />

                    Next

                    ' At the end of each passage in the main loop, the Item-Tag will be ended to start with a new item 
                    .WriteEndElement() ' </item>
                Next

                ' Complete the Items-Tag to signalize that all items have been written to the document
                .WriteEndElement() ' </Items>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function  

ListViewWithNoGroupsLoad

The function also skips the <Group>-tags, because they don’t exist.

The arguments and the return value are equal to those of the other function.

    ''' <summary>
    ''' Loads the whole content of a ListView without groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
    Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then

                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

We have to identify whether the ListView to be saved has groups or not!
Since we now know how to handle those ListViews having groups and those that haven’t, we must detect which kind of ListView the user wants to save.

Well, there are currently four functions in our “ListViewContent”-class. Make sure that they are declared as “Private Shared” and the class itself as “Public MustInherit”.

Why? I will explain later, at “Using the Code”.

Now we need two public shared functions that the user will call. In these two functions we do not save or load anything, we are just going to detect if the ListView control has groups or not. When this is done, we invoke the proper function.

Save

So, how can we detect the kind of the ListView to save?

We have access to all information we need from the ListView control. Therefore, the count of groups in a ListView that contains some groups, is greater than zero. But we also have to check if the ListView does show any groups at all.

Putting this in Visual Basic .Net is not difficult:

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Save(ByVal lsv As ListView, ByVal destinationPath As String) As String

        If lsv.Groups.Count > 0 AndAlso lsv.ShowGroups Then
            Return ListViewWithGroupsSave(lsv, destinationPath)

        Else
            Return ListViewWithNoGroupsSave(lsv, destinationPath)

        End If

    End Function 

If the given ListView has more than zero groups and it is also showing them, the function calls ListViewWithGroupsSave, else it calls ListViewWithNoGroupsSave.

Then the return value is returned. If no error occurs it will be an empty String, otherwise the Exception’s message.

Load

It’s just a little bit more difficult to identify if the file to be loaded contains groups. There are two namespaces we need:

  1. System.IO
  2. System.Text.RegularExpressions

First, we need the content of the saved configuration file. We use the function File.ReadAllText from the System.IO-namespace and remove all line breaks.

To find out if the file contains <group>-tags, we need the function RegEx.IsMatch from the System.Text.RegularExpressions-namespace. Regex is a powerful tool for validating and editing Strings. IsMatch checks a String for validity regarding a pattern.

That’s the very simple pattern we’ll use: (<groups>.*</groups>)|(<groups />)

  • The brackets are for grouping the two different expressions
  • This pattern searches for the tag <groups>…
  • A “.” stands for any character and the quantifier “*” indicates that the foregoing expression (“.” = any character) can occur any number of times.
  • Then the group-tag is closed with </groups>
  • The pipe (|) finds an expression before OR after the symbol |.
  • This pattern also finds empty groups (<groups />)

Because of the pipe, this function returns “True” if either <groups>.*</groups> or <groups /> is found.

So we come to the VB-Code:

    ''' <summary>
    ''' Loads the whole content of a ListView
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Load(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
      Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String

        Dim hasFileGroups As Boolean =
         Regex.IsMatch(File.ReadAllText(loadedPath).Replace(Chr(10), ""), "(<groups>.*</groups>)|(<groups />)",
         RegexOptions.IgnoreCase)

        If hasFileGroups Then
            Return ListViewWithGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        Else
            Return ListViewWithNoGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        End If

    End Function   

If the saved configuration file contains the <group>-tag, the function calls ListViewWithGroupsLoad, else it calls ListViewWithNoGroupsLoad

Then the return value is returned. If no error occurs it will be an empty String, otherwise the Exception’s message.

The whole Class

Here is the full code of the ”ListViewContent“-Class:

Option Strict On

Imports System.Text
Imports System.Xml
Imports System.IO
Imports System.Text.RegularExpressions

Public MustInherit Class ListViewContent

#Region "Private Methods for saving and loading the content of a ListView"

#Region "Save the whole content of a ListView with groups into a file"

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()


                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1
                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Groups> will contain all Groups of The ListView
                .WriteStartElement("Groups")

                For j As Integer = 0 To lsv.Groups.Count - 1
                    ' <Group
                    .WriteStartElement("Group")
                    .WriteAttributeString("name", lsv.Groups(j).Header)
                    ' name="xyz"
                    .WriteAttributeString("headerAlignment", lsv.Groups(j).HeaderAlignment.ToString)
                    ' headerAlignment="xyz">

                    ' Loop through the Items of the given ListView...
                    For k As Integer = 0 To lsv.Groups(j).Items.Count - 1

                        .WriteStartElement("item") ' <item
                        .WriteAttributeString("text", lsv.Groups(j).Items(k).Text) ' text="xyz"

                        ' ...and SubItems to write them to the Document
                        For l As Integer = 0 To lsv.Groups(j).Items(k).SubItems.Count - 2

                            .WriteStartElement("subItem") ' <subItem
                            .WriteAttributeString("text", lsv.Groups(j).Items(k).SubItems(l + 1).Text) ' text="xyz"

                            .WriteEndElement() ' />

                        Next

                        ' At the end of each passage in this loop, the Item-Tag will be ended to start with a new item 
                        .WriteEndElement() ' </item>
                    Next

                    ' At the end of each passage in this loop, the Group-Tag will be ended to start with a new group
                    .WriteEndElement() ' </Group>
                Next

                ' Complete the Groups-Tag to signalize that all groups have been written to the document
                .WriteEndElement() '</Groups>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#Region "Load the whole content of a ListView with groups from a file"

    ''' <summary>
    ''' Loads the whole content of a ListView with groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
     Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim grp As New ListViewGroup
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If

                    End If

                    ' The tag <Group> was found; it contains the name of the group and its text alignment
                    If .Name = "Group" Then

                        'If the Tag is a StartElement (<Group>, and not </Group>), the ListView is added all the saved Groups
                        If .IsStartElement Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("headerAlignment").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Set the required properties of the ListViewGroup (name and text alignment)
                            grp = New ListViewGroup(.GetAttribute("name"), align)

                            ' Add the group to the ListView
                            lsv.Groups.Add(grp)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            listItem.Group = grp
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then
                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region


#Region "Save the whole content of a ListView without groups into a file"

    ''' <summary>
    ''' Saves the content of a ListView control which has groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsSave(ByVal lsv As ListView, ByVal destinationPath As String) As String
        Try
            ' Set the encoding to Unicode
            Dim enc As New UnicodeEncoding

            ' Declare a XmlTextWriter-Object, with which we are going to write the config file
            Dim XMLobj As XmlTextWriter = New XmlTextWriter(destinationPath, enc)

            With XMLobj
                .Formatting = Formatting.Indented
                .Indentation = 4

                ' Open the document
                .WriteStartDocument()

                ' Start the document with a node named ListView, in which will be contained all the data
                .WriteStartElement("ListView") ' <ListView>

                ' The node <Columns> will contain the ColumnHeaders of the ListView
                .WriteStartElement("Columns") ' <Columns>

                ' Go through all Columns in the given ListView Control and write them into the document
                For i As Integer = 0 To lsv.Columns.Count - 1

                    .WriteStartElement("Column") ' <Column 

                    .WriteAttributeString("text", lsv.Columns(i).Text) ' text="xyz"

                    .WriteAttributeString("width", lsv.Columns(i).Width.ToString) ' width="xyz"

                    .WriteAttributeString("textAlign", lsv.Columns(i).TextAlign.ToString) ' textAlign="xyz"

                    .WriteEndElement() ' /> 

                Next

                ' Complete the Columns-Tag
                .WriteEndElement() ' </Columns>


                ' The node <Items> will contain all Items of the ListView
                .WriteStartElement("Items")    ' <Items>

                ' Loop through the given ListView's items...
                For k As Integer = 0 To lsv.Items.Count - 1

                    .WriteStartElement("item") ' <item
                    .WriteAttributeString("text", lsv.Items(k).Text) ' text="xyz">

                    ' ...and SubItems to write them to the Document
                    For l As Integer = 0 To lsv.Items(k).SubItems.Count - 2

                        .WriteStartElement("subItem") ' <subItem
                        .WriteAttributeString("text", lsv.Items(k).SubItems(l + 1).Text)    ' text="xyz"

                        .WriteEndElement()    ' />

                    Next

                    ' At the end of each passage in the main loop, the Item-Tag will be ended to start with a new item 
                    .WriteEndElement() ' </item>
                Next

                ' Complete the Items-Tag to signalize that all items have been written to the document
                .WriteEndElement() ' </Items>

                ' Close the ListView-Tag, now we're almost done
                .WriteEndElement() ' </ListView>

                ' Finally, complete the document and close the XmlTextWriter-Object
                .WriteEndDocument()
                .Close()


                '_______________________________________________________________________
                '                                                                       |
                ' For instance, a configuration file is set up like this:               |
                '-----------------------------------------------------------------------|
                '   <ListView>                                                          |
                '       <Columns>                                                       |
                '            <Column text=“Column 1“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 2“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 3“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 4“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 5“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 6“ width=“100“ textAlign=“Left“ />    |
                '            <Column text=“Column 7“ width=“100“ textAlign=“Left“ />    |
                '        </Columns>                                                     |
                '                                                                       |
                '        <Groups>                                                       |
                '            <Group name=“Group“ headerAlignment=“Left“>                |
                '                <item text=“Description“>                              |
                '                    <subItem text=“Text 0“ />                          |
                '                    <subItem text=“Text 1“ />                          |
                '                    <subItem text=“Text 2“ />                          |  
                '                    <subItem text=“Text 3“ />                          |
                '                    <subItem text=“Text 4“ />                          |
                '                    <subItem text=“Text 5“ />                          |
                '                </item>                                                |
                '            </Group>                                                   |   
                '        </Groups>                                                      |
                '    </ListView>                                                        |
                '_______________________________________________________________________|

            End With

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#Region "Load the whole content of a ListView without groups from a file"

    ''' <summary>
    ''' Loads the whole content of a ListView without groups from a file
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Private Shared Function ListViewWithNoGroupsLoad(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
    Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String
        Try
            ' If the user wants to, the ListView and its Columns are cleared, and an invisible Column will be insered to the top (index 0)
            If Not append Then lsv.Clear()
            If loadColumns Then lsv.Columns.Clear()
            If addInVisibleColumn Then lsv.Columns.Insert(0, "", 0)

            ' We need an XMLReader for reading the given configuration file
            Dim XMLReader As XmlReader = New XmlTextReader(loadedPath)

            ' Declare the ListViewItem, to wich the properties (text, SubItems) will be assigned
            Dim listItem As ListViewItem = New ListViewItem

            ' Now we're going to read the file
            With XMLReader

                ' As long as the reader hasn't come to the end of the document, this loop is executed
                Do While .Read

                    ' If the user wants to the Columns, read in their properties
                    If loadColumns Then

                        ' The tag <Columns> was found; it contains the HeaderColumns of the saved ListView
                        If .Name = "Column" Then
                            Dim align As HorizontalAlignment = 0

                            ' Convert the saved textAlign-String to a HorizontalAlignment
                            Select Case .GetAttribute("textAlign").ToLower
                                Case "left"
                                    align = HorizontalAlignment.Left
                                Case "right"
                                    align = HorizontalAlignment.Right
                                Case "center"
                                    align = HorizontalAlignment.Center
                            End Select

                            ' Add the columns with the loaded properties (text, width, text alignment)
                            lsv.Columns.Add(.GetAttribute("text"), Convert.ToInt32(.GetAttribute("width")), align)
                        End If
                    End If

                    ' The item-Tag was found; it contains the text of each Item and their SubItems
                    If .Name = "item" Then

                        ' If the Tag is a StartElement (<item>; and not </item>), assign the property "text" to the ListViewItem
                        If .IsStartElement Then
                            If addInVisibleColumn Then
                                listItem.SubItems.Add(.GetAttribute("text"))
                            Else
                                listItem.Text = .GetAttribute("text")
                            End If
                        Else

                            ' Otherwise, if it's an EndTag (</item>), the previously filled ListViewItem is added to the ListView
                            lsv.Items.Add(listItem)
                            listItem = New ListViewItem
                        End If
                    End If

                    ' The Element <subItem> was found; it contains the SubItem's text
                    If .Name = "subItem" Then

                        ' The ListView is added a SubItem with the attribute "text" from the element <subItem>
                        listItem.SubItems.Add(.GetAttribute("text"))
                    End If

                Loop

                .Close() ' Close the XMLTextReader

            End With

            ' Name all Groups as like their Headers
            For i As Integer = 0 To lsv.Groups.Count - 1
                lsv.Groups(i).Name = lsv.Groups(i).Header
            Next

            Return String.Empty    ' If this function worked faultless, an emtpy String will bei returned
        Catch ex As Exception
            Return ex.Message ' If an error occurs during some of the operation, the exception's message will be returned
        End Try
    End Function

#End Region

#End Region

    ''' <summary>
    ''' Saves the content of a ListView control which has no groups
    ''' </summary>
    ''' <param name="lsv">The ListView to be saved</param>
    ''' <param name="destinationPath">The path you want to save the config file to</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Save(ByVal lsv As ListView, ByVal destinationPath As String) As String

        If lsv.Groups.Count > 0 AndAlso lsv.ShowGroups Then
            Return ListViewWithGroupsSave(lsv, destinationPath)

        Else
            Return ListViewWithNoGroupsSave(lsv, destinationPath)

        End If

    End Function

    ''' <summary>
    ''' Loads the whole content of a ListView
    ''' </summary>
    ''' <param name="lsv">The ListView to be loaded</param>
    ''' <param name="loadedPath">The path to the configuration file</param>
    ''' <param name="append">"True" if you want to append the saved content to the ListView. "False" if you want the ListView to be cleared before the loading begins</param>
    ''' <param name="loadColumns">Indicates if the HeaderColumns should be loaded</param>
    ''' <param name="addInVisibleColumn">"True" if you want that an "invisible" column will be added to the ListView.</param>
    ''' <returns>If no errors occur the function returns an empty String, otherwise the Exception's Message</returns>
    Public Shared Function Load(ByVal lsv As ListView, ByVal loadedPath As String, Optional ByVal append As Boolean = False,
      Optional ByVal loadColumns As Boolean = True, Optional ByVal addInVisibleColumn As Boolean = False) As String

        Dim hasFileGroups As Boolean =
         Regex.IsMatch(File.ReadAllText(loadedPath).Replace(Chr(10), ""), "(<groups>.*</groups>)|(<groups />)",
         RegexOptions.IgnoreCase)

        If hasFileGroups Then
            Return ListViewWithGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        Else
            Return ListViewWithNoGroupsLoad(lsv, loadedPath, append, loadColumns, addInVisibleColumn)

        End If

    End Function

End Class

Using the Code

At “We have to identify whether the ListView to be saved has groups or not!”, I said that there have to be four private shared functions and two public shared functions in the class, and the class itself has to be declared as MustInherit. Now I’m going to explain why.

When you have a class, you usually have to dimension an instance of it to access its functions. But when you declare them as “shared”, you don’t need to. And that the class is declared as “MustInherit” means that no object can be created out of this class and you must use it as base class.

Here some examples how to use the functions:

  • Save the content to a file named “config.smp” on the user’s Desktop:
ListViewContent.Save(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"))
  • Append the currently saved file and add an invisible column to the ListView:
ListViewContent.Load(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"), True, True, True) 
  • Append the currently saved file without loading the ColumnHeaders:
ListViewContent.Load(ListView1, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "config.smp"), True, False)      

Sample Application

I have also written a demo application to demonstrate how you can use my ListViewContent-class.

save.png

In this program, you can add groups and items to the ListView control. If you choose the item “-none-“ in the ComboBox, then the groups in the ListView are hidden (set its property “ShowGroups” to “False”).

After loading with the option to add an invisible HeaderColumn, you can check the CheckBox for centering the texts of all HeaderColumns.

load.png

History

  • 23.02.2012 – First version posted.

License

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

Share

About the Author

Raffael Foidl
Student
Austria Austria
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberrht34124-Feb-12 3:19 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 23 Feb 2012
Article Copyright 2012 by Raffael Foidl
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid