- •Using Your Sybex Electronic Book
- •Acknowledgments
- •Contents at a Glance
- •Introduction
- •Who Should Read This Book?
- •How About the Advanced Topics?
- •The Structure of the Book
- •How to Reach the Author
- •The Integrated Development Environment
- •The Start Page
- •Project Types
- •Your First VB Application
- •Making the Application More Robust
- •Making the Application More User-Friendly
- •The IDE Components
- •The IDE Menu
- •The Toolbox Window
- •The Solution Explorer
- •The Properties Window
- •The Output Window
- •The Command Window
- •The Task List Window
- •Environment Options
- •A Few Common Properties
- •A Few Common Events
- •A Few Common Methods
- •Building a Console Application
- •Summary
- •Building a Loan Calculator
- •How the Loan Application Works
- •Designing the User Interface
- •Programming the Loan Application
- •Validating the Data
- •Building a Math Calculator
- •Designing the User Interface
- •Programming the MathCalculator App
- •Adding More Features
- •Exception Handling
- •Taking the LoanCalculator to the Web
- •Working with Multiple Forms
- •Working with Multiple Projects
- •Executable Files
- •Distributing an Application
- •VB.NET at Work: Creating a Windows Installer
- •Finishing the Windows Installer
- •Running the Windows Installer
- •Verifying the Installation
- •Summary
- •Variables
- •Declaring Variables
- •Types of Variables
- •Converting Variable Types
- •User-Defined Data Types
- •Examining Variable Types
- •Why Declare Variables?
- •A Variable’s Scope
- •The Lifetime of a Variable
- •Constants
- •Arrays
- •Declaring Arrays
- •Initializing Arrays
- •Array Limits
- •Multidimensional Arrays
- •Dynamic Arrays
- •Arrays of Arrays
- •Variables as Objects
- •So, What’s an Object?
- •Formatting Numbers
- •Formatting Dates
- •Flow-Control Statements
- •Test Structures
- •Loop Structures
- •Nested Control Structures
- •The Exit Statement
- •Summary
- •Modular Coding
- •Subroutines
- •Functions
- •Arguments
- •Argument-Passing Mechanisms
- •Event-Handler Arguments
- •Passing an Unknown Number of Arguments
- •Named Arguments
- •More Types of Function Return Values
- •Overloading Functions
- •Summary
- •The Appearance of Forms
- •Properties of the Form Control
- •Placing Controls on Forms
- •Setting the TabOrder
- •VB.NET at Work: The Contacts Project
- •Anchoring and Docking
- •Loading and Showing Forms
- •The Startup Form
- •Controlling One Form from within Another
- •Forms vs. Dialog Boxes
- •VB.NET at Work: The MultipleForms Project
- •Designing Menus
- •The Menu Editor
- •Manipulating Menus at Runtime
- •Building Dynamic Forms at Runtime
- •The Form.Controls Collection
- •VB.NET at Work: The DynamicForm Project
- •Creating Event Handlers at Runtime
- •Summary
- •The TextBox Control
- •Basic Properties
- •Text-Manipulation Properties
- •Text-Selection Properties
- •Text-Selection Methods
- •Undoing Edits
- •VB.NET at Work: The TextPad Project
- •Capturing Keystrokes
- •The ListBox, CheckedListBox, and ComboBox Controls
- •Basic Properties
- •The Items Collection
- •VB.NET at Work: The ListDemo Project
- •Searching
- •The ComboBox Control
- •The ScrollBar and TrackBar Controls
- •The ScrollBar Control
- •The TrackBar Control
- •Summary
- •The Common Dialog Controls
- •Using the Common Dialog Controls
- •The Color Dialog Box
- •The Font Dialog Box
- •The Open and Save As Dialog Boxes
- •The Print Dialog Box
- •The RichTextBox Control
- •The RTF Language
- •Methods
- •Advanced Editing Features
- •Cutting and Pasting
- •Searching in a RichTextBox Control
- •Formatting URLs
- •VB.NET at Work: The RTFPad Project
- •Summary
- •What Is a Class?
- •Building the Minimal Class
- •Adding Code to the Minimal Class
- •Property Procedures
- •Customizing Default Members
- •Custom Enumerations
- •Using the SimpleClass in Other Projects
- •Firing Events
- •Shared Properties
- •Parsing a Filename String
- •Reusing the StringTools Class
- •Encapsulation and Abstraction
- •Inheritance
- •Inheriting Existing Classes
- •Polymorphism
- •The Shape Class
- •Object Constructors and Destructors
- •Instance and Shared Methods
- •Who Can Inherit What?
- •Parent Class Keywords
- •Derived Class Keyword
- •Parent Class Member Keywords
- •Derived Class Member Keyword
- •MyBase and MyClass
- •Summary
- •On Designing Windows Controls
- •Enhancing Existing Controls
- •Building the FocusedTextBox Control
- •Building Compound Controls
- •VB.NET at Work: The ColorEdit Control
- •VB.NET at Work: The Label3D Control
- •Raising Events
- •Using the Custom Control in Other Projects
- •VB.NET at Work: The Alarm Control
- •Designing Irregularly Shaped Controls
- •Designing Owner-Drawn Menus
- •Designing Owner-Drawn ListBox Controls
- •Using ActiveX Controls
- •Summary
- •Programming Word
- •Objects That Represent Text
- •The Documents Collection and the Document Object
- •Spell-Checking Documents
- •Programming Excel
- •The Worksheets Collection and the Worksheet Object
- •The Range Object
- •Using Excel as a Math Parser
- •Programming Outlook
- •Retrieving Information
- •Recursive Scanning of the Contacts Folder
- •Summary
- •Advanced Array Topics
- •Sorting Arrays
- •Searching Arrays
- •Other Array Operations
- •Array Limitations
- •The ArrayList Collection
- •Creating an ArrayList
- •Adding and Removing Items
- •The HashTable Collection
- •VB.NET at Work: The WordFrequencies Project
- •The SortedList Class
- •The IEnumerator and IComparer Interfaces
- •Enumerating Collections
- •Custom Sorting
- •Custom Sorting of a SortedList
- •The Serialization Class
- •Serializing Individual Objects
- •Serializing a Collection
- •Deserializing Objects
- •Summary
- •Handling Strings and Characters
- •The Char Class
- •The String Class
- •The StringBuilder Class
- •VB.NET at Work: The StringReversal Project
- •VB.NET at Work: The CountWords Project
- •Handling Dates
- •The DateTime Class
- •The TimeSpan Class
- •VB.NET at Work: Timing Operations
- •Summary
- •Accessing Folders and Files
- •The Directory Class
- •The File Class
- •The DirectoryInfo Class
- •The FileInfo Class
- •The Path Class
- •VB.NET at Work: The CustomExplorer Project
- •Accessing Files
- •The FileStream Object
- •The StreamWriter Object
- •The StreamReader Object
- •Sending Data to a File
- •The BinaryWriter Object
- •The BinaryReader Object
- •VB.NET at Work: The RecordSave Project
- •The FileSystemWatcher Component
- •Properties
- •Events
- •VB.NET at Work: The FileSystemWatcher Project
- •Summary
- •Displaying Images
- •The Image Object
- •Exchanging Images through the Clipboard
- •Drawing with GDI+
- •The Basic Drawing Objects
- •Drawing Shapes
- •Drawing Methods
- •Gradients
- •Coordinate Transformations
- •Specifying Transformations
- •VB.NET at Work: Plotting Functions
- •Bitmaps
- •Specifying Colors
- •Defining Colors
- •Processing Bitmaps
- •Summary
- •The Printing Objects
- •PrintDocument
- •PrintDialog
- •PageSetupDialog
- •PrintPreviewDialog
- •PrintPreviewControl
- •Printer and Page Properties
- •Page Geometry
- •Printing Examples
- •Printing Tabular Data
- •Printing Plain Text
- •Printing Bitmaps
- •Using the PrintPreviewControl
- •Summary
- •Examining the Advanced Controls
- •How Tree Structures Work
- •The ImageList Control
- •The TreeView Control
- •Adding New Items at Design Time
- •Adding New Items at Runtime
- •Assigning Images to Nodes
- •Scanning the TreeView Control
- •The ListView Control
- •The Columns Collection
- •The ListItem Object
- •The Items Collection
- •The SubItems Collection
- •Summary
- •Types of Errors
- •Design-Time Errors
- •Runtime Errors
- •Logic Errors
- •Exceptions and Structured Exception Handling
- •Studying an Exception
- •Getting a Handle on this Exception
- •Finally (!)
- •Customizing Exception Handling
- •Throwing Your Own Exceptions
- •Debugging
- •Breakpoints
- •Stepping Through
- •The Local and Watch Windows
- •Summary
- •Basic Concepts
- •Recursion in Real Life
- •A Simple Example
- •Recursion by Mistake
- •Scanning Folders Recursively
- •Describing a Recursive Procedure
- •Translating the Description to Code
- •The Stack Mechanism
- •Stack Defined
- •Recursive Programming and the Stack
- •Passing Arguments through the Stack
- •Special Issues in Recursive Programming
- •Knowing When to Use Recursive Programming
- •Summary
- •MDI Applications: The Basics
- •Building an MDI Application
- •Built-In Capabilities of MDI Applications
- •Accessing Child Forms
- •Ending an MDI Application
- •A Scrollable PictureBox
- •Summary
- •What Is a Database?
- •Relational Databases
- •Exploring the Northwind Database
- •Exploring the Pubs Database
- •Understanding Relations
- •The Server Explorer
- •Working with Tables
- •Relationships, Indices, and Constraints
- •Structured Query Language
- •Executing SQL Statements
- •Selection Queries
- •Calculated Fields
- •SQL Joins
- •Action Queries
- •The Query Builder
- •The Query Builder Interface
- •SQL at Work: Calculating Sums
- •SQL at Work: Counting Rows
- •Limiting the Selection
- •Parameterized Queries
- •Calculated Columns
- •Specifying Left, Right, and Inner Joins
- •Stored Procedures
- •Summary
- •How About XML?
- •Creating a DataSet
- •The DataGrid Control
- •Data Binding
- •VB.NET at Work: The ViewEditCustomers Project
- •Binding Complex Controls
- •Programming the DataAdapter Object
- •The Command Objects
- •The Command and DataReader Objects
- •VB.NET at Work: The DataReader Project
- •VB.NET at Work: The StoredProcedure Project
- •Summary
- •The Structure of a DataSet
- •Navigating the Tables of a DataSet
- •Updating DataSets
- •The DataForm Wizard
- •Handling Identity Fields
- •Transactions
- •Performing Update Operations
- •Updating Tables Manually
- •Building and Using Custom DataSets
- •Summary
- •An HTML Primer
- •HTML Code Elements
- •Server-Client Interaction
- •The Structure of HTML Documents
- •URLs and Hyperlinks
- •The Basic HTML Tags
- •Inserting Graphics
- •Tables
- •Forms and Controls
- •Processing Requests on the Server
- •Building a Web Application
- •Interacting with a Web Application
- •Maintaining State
- •The Web Controls
- •The ASP.NET Objects
- •The Page Object
- •The Response Object
- •The Request Object
- •The Server Object
- •Using Cookies
- •Handling Multiple Forms in Web Applications
- •Summary
- •The Data-Bound Web Controls
- •Simple Data Binding
- •Binding to DataSets
- •Is It a Grid, or a Table?
- •Getting Orders on the Web
- •The Forms of the ProductSearch Application
- •Paging Large DataSets
- •Customizing the Appearance of the DataGrid Control
- •Programming the Select Button
- •Summary
- •How to Serve the Web
- •Building a Web Service
- •Consuming the Web Service
- •Maintaining State in Web Services
- •A Data-Driven Web Service
- •Consuming the Products Web Service in VB
- •Summary
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 |
