Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
(ebook) Visual Studio .NET Mastering Visual Basic.pdf
Скачиваний:
137
Добавлен:
17.08.2013
Размер:
15.38 Mб
Скачать

THE LISTVIEW CONTROL 773

Text property This property is the caption of the current item.

SubItems collection This property holds the subitems of the current ListViewItem. To retrieve a specific subitem, use a statement like the following:

sitem = ListView1.Items(idx1).SubItems(idx2)

where idx1 is the index of the item and idx2 the index of the desired subitem.

To add a new subitem to the SubItems collection, use the Add method, passing the text of the subitem as argument:

LItem.SubItems.Add(“subitem’s caption”)

The argument of the Add method can also be a ListViewItem object. If you want to add a subitem at a specific location, use the Insert method. The Insert method of the SubItems collection accepts two arguments: the index of the subitem before which the new subitem will be inserted and a string or ListViewItem to be inserted:

LItem.SubItems.Insert(idx, subitem)

Like the ListViewItem objects, each subitem can have its own font, which is set with the Font property.

Remove method This method removes an item by index. When you remove an item, it takes with it all of its subitems.

SetSubItemBackColor method This method sets the background color of the current subitem.

SetSubItemForeColor method This method sets the foreground color of the current subitem.

The items of the ListView control can be accessed through the ListItems property, which is a collection. As such, it exposes the standard members of a collection, which are described in the following section.

The Items Collection

All the items on the ListView control form a collection, the Items collection. This collection exposes the typical members of a collection that let you manipulate the control’s items. These members are discussed next:

Add method This method adds a new item to the Items collection. The syntax of the Add method is

ListView1.Items.Add(caption)

You can also specify the index of the image to be used along with the item and a collection of subitems to be appended to the new item, with the following form of the Add method:

ListView1.Items.Add(caption, imageIndex)

where imageIndex is the index of the desired image on the associated ImageList control.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

774 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

Finally, you can create a ListViewItem object in your code and then add it to the ListView control with the following form of the Add method:

ListView1.Items.Add(listItemObj)

The following statements create a new item, set its individual subitems, and then add the newly created ListViewItem object to the control:

Dim LItem As New ListViewItem()

LItem.Text = “new item”

LItem.SetSubItem(0, “sub item 1a”)

LItem.SetSubItem(1, “sub item 1b”)

LItem.SetSubItem(2, “sub item 1c”)

ListView1.ListItems.Add(LItem)

Count property Returns the number of items in the collection.

Clear method Removes all the items from the collection.

Item property Retrieves an item specified by an index value.

Remove method Removes an item from the Items collection.

The SubItems Collection

Each item in the ListView control may also have subitems. You can think of the item as the key of a record and the subitems as the other fields of the record. The subitems are displayed only in Report mode, but they are available to your code in any view. For example, you can display all items as icons, and when the user clicks on an icon, show the values of the selected item’s subitems on other controls.

To access the subitems of a given item, use its SubItems collection. The following statements add an item and three subitems to the ListView1 control:

Dim LItem As ListViewItem

Set LItem = ListView1.Items.Add(, , “Alfreds Futterkiste”)

LItem.SubItems(1) = “Maria Anders”

LItem.SubItems(2) = “030-0074321”

LItem.SubItems(3) = “030-0076545”

To access the SubItems collection, you must have a reference to the item to which the subitems belong. The Add method returns a reference to the newly added item, the LItem variable, which is then used to access the item’s subitems, as shown in the last example.

Displaying the subitems on the control requires some overhead. Subitems are displayed only in Report view mode. However, setting the View property to Report is not enough. You must first create the columns of the Report view, as explained earlier. The ListView control displays only as many subitems as there are columns in the control. The first column, with the header Company, displays the items of the list. The following columns display the subitems. Moreover, you can’t specify which subitem will be displayed under each header. The first subitem (“Maria Anders” in the above

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE LISTVIEW CONTROL 775

example) will be displayed under the second header, the second subitem (“030-0074321” in the same example) will be displayed under the third header, and so on. At runtime, the user can rearrange the columns by dragging them with the mouse. To disable the rearrangement of the columns at runtime, set the control’s AllowColumnReorder property to False (its default value is True).

Unless you set up each column’s width, all columns will have the same width. The width of individual columns is in pixels, and it’s usually specified as a percentage of the total width of the control, especially if the control is docked to the form. The following code sets up a ListView control with four headers, all having the same width:

Dim LWidth As Integer

LWidth = ListView1.Width – 5

ListView1.ColumnHeaders.Add(“Company”, LWidth / 4)

ListView1.ColumnHeaders.Add(“Contact”, LWidth / 4)

ListView1.ColumnHeaders.Add(“Phone”, LWidth / 4)

ListView1.ColumnHeaders.Add(“FAX”, LWidth / 4)

ListView1.View = DetailsView

This subroutine sets up four headers of equal width. The first header corresponds to the item (not a subitem). The number of headers you set up must be equal to the number of subitems you want to display on the control plus one. The constant 5 is subtracted to compensate for the width of the column separators.

VB.NET at Work: The ListViewDemo Project

Let’s put together the members of the ListView control to create a sample application that populates a ListView control and enumerates its items. The application is called ListViewDemo and you’ll find it in this chapter’s folder on the CD. The application’s form, shown in Figure 16.13, contains a ListView control whose items can be displayed in all possible views, depending on the status of the OptionButton controls in the List Style section on the right side of the form.

Figure 16.13

The ListViewDemo project demonstrates the basic members of the ListView control.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

776 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

When the application starts, it sets up the headers (columns) of the ListView control. You can comment out the lines that insert the headers in the Form’s Load event and then run the project to see what happens when the control is switched to Report view.

Let’s start by looking at the form’s initialization code. The control’s headers and their widths were set at design time, through the ColumnHeader Collection Editor, as explained earlier.

To populate the ListView control, click the Populate List button, whose code is shown next. The code creates a new ListViewItem object for each item to be added. Then it calls the Add method of the SubItems collection to add the item’s subitems (contact, phone, and fax numbers). After the ListViewItem has been set up, it’s added to the control with the Add method of its Items collection.

Listing 16.12 shows the statements that insert the first two items in the list. The remaining items are added with similar statements, which need not be repeated here. The sample data I used in the ListViewDemo application came from the NorthWind sample database, which is installed along with Visual Basic.

Listing 16.12: Populating a ListView Control

Dim LItem As New ListViewItem()

LItem.Text = “Alfreds Futterkiste”

LItem.SubItems.Add(“Anders Maria”)

LItem.SubItems.Add(“030-0074321”)

LItem.SubItems.Add(“030-0076545”)

LItem.ImageIndex = 0

ListView1.Items.Add(LItem)

LItem = New ListViewItem()

LItem.Text = “Around the Horn”

LItem.SubItems.Add(“Hardy Thomas”)

LItem.SubItems.Add(“(171) 555-7788”)

LItem.SubItems.Add(“(171) 555-6750”)

LItem.ImageIndex = 0

ListView1.Items.Add(LItem)

Enumerating the List

The Enumerate List button scans all the items in the list and displays them along with their subitems in the Output window. To scan the list, you must set up a loop that enumerates all the items in the Items collection. For each item in the list, set up a nested loop that scans all the subitems of the current item. The complete code for the Enumerate List button is shown in Listing 16.13.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE LISTVIEW CONTROL 777

Listing 16.13: Enumerating Items and SubItems

Private Sub bttnEnumerate_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles bttnEnumerate.Click Dim i, j As Integer

Dim LItem As ListViewItem

For i = 0 To ListView1.Items.Count - 1 LItem = ListView1.Items(i) Console.WriteLine(LItem.Text)

For j = 0 To LItem.SubItems.Count - 1

Console.WriteLine(“ “ & ListView1.Columns(j).Text & _

“ & Litem.SubItems(j).Text)

Next

Next

End Sub

The output of this code in the Output window is shown next. The subitems appear under the corresponding item, and they are indented by three spaces.

Alfreds Futterkiste

Company Alfreds Futterkiste

Contact Anders Maria

Telephone 030-0074321

FAX 030-0076545

Around the Horn

Company Around the Horn

Contact Hardy Thomas

Telephone (171) 555-7788

FAX (171) 555-6750

The code in Listing 16.13 uses a For…Next loop to iterate through the items of the control. You can also set up a For Each…Next loop, as shown here:

For Each ListViewItem In ListView1.Items

{ same statements }

Next

Sorting the ListView Control

The ListView control provides a Sorting method, which allows you to specify how the list’s items will be sorted. Each item may contain any number of subitems, and you should be able to sort the list according to any column. The values stored in the subitems may represent different data types (numeric values, strings, dates, and so on). The control doesn’t provide a default sorting mechanism for all data types. Instead, it uses a custom Comparer object, which you supply, to sort the items. The topic of building custom comparers has been discussed in detail in Chapter 11. As a reminder, the custom comparer is implemented as a function, which compares two items and returns an integer value (–1, 0, or 1), which indicates the order of the two items. Once this function is in place, the control uses it to sort its items.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

778 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

The ListView control’s ListViewItemSorter property accepts the name of a custom comparer, and the items on the control are sorted according to the custom comparer as soon as you set the Sorting property. As you may recall, you can provide several custom comparers and sort the items in many different ways. If you plan to display subitems along with your items in Report view, you should make the list sortable by any column. It’s customary for a ListView control to sort its items according to the values in a specific column each time the header of this column is clicked. And this is exactly the type of functionality you’ll add to the ListViewDemo project in this section.

The ListViewDemo control displays contact information. The items are company names, and the first subitem under each item is the name of a contact. We’ll create two custom comparers to sort the list according to either company name or contact. The two methods are identical since they compare strings, but it’s not any more complicated to compare dates, distances, and so on.

Let’s start with the two custom comparers. Each comparer must be implemented in its own class, and you assign the name of the custom comparer to the ListViewItemProperty of the control. Listing 16.14 shows the ListCompanyComparer and the ListContactComparer.

Listing 16.14: The Two Custom Comparers for the ListViewDemo Project

Class ListCompanySorter Implements IComparer

Public Function CompareTo(ByVal o1 As Object, ByVal o2 As Object) As Integer _ Implements System.Collections.IComparer.compare

Dim item1, item2 As ListViewItem item1 = CType(o1, ListViewItem) item2 = CType(o2, ListViewItem)

If item1.ToString.ToUpper > item2.ToString.ToUpper Then Return 1

Else

If item1.ToString.ToUpper < item2.ToString.ToUpper Then Return -1

Else Return 0

End If End If

End Function End Class

Class ListContactSorter Implements IComparer

Public Function CompareTo(ByVal o1 As Object, ByVal o2 As Object) As Integer _ Implements System.collections.IComparer.compare

Dim item1, item2 As ListVewItem item1 = CType(o1, ListViewItem) item2 = CType(o2, ListViewItem)

If item1.SubItems(1).ToString.ToUpper > _ item2.SubItems(1).ToString.ToUpper Then

Return 1 Else

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE LISTVIEW CONTROL 779

If item1.SubItems(1).ToString.ToUpper < _ item2.SubItems(1).ToString.ToUpper Then

Return -1 Else

Return 0 End If

End If

End Function End Class

The code is straightforward. If you need additional information, see the discussion of the IComparer interface in Chapter 11. The two functions are identical, except that the first one sorts according to the item and the second one sorts according to the first subitem.

To test the custom comparers, you simply assign their names to the ListViewItemSorter property of the ListView control. To take advantage of our custom comparers, we must write some code that intercepts the clicks on the control’s headers and calls the appropriate comparer. The ListView control fires the ColumnClick event each time a column header is clicked. This event handler reports the index of the column that was clicked through the e.Column argument, and we can use this argument in our code to sort the items accordingly. Listing 16.15. shows the event handler for the ColumnClick event.

Listing 16.15: The ListView Control’s ColumnClick Event Handler

Public Sub ListView1_ColumnClick(ByVal sender As Object, _

ByVal e As System.WinForms.ColumnClickEventArgs) _

Handles ListView1.ColumnClick

Select Case e.column

Case 0

ListView1.ListViewItemSorter = New ListCompanySorter()

ListView1.Sorting = SortOrder.Ascending

Case 1

ListView1.LisViewtItemSorter = New ListContactSorter()

ListView1.Sorting = SortOrder.Ascending

End Select

End Sub

Processing Selected Items

The user can select multiple items on a ListView control by default. Even though you can display a check mark in front of each item, it’s not customary. Items on a ListView control are selected with the mouse while holding down the Ctrl or Shift key.

The selected items form the SelectedListItemCollection, which is a property of the control. You can iterate through this collection with a For…Next loop or through the enumerator object exposed

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

780 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

by the collection. In the following example, I use a For Each…Next loop. Listing 16.16 is the code behind the Selected Items button of the ListViewDemo project. It goes through the selected items and displays each item, along with its subitems, in the Output window. Notice that you can select multiple items in any view, even when the subitems are not visible. They’re still there, however, and they can be retrieved through the SubItems collection.

Listing 16.16: Iterating the Selected Items on a ListView Control

Private Sub bttnIterate_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles bttnIterate.Click

Dim LItem As ListViewItem

Dim LItems As ListView.SelectedListViewItemCollection

LItems = ListView1.SelectedItems

For Each LItem In LItems

Console.Write(LItem.Text & vbTab)

Console.Write(LItem.SubItems(0).ToString & vbTab)

Console.Write(LItem.SubItems(1).ToString & vbTab)

Console.WriteLine(LItem.SubItems(2).ToString & vbTab)

Next

End Sub

VB.NET at Work: The CustomExplorer Project

The last example in this chapter combines the TreeView and ListView controls. It’s a fairly advanced example, but I’ve included it here for the most ambitious readers. It can also be used as the starting point for many custom applications, so give it a try. You can always come back to this project after you’ve mastered other aspects of the language.

The Explorer project, shown in Figure 16.14, is the core of a custom Explorer window, which displays a structured list of folders on the left pane and the list of files in the selected folder on the right pane. The left pane is populated when the application starts, but it takes a while. On my Pentium system, it takes nearly five seconds to populate the TreeView control with the structure of the C:\Windows folder. You can expand any folder on this pane and view its subfolders. To view the files in a folder, click its name, and the right pane will be populated with the names of the files, along with vital data, such as the file size, date of creation, and date of last modification. You already know how to manipulate folders and files, and you should be able to follow the code easily. If you have to, you can review the Directory and FileInfo objects and their properties in Chapter 13.

This section’s project is not limited to displaying folders and files; you can populate the two controls with data from several sources. For example, you can display customers in the left pane (and organize them by city or state) and their related data on the right pane (e.g., invoices and payments). Or you can populate the left pane with product names and the right pane with the respective sales. In general, you can use it as an interface for many types of applications. You can even use it as a custom Explorer to add features that are specific to your applications.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE LISTVIEW CONTROL 781

Figure 16.14

The Explorer project demonstrates how to combine a TreeView and a ListView control on the same form.

The left pane is populated from within the Form’s Load event handler subroutine. The code makes use of the Directory and File objects, which were discussed earlier in the book. Following is the code that populates the TreeView control with the subfolders of the C:\Program Files folder.

Dim Nd As New TreeNode()

Nd = TreeView1.Nodes.Add(“C:\Program Files”)

ScanFolder(“c:\Program Files”, ND)

Nd represents the control’s root node, and it’s the name of the folder to be scanned. To populate the control with the files of another folder or drive, change the name of the path accordingly. The code is short and, as you have guessed, all the work is done by the ScanFolder() subroutine. The ScanFolder() subroutine is a short, recursive procedure that scans all the folders under C:\Program Files and is shown in Listing 16.17. The second argument is the root node, under which the entire tree of the specified folder will appear.

Listing 16.17: The ScanFolder() Subroutine

Sub ScanFolder(ByVal folderSpec As String, ByRef currentNode As TreeNode) Dim thisFolder As String

Dim allFolders() As String

allFolders = Directory.GetDirectories(folderSpec) For Each thisFolder In allFolders

Dim Nd As TreeNode

Nd = New TreeNode(thisFolder) currentNode.Nodes.Add(Nd) folderSpec = thisFolder ScanFolder(folderSpec, Nd)

Next End Sub

The variable thisFolder represents the current folder (the one passed to the ScanFolder() subroutine as argument). Using this variable, the program creates the allFolders collection, which contains all the subfolders of the current folder. Then it scans every folder in this collection and adds its name to the TreeView control. The newly added node is the name of the folder. Within a given folder, all

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

782 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

subfolder names are unique. After adding a folder to the TreeView control, the procedure must scan the subfolders of the current folder. It does so by calling itself and passing another folder’s name as an argument. If you find the recursive implementation of the subroutine difficult to understand, go through the material of Chapter 18. You can use the ScanFolder() subroutine as is in your projects; just pass to it a reference to the folder you want to scan. The first argument to the ScanFolder() subroutine is the name of the folder to be scanned.

Notice that the ScanFolder subroutine doesn’t simply scan a folder. It also adds a node to the TreeView control for each new folder it runs into. That’s why it accepts two arguments, the name of the current folder and the node that represents this folder on the control. All folders are placed under their parent folder, and the structure of the tree represents the structure of your hard disk (or the section of the hard disk you’re mapping on the TreeView control). All this is done with a small recursive subroutine, the ScanFolder() subroutine.

Viewing a Folder’s Files

To view the files of a folder, click the folder’s name in the TreeView control. As explained earlier, the action of the selection of a new node is detected with the AfterSelect event. The code in the TreeView1_AfterSelect event handler, shown in Listing 16.18, displays the selected folder’s files on the ListView control.

Listing 16.18: Displaying a Folder’s Files

Private Sub TreeView1_AfterSelect(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles TreeView1.AfterSelect

Dim Nd As TreeNode Dim pathName As String

Nd = TreeView1.SelectedNode pathName = Nd.Text ShowFiles(pathName)

End Sub

The ShowFiles() subroutine actually displays the file names, and some of their properties, in the specified folder on the ListView control. Its code is shown in Listing 16.19.

Listing 16.19: The ShowFiles Subroutine

Sub ShowFiles(ByVal selFolder As String) ListView1.Items.Clear()

Dim files() As String Dim file As String

files = Directory.GetFiles(selFolder) For Each file In files

Dim LItem As New ListViewItem() LItem.Text = ExtractFileName(file) Dim FI As New FileInfo(file)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SAVING A TREE’S NODES TO DISK 783

LItem.SubItems.Add(FI.Length.ToString(“#,###”))

LItem.SubItems.Add(FormatDateTime(Directory.GetCreationTime(file), _

DateFormat.ShortDate))

LItem.SubItems.Add(FormatDateTime(Directory.GetLastAccessTime(file), _

DateFormat.ShortDate))

ListView1.Items.Add(LItem)

Next

End Sub

The ShowFiles subroutine creates a ListItem for each file. The item’s caption is the file’s name. The first subitem is the file’s length. The other two items are the file’s creation and last access times. You can add more subitems if your application needs them. The ListView control in this example uses the Report view to display the items, so you don’t have to worry about images. If we used the Large Icon or Small Icon view, we’d have to come up with icons for all types of files (or the most common ones).

As mentioned earlier, the ListView control isn’t going to display any items unless you specify the proper columns through the Columns collection. The columns, along with their widths and captions, were set at design time through the ColumnHeader Collection Editor.

Saving a Tree’s Nodes to Disk

You’ve learned how to populate the TreeView and ListView controls, how to manipulate them at runtime, and how to sort the ListView control in any way you wish, but what good are all these techniques unless you can save the tree’s nodes or the ListItems to a disk file and reuse them in a later session?

In Chapter 11, you saw how to serialize complicated objects, like an ArrayList. It would be nice if the TreeNode object were serializable; you could serialize the root node and all the nodes under it with a single call to the Serialize method. Unfortunately, this is not the case. Well, how about subclassing the TreeNode object? Create a new class that inherits from the TreeNode class and is serializable. This is an option, but it’s not simple. Besides, the subclassed TreeNode object will be specific to an application.

Since we already know how to serialize an ArrayList, we can extract the items of the TreeView control to an ArrayList and then serialize the ArrayList. The code of this section serializes the strings displayed on a TreeView control. You know how to scan the nodes of a TreeView control, and the code for serializing the control’s nodes seems trivial. It’s not quite so.

The ArrayList has a linear structure: each item is independent of any other. The TreeView control, however, has a hierarchical structure. Most of its nodes are children of other nodes, as well as parents of other nodes. Therefore, we must store not only the data (strings), but their structure as well. To store this information, we’ll create a new structure with two fields: one for the node’s value and another for the node’s indentation:

<Serializable()> Structure sNode

Dim node As String

Dim level As Integer

End Structure

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

784 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

We want to be able to serialize this structure, so we must prefix it with the <Serializable> attribute. The level field is the node’s indentation; the level field of all root nodes is zero. The nodes immediately under the root have a level of 1, and so on. To serialize the TreeView control, we’ll iterate through its nodes and store each node to an sNode variable. Each time we switch to a child node, we’ll increase the current value of the level by one. Each time we move up to a parent node, we’ll decrease the same value accordingly. All the sNode structures will be added to an ArrayList, which will then be serialized.

Likewise, when we read the ArrayList from the disk file, we must reconstruct the original tree. Items with a level value of zero are root nodes. The first item with a level value of 1 is the first child node under the most recently added root node. As long as the level field doesn’t change, the new nodes are added under the same parent. When this value increases, we must create a new child node under the current node. When this value decreases, we must move up to the current node’s parent and create a new child under it. The only complication is that a level value may decrease by more than one. In this case, we must move up to the parent’s parent, or even higher in the hierarchy. Figure 16.15 shows a typical TreeView control and how its nodes are stored in the ArrayList.

Figure 16.15

The structure of the nodes of a TreeView control

The control on the left is a TreeView control, populated at design time. The control on the right is a ListBox control with the items of the ArrayList. The first column is the level field (the node’s indentation), while the second column is the node’s text.

Now we can look at the code for serializing the control. The code presented in this section is part of the Globe project—namely, it’s the code behind the Save Nodes and Load Nodes commands of the File menu. The File Save Nodes command prompts the user with the File Save dialog box for the path of a file, where the nodes will be stored. Then it calls the SaveNodes() subroutine, passing the root node of the control and the path of the file where the items will be stored. Listing 16.20 shows this menu item’s Click event handler.

Listing 16.20: The File Save Nodes MenuItem’s Event Handler

Private Sub FileSave_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles FileSave.Click

SaveFileDialog1.DefaultExt = “XML”

If SaveFileDialog1.ShowDialog = DialogResult.OK Then

CreateList(GlobeTree.Nodes(0), SaveFileDialog1.FileName)

End If

End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SAVING A TREE’S NODES TO DISK 785

TheCreateList() subroutine goes through the nodes of the root node and stores them into the GlobeNodes ArrayList. This ArrayList is declared at the Form level with the following statement:

Dim GlobeNodes As New ArrayList()

CreateList() is a recursive subroutine that scans the immediate children of the node passed as argument. If a child node contains its own children, the subroutine calls itself to iterate through the children. This process may continue to any depth. The code of the subroutine is shown in Listing 16.21; its structure is similar to the ScanNode() subroutine of Listing 16.11.

Listing 16.21: The CreateList() Subroutine

Sub CreateList(ByVal node As TreeNode, ByVal fName As String) Static level As Integer

Dim thisNode As TreeNode Dim myNode As sNode Application.DoEvents() myNode.level = level myNode.node = node.Text GlobeNodes.Add(myNode)

If node.Nodes.Count > 0 Then level = level + 1

For Each thisNode In node.Nodes CreateList(thisNode, fName)

Next

level = level - 1 End If SaveNodes(fName)

End Sub

After the ArrayList has been populated, the code calls the SaveNodes() subroutine, which persists the ArrayList to a disk file. The path of the file is the second argument of the CreateList() subroutine. SaveNodes(), shown in Listing 16.22, is a straightforward subroutine that serializes the GlobeNodes ArrayList to disk.

Listing 16.22: The SaveNodes() Subroutine

Sub SaveNodes(ByVal fName As String) Dim formatter As SoapFormatter Dim saveFile As FileStream saveFile = File.Create(fName) formatter = New SoapFormatter()

formatter.Serialize(saveFile, GlobeNodes) saveFile.Close()

End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

786 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

Tip For more information on serializing ArrayLists, see Chapter 13.

The File Load Nodes command prompts the user for a filename and then calls the LoadNodes() subroutine to read the ArrayList persisted in this file and load the control with its nodes. The Click event handler of the Load Nodes command is shown in Listing 16.23.

Listing 16.23: The File Load Nodes MenuItem’s Event Handler

Private Sub FileLoad_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles FileLoad.Click

OpenFileDialog1.DefaultExt = “XML”

If OpenFileDialog1.ShowDialog = DialogResult.OK Then

LoadNodes(GlobeTree, OpenFileDialog1.FileName)

End If

End Sub

The LoadNodes() subroutine loads the items read from the file into the GlobeNodes ArrayList and then calls the ShowNodes() subroutine to load the nodes from the ArrayList onto the control. The LoadNodes() subroutine is shown in Listing 16.24.

Listing 16.24: Loading the GlobeNodes ArrayList

Sub LoadNodes(ByVal TV As TreeView, ByVal fName As String) TV.Nodes.Clear()

Dim formatter As SoapFormatter Dim openFile As FileStream

openFile = File.Open(fName, FileMode.Open) formatter = New SoapFormatter()

GlobeNodes = CType(formatter.Deserialize(openFile), ArrayList) openFile.Close()

ShowNodes(TV) End Sub

The most interesting code is in the ShowNodes() subroutine, which goes through the items in the ArrayList and re-creates the original structure of the TreeView control. At each iteration, the subroutine examines the value of the item’s level field. If it’s the same as the current node’s level, then the new node is added under the same node as the current node (we’re on the same indentation level). If the current item’s level field is larger than the current node’s level, then the new node is added under the current node (it’s a child of the current node). Finally, if the current item’s level field is smaller than then current node’s level, the code moves up to the parent of the current node. This step may be repeated several times, depending on the difference of the two levels. If the current node’s level is 4 and the level field of the new node is 1, the code will move up three levels (it will actually be added under the most recent root node). Listing 16.25 is the code of the ShowNodes() subroutine.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SAVING A TREE’S NODES TO DISK 787

Listing 16.25: The ShowNodes() Subroutine

Sub ShowNodes(ByVal TV As TreeView) Dim o As Object

Dim currNode As TreeNode Dim level As Integer = 0

Dim fromLowerLevel As Integer Dim i As Integer

For i = 0 To GlobeNodes.Count - 1 o = GlobeNodes(i)

If o.level = level Then

If currNode Is Nothing Then

currNode = TV.Nodes.Add(o.node.ToString) Else

currNode = currNode.Parent.Nodes.Add(o.node.ToString) End If

Else

If o.level > level Then

currNode = currNode.Nodes.Add(o.node.ToString) level = o.level

Else

While o.level <= level currNode = currNode.Parent level = level - 1

End While

currNode = currNode.Nodes.Add(o.node.ToString) End If

End If TV.ExpandAll()

Application.DoEvents() Next

End Sub

Why did I use a SoapFormatter and not a BinaryFormatter to persists the data? I just wanted to see the structure of the data in text format. You will probably change the code to save the data in binary format, because it’s much more compact. Of course, XML and SOAP are quite fashionable these days. You can also claim that the data can be read on any other system and you follow industry standards. I suggest you use mostly the binary format for storing application data. If you want to exchange data with another system, create a DataSet in XML format—a topic discussed in Part IV of this book.

The technique shown here persists the strings displayed on the control, and it work with most applications. If you’re using a TreeView control to store objects, you must adjust the code of this section to persist the objects, not just strings.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

788 Chapter 16 THE TREEVIEW AND LISTVIEW CONTROLS

If you’re wondering what the persisted nodes look like in the XML file, here’s how the first few items of the Globe tree are persisted. The file format is verbose indeed, but the items of interest appear in bold (they’re the node and level fields):

-<item xsi:type=”a3:NodeSerializer+sNode” xmlns:a3=”http://schemas.microsoft.com/ clr/nsassem/Globe/Globe%2C%20Version%3D1.0.638.15776%2C%20Culture%3Dneutral%2C%20Pu blicKeyToken%3Dnull”>

<node id=”ref-4”>Globe</node> <level>0</level>

</item>

-<item xsi:type=”a3:NodeSerializer+sNode” xmlns:a3=”http://schemas.microsoft.com/ clr/nsassem/Globe/Globe%2C%20Version%3D1.0.638.15776%2C%20Culture%3Dneutral%2C%20Pu blicKeyToken%3Dnull”>

<node id=”ref-5”>Africa</node> <level>1</level>

</item>

-<item xsi:type=”a3:NodeSerializer+sNode” xmlns:a3=”http://schemas.microsoft.com/ clr/nsassem/Globe/Globe%2C%20Version%3D1.0.638.15776%2C%20Culture%3Dneutral%2C%20Pu blicKeyToken%3Dnull”>

<node id=”ref-6”>Egypt</node> <level>2</level>

</item>

-<item xsi:type=”a3:NodeSerializer+sNode” xmlns:a3=”http://schemas.microsoft.com/ clr/nsassem/Globe/Globe%2C%20Version%3D1.0.638.15776%2C%20Culture%3Dneutral%2C%20Pu blicKeyToken%3Dnull”>

<node id=”ref-7”>Alexandria</node> <level>3</level>

</item>

Persisting the items of a ListView control is even simpler. You must create a new structure that reflects the structure of each row (the item and subitems of each row), and then create an ArrayList with items of this type. Persisting the ArrayList is straightforward and so is the loading of the control, since the ListView control doesn’t have a hierarchical structure. Its items are organized in a linear fashion, just like the items of the ArrayList.

To reuse the subroutines that serialize and deserialize the nodes of a TreeView control, you can create a new class that exposes the CreateList() and LoadNodes() subroutines as methods. The other two subroutines that actually save the ArrayList to disk and load a disk file into the ArrayList are Private to the class and can be called only from within the code of the two methods.

The Globe project on the CD contains a class, the NodeSerializer class. This class contains the code and the declarations discussed in this section, and I will not repeat the code here. To use this class in your code, you must create an instance of the class and call the appropriate method.

To persist the TreeView control to a file, use the following statements:

Dim NS As New NodeSerializer()

NS.CreateList(GlobeTree.Nodes(0), SaveFileDialog1.FileName)

To load a TreeView control previously saved to a file, use the following statements:

Dim NS As New NodeSerializer()

NS.LoadNodes(GlobeTree, OpenFileDialog1.FileName)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com