Chapter 7. More Windows Controls

In this chapter, I will continue the discussion of the Windows controls. I'll start with the controls that implement the common dialog boxes and the RichTextBox control. Then I will deal with two more advanced controls, TreeView and ListView.

The .NET Framework provides a set of controls for displaying common dialog boxes, such as the Open and Color dialog boxes. Each of these controls encapsulates a large amount of functionality that would take a lot of code to duplicate. The common dialog controls are fundamental components because they enable you to design user interfaces with the look and feel of a Windows application.

You'll also explore the RichTextBox control, which is an advanced version of the TextBox control. The RichTextBox control provides all the functionality you'll need to build a word processor — WordPad is actually built around the RichTextBox control. The RichTextBox control allows you to format text by mixing fonts and attributes, aligning paragraphs differently, and so on.

The TreeView and ListView controls implement two of the more-advanced data structures. TreeView can be used to present a hierarchical list — a tree in which items that belong to other items appear under their parent with the proper indentation. For instance, a list of city and state names should be structured so that each city appears under the corresponding state. ListView can be used to present a "flat" structure where each item has a number of subitems. A typical example is a file, whose most important attributes are name, size, type, and modification date. These attributes can be presented as subitems in a list of files.

The TreeView and ListView controls were designed to hide much of the complexity of these structures, and they do this very well. They are among the more-advanced controls, and they are certainly more difficult to program than the ones discussed in the preceding chapters. These two controls, however, are the basic makings of unique user interfaces, as you'll see in this chapter's examples.

In this chapter you'll learn how to do the following:

  • Use the OpenFileDialog and SaveFileDialog controls to prompt users for filenames.

  • Use ColorDialog and FontDialog controls to prompt users for colors and typefaces.

  • Use the RichTextBox control as an advanced text editor to present richly formatted text.

  • Use the TreeView and ListView controls to present hierarchical lists and lists of structured items.

The Common Dialog Controls

A rather tedious, but quite common, task in nearly every application is to prompt the user for filenames, font names and sizes, or colors to be used by the application. Designing your own dialog boxes for these purposes would be a hassle, not to mention that your applications wouldn't conform to the basic Windows interface design principles. Truth be told, users are not fond of surprises, and all your creative effort will most likely backfire. Unexpected interface features are guaranteed to curb GUI usability and result in a number of frustrated users. In fact, all Windows applications use standard dialog boxes for common operations; two of them are shown in Figure 7.1. These dialog boxes are implemented as standard controls in the Toolbox. To use any of the common dialog controls in your interface, just place the appropriate control from the Dialogs section of the Toolbox on your form and activate it from within your code by calling the ShowDialog method.

The Open and Font common dialog boxes

Figure 7.1. The Open and Font common dialog boxes

The common dialog controls are invisible at runtime, and they're not placed on your forms because they're implemented as modal dialog boxes and they're displayed as needed. You simply add them to the project by double-clicking their icons in the Toolbox; a new icon appears in the components tray of the form, just below the Form Designer. The following common dialog controls are in the Toolbox under the Dialogs tab:

OpenFileDialog

Lets users select a file to open. It also allows the selection of multiple files for applications that must process many files at once.

SaveFileDialog

Lets users select or specify the path of a file in which the current document will be saved.

FolderBrowserDialog

Lets users select a folder (an operation that can't be performed with the OpenFileDialog control).

ColorDialog

Lets users select a color from a list of predefined colors or specify custom colors.

FontDialog

Lets users select a typeface and style to be applied to the current text selection. The Font dialog box has an Apply button, which you can intercept from within your code and use to apply the currently selected font to the text without closing the dialog box.

There are three more common dialog controls: the PrintDialog, PrintPreviewDialog, and PageSetupDialog controls. These controls are discussed in detail in the tutorial "Printing with Visual Basic 2010," available for download from www.sybex.com/go/masteringvb2010, in the context of VB's printing capabilities.

Using the Common Dialog Controls

To display any of the common dialog boxes from within your application, you must first add an instance of the appropriate control to your project. Then you must set some basic properties of the control through the Properties window. Most applications set the control's properties from within the code because common dialogs interact closely with the application. When you call the Color common dialog, for example, you should preselect a color from within your application and make it the default selection on the control. When prompting the user for the color of the text, the default selection should be the current setting of the control's ForeColor property. Likewise, the Save dialog box must suggest a filename when it first pops up (or the filename's extension, at least).

To display a common dialog box from within your code, you simply call the control's ShowDialog method, which is common for all controls. Note that all common dialog controls can be displayed only modally and they don't expose a Show method. As soon as you call the ShowDialog method, the corresponding dialog box appears onscreen, and the execution of the program is suspended until the box is closed. Using the Open, Save, and FolderBrowser dialog boxes, users can traverse the entire structure of their drives and locate the desired filename or folder. When the user clicks the Open or Save button, the dialog box closes and the program's execution resumes. The code should read the name of the file selected by the user through the FileName property and use it to open the file or store the current document there. The folder selected in the FolderBrowserDialog control is returned to the application through the SelectedPath property.

Here is the sequence of statements used to invoke the Open common dialog and retrieve the selected filename:

If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
   fileName = OpenFileDialog1.FileName
   ' Statements to open the selected file
End If

The ShowDialog method returns a value indicating how the dialog box was closed. You should read this value from within your code and ignore the settings of the dialog box if the operation was cancelled.

The variable fileName in the preceding code segment is the full pathname of the file selected by the user. You can also set the FileName property to a filename, which will be displayed when the Open dialog box is first opened:

OpenFileDialog1.FileName =
           "C:WorkFilesDocumentsDocument1.doc"
If OpenFileDialog1.ShowDialog =
              Windows.Forms.DialogResult.OK Then
   fileName = OpenFileDialog1.FileName
   ' Statements to open the selected file
End If

Similarly, you can invoke the Color dialog box and read the value of the selected color by using the following statements:

ColorDialog1.Color = TextBox1.BackColor
If ColorDialog1.ShowDialog = DialogResult.OK Then
    TextBox1.BackColor = ColorDialog1.Color
End If

The ShowDialog method is common to all controls. The Title property is also common to all controls and it's the string displayed in the title bar of the dialog box. The default title is the name of the dialog box (for example, Open, Color, and so on), but you can adjust it from within your code with a statement such as the following:

ColorDialog1.Title = "Select Drawing Color"

The ColorDialog Control

The Color dialog box, shown in Figure 7.2, is one of the simplest dialog boxes. Its Color property returns the color selected by the user or sets the initially selected color when the user opens the dialog box.

The Color dialog box

Figure 7.2. The Color dialog box

The following statements set the initial color of the ColorDialog control, display the dialog box, and then use the color selected in the control to fill the form. First, place a ColorDialog control in the form and then insert the following statements in a button's Click event handler:

Private Sub Button1_Click(...) Handles Button1.Click
    ColorDialog1.Color = Me.BackColor
    If ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
        Me.BackColor = ColorDialog1.Color
    End If
End Sub

The following sections discuss the basic properties of the ColorDialog control.

AllowFullOpen

Set this property to True if you want users to be able to open the dialog box and define their own custom colors, as you can in the one shown in Figure 7.2. The AllowFullOpen property doesn't open the custom section of the dialog box; it simply enables the Define Custom Colors button in the dialog box. Otherwise, this button is disabled.

AnyColor

This property is a Boolean value that determines whether the dialog box displays all available colors in the set of basic colors.

Color

This is the color specified on the control. You can set it to a color value before showing the dialog box to suggest a reasonable selection. On return, read the value of the same property to find out which color was picked by the user in the control:

ColorDialog1.Color = Me.BackColor
If ColorDialog1.ShowDialog = DialogResult.OK Then
    Me.BackColor = ColorDialog1.Color
End If

CustomColors

This property indicates the set of custom colors that will be shown in the dialog box. The Color dialog box has a section called Custom Colors, in which you can display 16 additional custom colors. The CustomColors property is an array of integers that represent colors. To display three custom colors in the lower section of the Color dialog box, use a statement such as the following:

Dim colors() As Integer = {222663, 35453, 7888}
ColorDialog1.CustomColors = colors

You'd expect that the CustomColors property would be an array of Color values, but it's not. You can't create the array CustomColors with a statement such as this one:

Dim colors() As Color =
           {Color.Azure, Color.Navy, Color.Teal}

Because it's awkward to work with numeric values, you should convert color values to integer values by using a statement such as the following:

Color.Navy.ToArgb

The preceding statement returns an integer value that represents the color navy. This value, however, is negative because the first byte in the color value represents the transparency of the color. To get the value of the color, you must take the absolute value of the integer value returned by the previous expression. To create an array of integers that represent color values, use a statement such as the following:

Dim colors() As Integer =
           {Math.Abs(Color.Gray.ToArgb),
            Math.Abs(Color.Navy.ToArgb),
            Math.Abs(Color.Teal.ToArgb)}

Now you can assign the colors array to the CustomColors property of the control and the colors will appear in the Custom Colors section of the Color dialog box.

SolidColorOnly

This indicates whether the dialog box will restrict users to selecting solid colors only. This setting should be used with systems that can display only 256 colors. Although today few systems can't display more than 256 colors, some interfaces are limited to this number. When you run an application through Remote Desktop, for example, only the solid colors are displayed correctly on the remote screen regardless of the remote computer's graphics card (and that's for efficiency reasons).

The FontDialog Control

The Font dialog box, shown in Figure 7.3, lets the user review and select a font and then set its size and style. Optionally, by clicking the Apply button users can also select the font's color and even apply the current settings to the selected text on a control of the form without closing the dialog box. This button isn't displayed by default; to show this button, you must set the control's ShowApply property to True. To see how the Apply button is used, see the description of the ShowApply property a little later in this section.

The Font dialog box

Figure 7.3. The Font dialog box

When the dialog is closed by clicking the OK button, you can retrieve the selected font by using the control's Font property. In addition to the OK button, the Font dialog box may contain the Apply button, which reports the current setting to your application. You can intercept the Click event of the Apply button and adjust the appearance of the text on your form while the common dialog is still visible.

The main property of this control is the Font property, which sets the initially selected font in the dialog box and retrieves the font selected by the user. The following statements display the Font dialog box after setting the initial font to the current font of the TextBox1 control. When the user closes the dialog box, the code retrieves the selected font and assigns it to the same TextBox control:

FontDialog1.Font = TextBox1.Font
If FontDialog1.ShowDialog = DialogResult.OK Then
   TextBox1.Font = FontDialog1.Font
End If

Use the following properties to customize the Font dialog box before displaying it.

AllowScriptChange

This property is a Boolean value that indicates whether the Script combo box will be displayed in the Font dialog box. This combo box allows the user to change the current character set and select a non-Western language (such as Greek, Hebrew, Cyrillic, and so on).

AllowVerticalFonts

This property is a Boolean value that indicates whether the dialog box allows the display and selection of both vertical and horizontal fonts. Its default value is False, which displays only horizontal fonts.

Color, ShowColor

The Color property sets or returns the selected font color. To enable users to select a color for the font, you must also set the ShowColor property to True.

FixedPitchOnly

This property is a Boolean value that indicates whether the dialog box allows only the selection of fixed-pitch fonts. Its default value is False, which means that all fonts (fixed- and variable-pitch fonts) are displayed in the Font dialog box. Fixed-pitch fonts, or monospaced fonts, consist of characters of equal widths that are sometimes used to display columns of numeric values so that the digits are aligned vertically.

Font

This property is a Font object. You can set it to the preselected font before displaying the dialog box and assign it to a Font property upon return. You've already seen how to preselect a font and how to apply the selected font to a control from within your application.

You can also create a new Font object and assign it to the control's Font property. Upon return, the TextBox control's Font property is set to the selected font:

Dim newFont As New Font("Verdana", 12, FontStyle.Underline)
FontDialog1.Font = newFont
If FontDialog1.ShowDialog() = DialogResult.OK Then
    TextBox1.ForeColor = FontDialog1.Color
End If

FontMustExist

This property is a Boolean value that indicates whether the dialog box forces the selection of an existing font. If the user enters a font name that doesn't correspond to a name in the list of available fonts, a warning is displayed. Its default value is True, and there's no reason to change it.

MaxSize, MinSize

These two properties are integers that determine the minimum and maximum point size the user can specify in the Font dialog box. Use these two properties to prevent the selection of extremely large or extremely small font sizes because these fonts might throw off a well-balanced interface (text will overflow in labels, for example).

ShowApply

This property is a Boolean value that indicates whether the dialog box provides an Apply button. Its default value is False, so the Apply button isn't normally displayed. If you set this property to True, you must also program the control's Apply event — the changes aren't applied automatically to any of the controls in the current form.

The following statements display the Font dialog box with the Apply button:

Private Sub Button2_Click(...) Handles Button2.Click
   FontDialog1.Font = TextBox1.Font
   FontDialog1.ShowApply = True
   If FontDialog1.ShowDialog = DialogResult.OK Then
       TextBox1.Font = FontDialog1.Font
   End If
End Sub

The FontDialog control raises the Apply event every time the user clicks the Apply button. In this event's handler, you must read the currently selected font and use it in the form so that users can preview the effect of their selection:

Private Sub FontDialog1_Apply(...) Handles FontDialog1.Apply
   TextBox1.Font = FontDialog1.Font
End Sub

ShowEffects

This property is a Boolean value that indicates whether the dialog box allows the selection of special text effects, such as strikethrough and underline. The effects are returned to the application as attributes of the selected Font object, and you don't have to do anything special in your application.

The OpenDialog and SaveDialog Controls

Open and Save As, the two most widely used common dialog boxes (see Figure 7.4), are implemented by the OpenFileDialog and SaveFileDialog controls. Nearly every application prompts users for filenames, and the .NET Framework provides two controls for this purpose. The two dialog boxes are nearly identical, and most of their properties are common, so we'll start with the properties that are common to both controls.

The Open and Save As common dialog boxes

Figure 7.4. The Open and Save As common dialog boxes

When either of the two controls is displayed, it rarely displays all the files in any given folder. Usually the files displayed are limited to the ones that the application recognizes so that users can easily spot the file they want. The Filter property limits the types of files that will appear in the Open or Save As dialog box.

It's also standard for the Windows interface not to display the extensions of filenames (although Windows distinguishes files by their filename extensions). The file type ComboBox, which appears at the bottom of the form next to the File Name box, contains the various file types recognized by the application. The various file types can be described in plain English with long descriptive names and without their filename extensions.

The extension of the default file type for the application is described by the DefaultExtension property, and the list of the file types displayed in the Save As Type box is determined by the Filter property.

To prompt the user for a file to be opened, use the following statements. The Open dialog box displays the files with the filename extension .bin only:

OpenFileDialog1.DefaultExt = ".bin"
OpenFileDialog1.AddExtension = True
OpenFileDialog1.Filter = "Binary Files|*.bin"
If OpenFileDialog1.ShowDialog() =
                 Windows.Forms.DialogResult.OK Then
    Debug.WriteLine(OpenFileDialog1.FileName)
End If

The following sections describe the properties of the OpenFileDialog and SaveFileDialog controls.

AddExtension

This property is a Boolean value that determines whether the dialog box automatically adds an extension to a filename if the user omits it. The extension added automatically is the one specified by the DefaultExtension property, which you must set before calling the ShowDialog method. This is the default filename extension of the files recognized by your application.

CheckFileExists

This property is a Boolean value that indicates whether the dialog box displays a warning if the user enters the name of a file that does not exist in the Open dialog box or if the user enters the name of a file that exists in the Save dialog box.

CheckPathExists

This property is a Boolean value that indicates whether the dialog box displays a warning if the user specifies a path that does not exist as part of the user-supplied filename.

DefaultExt

This property sets the default extension for the filenames specified on the control. Use this property to specify a default filename extension, such as .txt or .doc, so that when a file with no extension is specified by the user, the default extension is automatically appended to the filename. You must also set the AddExtension property to True. The default extension property starts with the period, and it's a string — for example, .bin.

DereferenceLinks

This property indicates whether the dialog box returns the location of the file referenced by the shortcut or the location of the shortcut itself. If you attempt to select a shortcut on your Desktop when the DereferenceLinks property is set to False, the dialog box will return to your application a value such as C:WINDOWSSYSTEM32lnkstub.exe, which is the name of the shortcut, not the name of the file represented by the shortcut. If you set the DereferenceLinks property to True, the dialog box will return the actual filename represented by the shortcut, which you can use in your code.

FileName

Use this property to retrieve the full path of the file selected by the user in the control. If you set this property to a filename before opening the dialog box, this value will be the proposed filename. The user can click OK to select this file or select another one in the control. The two controls provide another related property, the FileNames property, which returns an array of filenames. To find out how to allow the user to select multiple files, see the discussion of the MultiSelect and FileNames properties later in this chapter.

Filter

This property is used to specify the type(s) of files displayed in the dialog box. To display text files only, set the Filter property to Text files|*.txt. The pipe symbol separates the description of the files (what the user sees) from the actual filename extension (how the operating system distinguishes the various file types).

If you want to display multiple extensions, such as .bmp, .gif, and .jpg, use a semicolon to separate extensions with the Filter property. Set the Filter property to the string Images|*.bmp;*.gif;*.jpg to display all the files of these three types when the user selects Images in the Save As Type combo box under the box with the filename.

Don't include spaces before or after the pipe symbol because these spaces will be displayed on the dialog box. In the Open dialog box of an image-processing application, you'll probably provide options for each image file type as well as an option for all images:

OpenFileDialog1.Filter =
     "Bitmaps|*.bmp|GIF Images|*.gif|" &
     "JPEG Images|*.jpg|All Images|*.bmp;*.gif;*.png"

FilterIndex

When you specify more than one file type when using the Filter property of the Open dialog box, the first file type becomes the default. If you want to use a file type other than the first one, use the FilterIndex property to determine which file type will be displayed as the default when the Open dialog box is opened. The index of the first type is 1, and there's no reason to ever set this property to 1. If you use the Filter property value of the example in the preceding section and set the FilterIndex property to 2, the Open dialog box will display GIF files by default.

InitialDirectory

This property sets the initial folder whose files are displayed the first time that the Open and Save dialog boxes are opened. Use this property to display the files of the application's folder or to specify a folder in which the application stores its files by default. If you don't specify an initial folder, the dialog box will default to the last folder where the most recent file was opened or saved. It's also customary to set the initial folder to the application's path by using the following statement:

OpenFileDialog1.InitialDirectory = Application.ExecutablePath

The expression Application.ExecutablePath returns the path in which the application's executable file resides.

RestoreDirectory

Every time the Open and Save As dialog boxes are displayed, the current folder is the one that was selected by the user the last time the control was displayed. The RestoreDirectory property is a Boolean value that indicates whether the dialog box restores the current directory before closing. Its default value is False, which means that the initial directory is not restored automatically. The InitialDirectory property overrides the RestoreDirectory property.

FileNames

If the Open dialog box allows the selection of multiple files (you have set the MultiSelect property to True), the FileNames property contains the pathnames of all selected files. FileNames is a collection, and you can iterate through the filenames with an enumerator. This property should be used only with the OpenFileDialog control, even though the SaveFileDialog control exposes a FileNames property.

MultiSelect

This property is a Boolean value that indicates whether the user can select multiple files in the dialog box. Its default value is False, and users can select a single file. When the MultiSelect property is True, the user can select multiple files, but they must all come from the same folder (you can't allow the selection of multiple files from different folders). This property is unique to the OpenFileDialog control. This and the following two properties are unique to the OpenFileDialog control.

ReadOnlyChecked, ShowReadOnly

The ReadOnlyChecked property is a Boolean value that indicates whether the Read-Only check box is selected when the dialog box first pops up (the user can clear this box to open a file in read/write mode). You can set this property to True only if the ShowReadOnly property is also set to True. The ShowReadOnly property is also a Boolean value that indicates whether the Read-Only check box is available. If this check box appears on the form, the user can select it so the file will be opened as read-only. Files opened as read-only shouldn't be saved with the same filename — always prompt the user for a new filename.

The OpenFile and SaveFile Methods

The OpenFileDialog control exposes the OpenFile method, which allows you to quickly open the selected file. Likewise, the SaveFileDialog control exposes the SaveFile method, which allows you to quickly save a document to the selected file. Normally, after retrieving the name of the file selected by the user, you must open this file for reading (in the case of the Open dialog box) or writing (in the case of the Save dialog box). The topic of reading from or writing to files is discussed in detail in the tutorial "Accessing Files and Folders with the System.IO Class," which is available for download at www.sybex.com/go/masteringvb2010.

When this method is applied to the Open dialog box, the file is opened with read-only permission. The same method can be applied to the SaveFile dialog box, in which case the file is opened with read-write permission. Both methods return a Stream object, and you can call this object's Read and Write methods to read from or write to the file.

VB 2010 at Work: Multiple File Selection

The Open dialog box allows the selection of multiple files. This feature can come in handy when you want to process files en masse. You can let the user select many files, usually of the same type, and then process them one at a time. Or, you might want to prompt the user to select multiple files to be moved or copied.

To allow the user to select multiple files in the Open dialog box, set the MultiSelect property to True. The user can then select multiple files with the mouse by holding down the Shift or Ctrl key. The names of the selected files are reported by the property FileNames, which is an array of strings. The FileNames array contains the pathnames of all selected files, and you can iterate through them and process each file individually.

One of this chapter's sample projects is the MultipleFiles project, which demonstrates how to use the FileNames property. The application's form is shown in Figure 7.5. The button at the top of the form, Show Files in Folder, displays the Open dialog box, where you can select multiple files. After closing the dialog box by clicking the Open button on the Open dialog box, the application displays the pathnames of the selected files on a ListBox control.

The MultipleFiles project lets the user select multiple files in the Open dialog box.

Figure 7.5. The MultipleFiles project lets the user select multiple files in the Open dialog box.

The code behind the Open Files button is shown in Listing 7.1. In this example, I used the array's enumerator to iterate through the elements of the FileNames array. You can use any of the methods discussed in Chapter 2, "VB Programming Essentials" to iterate through the array.

Example 7.1. Processing multiple selected files

Private Sub bttnFile_Click(...) Handles bttnFile.Click
    OpenFileDialog1.Multiselect = True
    OpenFileDialog1.ShowDialog()
    Dim filesEnum As IEnumerator
    ListBox1.Items.Clear()
    filesEnum = OpenFileDialog1.FileNames.GetEnumerator()
    While filesEnum.MoveNext
       ListBox1.Items.Add(filesEnum.Current)
    End While
End Sub

The FolderBrowserDialog Control

Sometimes we need to prompt users for a folder rather than a filename. An application that processes files in batch mode shouldn't force users to select the files to be processed. Instead, it should allow users to select a folder and process all files of a specific type in the folder (it could encrypt all text documents or resize all image files, for example). As elaborate as the File Open dialog box might be, it doesn't allow the selection of a folder. To prompt users for a folder's path, use the FolderBrowser dialog box, which is a very simple one; it's shown in Figure 7.6. The FolderBrowserDialog control exposes a small number of properties, which are discussed next.

Selecting a folder via the FolderBrowser dialog box

Figure 7.6. Selecting a folder via the FolderBrowser dialog box

RootFolder

This property indicates the initial folder to be displayed when the dialog box is shown. It is not necessarily a string; it can also be a member of the SpecialFolder enumeration. To see the members of the enumeration, enter the following expression:

FolderBrowserDialog1.RootFolder =

As soon as you enter the equals sign, you will see the members of the enumeration. The most common setting for this property is My Computer, which represents the target computer's file system. You can set the RootFolder property to a number of special folders (for example, Personal, Desktop, ApplicationData, LocalApplicationData, and so on). You can also set this property to a string with the desired folder's pathname.

SelectedPath

After the user closes the FolderBrowser dialog box by clicking the OK button, you can retrieve the name of the selected folder with the SelectedPath property, which is a string, and you can use it with the methods of the System.IO namespace to access and manipulate the selected folder's files and subfolders.

ShowNewFolderButton

This property determines whether the dialog box will contain a New button; its default value is True. When users click the New button to create a new folder, the dialog box prompts them for the new folder's name and creates a new folder with the specified name under the selected folder.

VB 2010 at Work: Folder Browsing Demo Project

The FolderBrowser control is a trivial control, but I'm including a sample application, available for download from www.sybex.com/go/masteringvb2010, to demonstrate its use. The same application demonstrates how to retrieve the files and subfolders of the selected folder and how to create a directory listing in a RichTextBox control, like the one shown in Figure 7.6. The members of the System.IO namespace, which allow you to access and manipulate files and folders from within your code, are discussed in detail in the tutorial "Accessing Files and Folders," which is available for download at www.sybex.com/go/masteringvb2010.

The FolderBrowser dialog box is set to display the entire file system of the target computer and is invoked with the following statements:

FolderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer
FolderBrowserDialog1.ShowNewFolderButton = False
If FolderBrowserDialog1.ShowDialog = DialogResult.OK Then
' process files in selected folder
End If

As usual, we examine the value returned by the ShowDialog method of the control and we proceed if the user has closed the dialog box by clicking the OK button. The code that iterates through the selected folder's files and subfolders, shown in Listing 7.2, is basically a demonstration of some members of the System.IO namespace, but I'll review it briefly here.

Example 7.2. Scanning a folder

Private Sub bttnSelectFiles_Click(...) Handles bttnSelectFiles.Click
    FolderBrowserDialog1.RootFolder =
                   Environment.SpecialFolder.MyComputer
    FolderBrowserDialog1.ShowNewFolderButton = False
    If FolderBrowserDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
        RichTextBox1.Clear()
        ' Retrieve initial folder
        Dim initialFolder As String =
                   FolderBrowserDialog1.SelectedPath
        Dim InitialDir As New IO.DirectoryInfo(
                     FolderBrowserDialog1.SelectedPath)
        ' and print its name w/o any indentation
        PrintFolderName(InitialDir, "")
        ' and then print the files in the top folder
        If InitialDir.GetFiles("*.*").Length = 0 Then
            SwitchToItalics()
            RichTextBox1.AppendText(
                 "folder contains no files" & vbCrLf)
            SwitchToRegular()
        Else
            PrintFileNames(InitialDir, "")
        End If
        Dim DI As IO.DirectoryInfo
        ' Iterate through every subfolder and print it
        For Each DI In InitialDir.GetDirectories
            PrintDirectory(DI)
        Next
    End If
End Sub

The selected folder's name is stored in the initialFolder variable and is passed as an argument to the constructor of the DirectoryInfo class. The InitialDir variable represents the specified folder. This object is passed to the PrintFolderName() subroutine, which prints the folder's name in bold. Then the code iterates through the same folder's files and prints them with the PrintFileNames() subroutine, which accepts as an argument the DirectoryInfo object that represents the current folder and the indentation level. After printing the initial folder's name and the names of the files in the folder, the code iterates through the subfolders of the initial folder. The GetDirectories method of the DirectoryInfo class returns a collection of objects, one for each subfolder under the folder represented by the InitialDir variable. For each subfolder, it calls the PrintDirectory() subroutine, which prints the folder's name and the files in this folder, and then iterates through the folder's subfolders. The code that iterates through the selected folder's files and subfolders is shown in Listing 7.3.

Example 7.3. The PrintDirectory() subroutine

Private Sub PrintDirectory(ByVal CurrentDir As IO.DirectoryInfo)
    Static IndentationLevel As Integer = 0
    IndentationLevel += 1
    Dim indentationString As String = ""
    indentationString =
             New String(Convert.ToChar(vbTab), IndentationLevel)
    PrintFolderName(CurrentDir, indentationString)
    If CurrentDir.GetFiles("*.*").Length = 0 Then
        SwitchToItalics()
        RichTextBox1.AppendText(indentationString &
                 "folder contains no files" & vbCrLf)
        SwitchToRegular()
    Else
        PrintFileNames(CurrentDir, indentationString)
    End If
    Dim folder As IO.DirectoryInfo
    For Each folder In CurrentDir.GetDirectories
        PrintDirectory(folder)
    Next
    IndentationLevel -= 1
End Sub

The code that iterates through the subfolders of a given folder is discussed in detail in the tutorial "Accessing Files and Folders," available for download from www.sybex.com/go/masteringvb2010, so you need not worry if you can't figure out how it works yet. In the following sections, you'll learn how to display formatted text in the RichTextBox control.

The RichTextBox Control

The RichTextBox control is the core of a full-blown word processor. It provides all the functionality of a TextBox control; it can handle multiple typefaces, sizes, and attributes and offers precise control over the margins of the text (see Figure 7.7). You can even place images in your text on a RichTextBox control (although you won't have the kind of control over the embedded images that you have with Microsoft Word).

The fundamental property of the RichTextBox control is its Rtf property. Similar to the Text property of the TextBox control, this property is the text displayed on the control. Unlike the Text property, however, which returns (or sets) the text of the control but doesn't contain formatting information, the Rtf property returns the text along with any formatting information. Therefore, you can use the RichTextBox control to specify the text's formatting, including paragraph indentation, font, and font size or style.

A word processor based on the functionality of the RichTextBox control

Figure 7.7. A word processor based on the functionality of the RichTextBox control

RTF, which stands for Rich Text format, is a standard for storing formatting information along with the text. The beauty of the RichTextBox control for programmers is that they don't need to supply the formatting codes. The control provides simple properties to change the font of the selected text, change the alignment of the current paragraph, and so on. The RTF code is generated internally by the control and used to save and load formatted files. It's possible to create elaborately formatted documents without knowing the RTF specification.

The WordPad application that comes with Windows is based on the RichTextBox control. You can easily duplicate every bit of WordPad's functionality with the RichTextBox control, as you will see later in this chapter.

The RTF Language

A basic knowledge of the Rich Text format, its commands, and how it works will certainly help you understand the RichTextBox control's inner workings. RTF is a language that uses simple commands to specify the formatting of a document. These commands, or tags, are ASCII strings, such as par (the tag that marks the beginning of a new paragraph) and  (the tag that turns on the bold style). And this is where the value of the Rich Text format lies. RTF documents don't contain special characters and can be easily exchanged among different operating systems and computers, as long as there is an RTF-capable application to read the document.

RTF is similar to Hypertext Markup Language (HTML), and if you're familiar with HTML, a few comparisons between the two standards will provide helpful hints and insight into the RTF language. Like HTML, RTF was designed to create formatted documents that could be displayed on different systems. The following RTF segment displays a sentence with a few words in italic:

RTF0 (which stands for Rich Text Format) is a i
 document formatting languagei0 that uses simple
 commands to specify the formatting of the document.

The following is the equivalent HTML code:

<b>RTF</b> (which stands for Rich Text Format) is a
 <i>document formatting language</i> that uses simple
 commands to specify the formatting of the document.

The <b> and <i> tags of HTML, for example, are equivalent to the  and i tags of RTF. The closing tags in RTF are 0 and i0, respectively.

Although you don't need to understand the RTF specifications to produce formatted text with the RichTextBox control, if you want to generate RTF documents from within your code, visit the RTF Cookbook site at http://search.cpan.org/∼sburke/RTF-Writer/lib/RTF/Cookbook.pod. There's also a Microsoft resource on RTF at http://msdn2.microsoft.com/en-us/library/aa140277(office.10).aspx.

Text Manipulation and Formatting Properties

The RichTextBox control provides properties for manipulating the selected text on the control. The names of these properties start with the Selection or Selected prefix, and the most commonly used ones are shown in Table 7.1. Some of these properties are discussed in further detail in following sections.

Table 7.1. RichTextBox properties for manipulating selected text

Property

What It Manipulates

SelectedText

The selected text

SelectedRtf

The RTF code of the selected text

SelectionStart

The position of the selected text's first character

SelectionLength

The length of the selected text

SelectionFont

The font of the selected text

SelectionColor

The color of the selected text

SelectionBackColor

The background color of the selected text

SelectionAlignment

The alignment of the selected text

SelectionIndent, SelectionRightIndent, SelectionHangingIndent

The indentation of the selected text

RightMargin

The distance of the text's right margin from the left edge of the control

SelectionTabs

An array of integers that sets the tab stop positions in the control

SelectionBullet

Whether the selected text is bulleted

BulletIndent

The amount of bullet indent for the selected text

SelectedText

The SelectedText property represents the selected text, whether it was selected by the user via the mouse or from within your code. To assign the selected text to a variable, use the following statement:

selText=RichTextbox1.SelectedText

You can also modify the selected text by assigning a new value to the SelectedText property. The following statement converts the selected text to uppercase:

RichTextbox1.SelectedText =
               RichTextbox1.SelectedText.ToUpper

You can assign any string to the SelectedText property. If no text is selected at the time, the statement will insert the string at the location of the pointer.

SelectionStart, SelectionLength

To simplify the manipulation and formatting of the text on the control, two additional properties, SelectionStart and SelectionLength, report (or set) the position of the first selected character in the text and the length of the selection, respectively, regardless of the formatting of the selected text. One obvious use of these properties is to select (and highlight) some text on the control:

RichTextBox1.SelectionStart = 0
RichTextBox1.SelectionLength = 100

You can also use the Select method, which accepts as arguments the starting location and the length of the text to be selected.

SelectionAlignment

Use this property to read or change the alignment of one or more paragraphs. This property's value is one of the members of the HorizontalAlignment enumeration: Left, Right, and Center. Users don't have to select an entire paragraph to align it; just placing the pointer anywhere in the paragraph will do the trick because you can't align part of the paragraph.

SelectionIndent, SelectionRightIndent, SelectionHangingIndent

These properties allow you to change the margins of individual paragraphs. The SelectionIndent property sets (or returns) the amount of the text's indentation from the left edge of the control. The SelectionRightIndent property sets (or returns) the amount of the text's indentation from the right edge of the control. The SelectionHangingIndent property indicates the indentation of each paragraph's first line with respect to the following lines of the same paragraph. All three properties are expressed in pixels.

The SelectionHangingIndent property includes the current setting of the SelectionIndent property. If all the lines of a paragraph are aligned to the left, the SelectionIndent property can have any value (this is the distance of all lines from the left edge of the control), but the SelectionHangingIndent property must be zero. If the first line of the paragraph is shorter than the following lines, the SelectionHangingIndent has a negative value. Figure 7.8 shows several differently formatted paragraphs. The settings of the SelectionIndent and SelectionHangingIndent properties are determined by the two sliders at the top of the form.

Various combinations of the SelectionIndent and SelectionHangingIndent properties produce all possible paragraph styles.

Figure 7.8. Various combinations of the SelectionIndent and SelectionHangingIndent properties produce all possible paragraph styles.

SelectionBullet, BulletIndent

You use these properties to create a list of bulleted items. If you set the SelectionBullet property to True, the selected paragraphs are formatted with a bullet style, similar to the <ul> tag in HTML. To create a list of bulleted items, select them from within your code and assign the value True to the SelectionBullet property. To change a list of bulleted items back to normal text, make the same property False.

The paragraphs formatted as bullets are also indented from the left by a small amount. To set the amount of the indentation, use the BulletIndent property, which is also expressed in pixels.

SelectionTabs

Use this property to set the tab stops in the RichTextBox control. The Selection tab should be set to an array of integer values, which are the absolute tab positions in pixels. Use this property to set up a RichTextBox control for displaying tab-delimited data.

Methods

The first two methods of the RichTextBox control you need to know are SaveFile and LoadFile. The SaveFile method saves the contents of the control to a disk file, and the LoadFile method loads the control from a disk file.

SaveFile

The syntax of the SaveFile method is as follows, where path is the path of the file in which the current document will be saved:

RichTextBox1.SaveFile(path, filetype)

By default, the SaveFile method saves the document in RTF format and uses the .rtf extension. You can specify a different format by using the second optional argument, which can take on the value of one of the members of the RichTextBoxStreamType enumeration, described in Table 7.2.

LoadFile

Similarly, the LoadFile method loads a text or RTF file to the control. Its syntax is identical to the syntax of the SaveFile method:

RichTextBox1.LoadFile(path, filetype)

Table 7.2. The RichTextBoxStreamType enumeration

Format

Effect

PlainText

Stores the text on the control without any formatting

RichNoOLEObjs

Stores the text without any formatting and ignores any embedded OLE (Object Linking and Embedding) objects

RichText

Stores the text in RTF format (text with embedded RTF commands)

TextTextOLEObjs

Stores the text along with the embedded OLE objects

UnicodePlainText

Stores the text in Unicode format

The filetype argument is optional and can have one of the values of the RichTextBoxStreamType enumeration. Saving and loading files to and from disk files is as simple as presenting a Save or Open common dialog to the user and then calling one of the SaveFile or LoadFile methods with the filename returned by the common dialog box.

Select, SelectAll

The Select method selects a section of the text on the control, similar to setting the SelectionStart and SelectionLength properties. The Select method accepts two arguments, the location of the first character to be selected and the length of the selection:

RichTextBox1.Select(start, length)

The SelectAll method accepts no arguments and it selects all the text on the control.

Advanced Editing Features

The RichTextBox control provides all the text-editing features you'd expect to find in a text-editing application, similar to the TextBox control. Among its more-advanced features, the RichTextBox control provides the AutoWordSelection property, which controls how the control selects text. If it's True, the control selects a word at a time.

In addition to formatted text, the RichTextBox control can handle object linking and embedding (OLE) objects. You can insert images in the text by pasting them with the Paste method. The Paste method doesn't require any arguments; it simply inserts the contents of the Clipboard at the current location (the location of the cursor) in the document.

Unlike the plain TextBox control, the RichTextBox control encapsulates undo and redo operations at multiple levels. Each operation has a name (Typing, Deletion, and so on), and you can retrieve the name of the next operation to be undone or redone and display it on the menu. Instead of a simple Undo or Redo caption, you can change the captions of the Edit menu to something like Undo Delete or Redo Typing. To program undo and redo operations from within your code, you must use the properties and methods discussed in the following sections.

CanUndo, CanRedo

These two properties are Boolean values you can read to find out whether there's an operation that can be undone or redone. If they're False, you must disable the corresponding menu command from within your code. The following statements disable the Undo command if there's no action to be undone at the time (EditUndo is the name of the Undo command on the Edit menu):

If RichTextBox1.CanUndo Then
   EditUndo.Enabled = True
Else
   EditUndo.Enabled = False
End If

These statements should appear in the menu item's Select event handler (not in the Click event handler) because they must be executed before the menu is displayed. The Select event is triggered when a menu is opened. As a reminder, the Click event is fired when you click an item and not when you open a menu. For more information on programming the events of a menu, see Chapter 6, "Working with Forms."

UndoActionName, RedoActionName

These two properties return the name of the action that can be undone or redone. The most common value of both properties is Typing, which indicates that the Undo command will delete a number of characters. Another common value is Delete, and some operations are named Unknown. If you change the indentation of a paragraph on the control, this action's name is Unknown. Even when an action's name is Unknown the action can be undone with the Undo method.

The following statement sets the caption of the Undo command to a string that indicates the action to be undone (Editor is the name of a RichTextBox control):

If Editor.CanUndo Then
    EditUndo.Text = "Undo " & Editor.UndoActionName
End If

Undo, Redo

These two methods undo or redo an action. The Undo method cancels the effects of the last action of the user on the control. The Redo method redoes the most recent undo action. The Redo method does not repeat the last action; it applies to undo operations only.

Cutting, Copying, and Pasting

To cut, copy, and paste text in the RichTextBox control, you can use the same techniques you use with the regular TextBox control. For example, you can replace the current selection by assigning a string to the SelectedText property. The RichTextBox, however, provides a few useful methods for performing these operations. The Copy, Cut, and Paste methods perform the corresponding operations. The Cut and Copy methods are straightforward and require no arguments. The Paste method accepts a single argument, which is the format of the data to be pasted. Because the data will come from the Clipboard, you can extract the format of the data in the Clipboard at the time and then call the CanPaste method to find out whether the control can handle this type of data. If so, you can then paste them in the control by using the Paste method.

This technique requires a bit of code because the Clipboard class doesn't return the format of the data in the Clipboard. You must call the following method of the Clipboard class to find out whether the data is of a specific type and then paste it on the control:

If Clipboard.GetDataObject.GetDataPresent(DataFormats.Text) Then
   RichTextBox1.Paste(DataFormats.GetFormat("Text")
End If

This is a very simple case because we know that the RichTextBox control can accept text. For a robust application, you must call the GetDataPresent method for each type of data your application should be able to handle. (You may not want to allow users to paste all types of data that the control can handle.) By the way, you can simplify the code with the help of the ContainsText/ContainsImage and GetText/GetImage methods of the My.Application.Clipboard object.

In the RTFPad project in this chapter, we'll use a structured exception handler to allow users to paste anything in the control. If the control can't handle it, the data won't be pasted in the control.

VB 2010 at Work: The RTFPad Project

Creating a functional — even fancy — word processor based on the RichTextBox control is unexpectedly simple. The challenge is to provide a convenient interface that lets the user select text, apply attributes and styles to it, and then set the control's properties accordingly. The RTFPad sample application of this section does just that. You can download a copy from www.sybex.com/go/masteringvb2010.

The RTFPad application (refer to Figure 7.7) is based on the TextPad application developed in Chapter 5, "Basic Windows Controls." It contains the same text-editing commands and some additional text-formatting commands that can be implemented only with the RichTextBox control; for example, it allows you to apply multiple fonts and styles to the text and, of course, multiple Undo/Redo operations.

The two TrackBar controls above the RichTextBox control manipulate the indentation of the text. We already explored this arrangement in the discussion of the TrackBar control in Chapter 5, but let's review the operation of the two controls again. Each TrackBar control has a width of 816 pixels, which is equivalent to 8.5 inches on a monitor that has a resolution of 96 dots per inch (dpi). The height of the TrackBar controls is 42 pixels, but unfortunately they can't be made smaller. The Minimum property of both controls is 0, and the Maximum property is 16. The TickFrequency is 1. With these values, you can adjust the indentation in steps of ½ inch. Set the Maximum property to 32 and you'll be able to adjust the indentation in steps of ¼ inch. It's not the perfect interface, as it's built for A4 pages in portrait orientation only. You can experiment with this interface to build an even more functional word processor.

Each time the user slides the top TrackBar control, the code sets the SelectionIndent property to the proper percentage of the control's width. Because the SelectionHangingIndent includes the value of the SelectionIndent property, it also adjusts the setting of the SelectionHangingIndent property. Listing 7.4 is the code that's executed when the upper TrackBar control is scrolled.

Example 7.4. Setting the SelectionIndent property

Private Sub TrackBar1_Scroll(...) Handles TrackBar1.Scroll
    Editor.SelectionIndent = Convert.ToInt32(Editor.Width *
                             (TrackBar1.Value / TrackBar1.Maximum))
    Editor.SelectionHangingIndent =
                             Convert.ToInt32(Editor.Width *
                             (TrackBar2.Value / TrackBar2.Maximum) –
                             Editor.SelectionIndent)
End Sub

Editor is the name of the RichTextBox control on the form. The code sets the control's indentation to the same percentage of the control's width, as indicated by the value of the top TrackBar control. It also does the same for the SelectionHangingIndent property, which is controlled by the lower TrackBar control. If the user has scrolled the lower TrackBar control, the code sets the RichTextBox control's SelectionHangingIndent property in the event handler, as presented in Listing 7.5.

Example 7.5. Setting the SelectionHangingIndent property

Private Sub TrackBar2_Scroll(...) Handles TrackBar2.Scroll
      Editor.SelectionHangingIndent =
               Convert.ToInt32(Editor.Width *
               (TrackBar2.Value / TrackBar2.Maximum) -
               Editor.SelectionIndent)
End Sub

Enter a few lines of text in the control, select one or more paragraphs, and check out the operation of the two sliders.

The Scroll events of the two TrackBar controls adjust the text's indentation. The opposite action must take place when the user rests the pointer on another paragraph: The sliders' positions must be adjusted to reflect the indentation of the selected paragraph. The selection of a new paragraph is signaled to the application by the SelectionChanged event. The statements of Listing 7.6, which are executed from within the SelectionChanged event, adjust the two slider controls to reflect the indentation of the text.

Example 7.6. Setting the slider controls

Private Sub Editor_SelectionChanged(...)
                  Handles Editor.SelectionChanged
    If Editor.SelectionIndent = Nothing Then
        TrackBar1.Value = TrackBar1.Minimum
        TrackBar2.Value = TrackBar2.Minimum
    Else
TrackBar1.Value = Convert.ToInt32(
                  Editor.SelectionIndent *
                  TrackBar1.Maximum / Editor.Width)
        TrackBar2.Value = Convert.ToInt32( _
                  (Editor.SelectionHangingIndent /
                   Editor.Width) *
                   TrackBar2.Maximum + TrackBar1.Value)
    End If
End Sub

If the user selects multiple paragraphs with different indentations, the SelectionIndent property returns Nothing. The code examines the value of this property and, if it's Nothing, moves both controls to the left edge. This way, the user can slide the controls and set the indentations for multiple paragraphs. Some applications make the handles gray to indicate that the selected text doesn't have uniform indentation, but unfortunately you can't gray the sliders and keep them enabled. Of course, you can always design a custom control. This wouldn't be a bad idea, especially if you consider that the TrackBar controls are too tall for this type of interface and can't be made very narrow (as a result, the interface of the RTFPad application isn't very elegant).

The File Menu

The RTFPad application's File menu contains the usual Open, Save, and Save As commands, which are implemented with the control's LoadFile and SaveFile methods. Listing 7.7 shows the implementation of the Open command in the File menu.

Example 7.7. The Open command

Private Sub OpenToolStripMenuItem_Click(...) Handles
                     OpenToolStripMenuItem.Click
    If DiscardChanges() Then
        OpenFileDialog1.Filter =
               "RTF Files|*.RTF|DOC Files|*.DOC|" &
               "Text Files|*.TXT|All Files|*.*"
        If OpenFileDialog1.ShowDialog() =
                       DialogResult.OK Then
            fName = OpenFileDialog1.FileName
            Editor.LoadFile(fName)
            Editor.Modified = False
        End If
    End If
End Sub

The fName variable is declared on the form's level and holds the name of the currently open file. This variable is set every time a new file is successfully opened, and it's used by the Save command to automatically save the open file without prompting the user for a filename.

DiscardChanges() is a function that returns a Boolean value, depending on whether the control's contents can be discarded. The function examines the Editor control's Modified property. If True, it prompts users as to whether they want to discard the edits. Depending on the value of the Modified property and the user response, the function returns a Boolean value. If the DiscardChanges() function returns True, the program goes on and opens a new document. If the function returns False, the program aborts the operation to give the user a chance to save the document. Listing 7.8 shows the DiscardChanges() function.

Example 7.8. The DiscardChanges() function

Function DiscardChanges() As Boolean
   If Editor.Modified Then
      Dim reply As MsgBoxResult
      reply = MsgBox(
          "Text hasn't been saved. Discard changes?",
           MsgBoxStyle.YesNo)
      If reply = MsgBoxResult.No Then
         Return False
      Else
         Return True
      End If
   Else
      Return True
   End If
End Function

The Modified property becomes True after the first character is typed and isn't reset back to False. The RichTextBox control doesn't handle this property very intelligently and doesn't reset it to False even after saving the control's contents to a file. The application's code sets the Editor.Modified property to False after creating a new document as well as after saving the current document.

The Save As command (see Listing 7.9) prompts the user for a filename and then stores the Editor control's contents to the specified file. It also sets the fName variable to the file's path so that the Save command can use it.

Example 7.9. The Save As command

Private Sub SaveAsToolStripMenuItem_Click(...)
                  Handles SaveAsToolStripMenuItem.Click
    SaveFileDialog1.Filter =
                 "RTF Files|*.RTF|DOC Files" &
                 "|*.DOC|Text Files|*.TXT|All Files|*.*"
    SaveFileDialog1.DefaultExt = "RTF"
    If SaveFileDialog1.ShowDialog() = DialogResult.OK Then
        fName = SaveFileDialog1.FileName
Editor.SaveFile(fName)
        Editor.Modified = False
    End If
End Sub

The Save command's code is similar, only it doesn't prompt the user for a filename. It calls the SaveFile method, passing the fName variable as an argument. If the fName variable has no value (in other words, if a user attempts to save a new document by using the Save command), the code activates the event handler of the Save As command automatically and resets the control's Modified property to False. Listing 7.10 shows the code behind the Save command.

Example 7.10. The Save command

Private Sub SaveToolStripMenuItem_Click(...)
                 Handles SaveToolStripMenuItem.Click
    If fName <> "" Then
        Editor.SaveFile(fName)
        Editor.Modified = False
    Else
        SaveAsToolStripMenuItem_Click(sender, e)
    End If
End Sub

The Edit Menu

The Edit menu contains the usual commands for exchanging data through the Clipboard (Copy, Cut, Paste), Undo/Redo commands, and a Find command to invoke the Search & Replace dialog box. All the commands are almost trivial, thanks to the functionality built into the control. The basic Cut, Copy, and Paste commands call the RichTextBox control's Copy, Cut, and Paste methods to exchange data through the Clipboard. Listing 7.11 shows the implementation of the Paste command.

Example 7.11. The Paste command

Private Sub PasteToolStripMenuItem_Click(...)
            Handles PasteToolStripMenuItem.Click
    Try
        Editor.Paste()
    Catch exc As Exception
        MsgBox(
              "Can't paste current clipboard's contents. " &
              "Try pasting the data in some other format.")
      End Try
  End Sub

As you may recall from the discussion of the Paste command, using the CanPaste method isn't trivial, you have to handle each data type differently. By using an exception handler, you allow the user to paste all types of data that the RichTextBox control can accept and display a message when an error occurs. Using exceptions for programming application logic can be quite costly, but in this case it's acceptable because the RTFPad editor is a desktop application serving a single user. A delay of a few milliseconds in this case should not make a huge difference.

For a more robust solution though, you might wish to handle each data type separately using the CanPaste method. That way, you can provide the user with much more precise feedback over the problem that caused the error; that is, the exact format of the data in the Clipboard they are trying to paste but the RichTextBox is not able to handle. That way you can save the user from having to guess the format your application can handle.

The Undo and Redo commands of the Edit menu are coded as follows. First, the name of the action to be undone or redone is displayed in the Edit menu. When the Edit menu is selected, the DropDownOpened event is fired. This event takes place before the Click event, so I inserted a few lines of code that read the name of the most recent action that can be undone or redone and print it next to the Undo or Redo command's caption. If there's no such action, the program will disable the corresponding command. Listing 7.12 is the code that's executed when the Edit menu is dropped.

Example 7.12. Setting the captions of the Undo and Redo commands

Private Sub EditToolStripMenuItem_DropDownOpened(...) Handles
                 EditToolStripMenuItem.DropDownOpened
    If Editor.UndoActionName <> "" Then
        UndoToolStripMenuItem.Text =
                     "Undo " & Editor.UndoActionName
        UndoToolStripMenuItem.Enabled = True
    Else
        UndoToolStripMenuItem.Text = "Undo"
        UndoToolStripMenuItem.Enabled = False
    End If
    If Editor.RedoActionName <> "" Then
        RedoToolStripMenuItem.Text =
                     "Redo" & Editor.RedoActionName
        RedoToolStripMenuItem.Enabled = True
    Else
        RedoToolStripMenuItem.Text = "Redo"
        RedoToolStripMenuItem.Enabled = False
    End If
End Sub

When the user selects one of the Undo or Redo commands, the code simply calls the appropriate method from within the menu item's Click event handler, as shown in Listing 7.13.

Example 7.13. Undoing and redoing actions

Private Sub RedoToolStripMenuItem_Click(...) Handles
  RedoToolStripMenuItem.Click
      If Editor.CanRedo Then Editor().Redo()
  End Sub

  Private Sub UndoToolStripMenuItem_Click(...) Handles
  UndoToolStripMenuItem.Click
      If Editor.CanUndo Then Editor.Undo()
  End Sub

Calling the CanUndo and CanRedo method is unnecessary; if the corresponding action can't be performed, the two menu items will be disabled, but an additional check does no harm.

The Format Menu

The commands of the Format menu control the alignment and the font attributes of the current selection. The Font command displays the Font dialog box and then assigns the font selected by the user to the current selection. Listing 7.14 shows the code behind the Font command.

Example 7.14. The Font command

Private Sub FontToolStripMenuItem_Click(...) Handles
                      FontToolStripMenuItem.Click
    If Not Editor.SelectionFont Is Nothing Then
         FontDialog1.Font = Editor.SelectionFont
    Else
         FontDialog1.Font = Nothing
    End If
    FontDialog1.ShowApply = True
    If FontDialog1.ShowDialog() = DialogResult.OK Then
         Editor.SelectionFont = FontDialog1.Font
    End If
End Sub

Notice that the code preselects a font in the dialog box, the font of the current selection. If the current selection isn't formatted with a single font, no font is preselected.

To enable the Apply button of the Font dialog box, set the control's ShowApply property to True and insert the following statement in its Apply event handler:

Private Sub FontDialog1_Apply(...) Handles FontDialog1.Apply
    Editor.SelectionFont = FontDialog1.Font
    Editor.SelectionColor = FontDialog1.Color
End Sub

The options of the Align menu set the RichTextBox control's SelectionAlignment property to different members of the HorizontalAlignment enumeration. The Align

The Font command
Editor.SelectionAlignment = HorizontalAlignment.Left

The Search & Replace Dialog Box

The Find command in the Edit menu opens the dialog box shown in Figure 7.9, which performs search-and-replace operations (whole-word or case-sensitive match or both). The Search & Replace form (it's the frmFind form in the project) has its TopMost property set to True so that it remains visible while it's open, even if it doesn't have the focus. The code behind the buttons on this form is quite similar to the code for the Find & Replace dialog box of the TextPad application, with one basic difference: the RTFPad project's code uses the RichTextBox control's Find method; the simple TextBox control doesn't provide an equivalent method and we had to use the methods of the String class to perform the same operations. The Find method of the RichTextBox control performs all types of searches, and some of its options are not available with the IndexOf method of the String class.

The Search & Replace dialog box of the RTFPad application

Figure 7.9. The Search & Replace dialog box of the RTFPad application

To invoke the Search & Replace dialog box, the code calls the Show method of the frmFind form, as discussed in Chapter 5, via the following statement:

frmFind.Show()

The Find method of the RichTextBox control allows you to perform case-sensitive or -insensitive searches as well as search for whole words only. These options are specified through an argument of the RichTextBoxFinds type. The SetSearchMode() function (see Listing 7.15) examines the settings of the two check boxes at the bottom of the form and sets the mode variable, which represents the Find method's search mode.

Example 7.15. Setting the search options

Function SetSearchMode() As RichTextBoxFinds
    Dim mode As RichTextBoxFinds =
                 RichTextBoxFinds.None
    If chkCase.Checked = True Then
        mode = mode Or RichTextBoxFinds.MatchCase
    End If
    If chkWord.Checked = True Then
        mode = mode Or RichTextBoxFinds.WholeWord
    End If
    Return mode
End Function

The Click event handlers of the Find and Find Next buttons call this function to retrieve the constant that determines the type of search specified by the user on the form. This value is then passed to the Find method. Listing 7.16 shows the code behind the Find and Find Next buttons.

Example 7.16. The Find and Find Next commands

Private Sub bttnFind_Click(...) Handles bttnFind.Click
    Dim wordAt As Integer
    Dim srchMode As RichTextBoxFinds
    srchMode = SetSearchMode()
    wordAt = frmEditor.Editor.Find(
                    txtSearchWord.Text, 0, srchMode)
    If wordAt = −1 Then
        MsgBox("Can't find word")
        Exit Sub
    End If
    frmEditor.Editor.Select(wordAt, txtSearchWord.Text.Length)
    bttnFindNext.Enabled = True
    bttnReplace.Enabled = True
    bttnReplaceAll.Enabled = True
    frmEditor.Editor.ScrollToCaret()
End Sub

Private Sub bttnFindNext_Click(...) Handles bttnFindNext.Click
    Dim selStart As Integer
    Dim srchMode As RichTextBoxFinds
    srchMode = SetSearchMode()
     selStart = frmEditor.Editor.Find(
                  txtSearchWord.Text,
                  frmEditor.Editor.SelectionStart + 2,
                  srchMode)
If selStart = −1 Then
        MsgBox("No more matches")
        Exit Sub
     End If
     frmEditor.Editor.Select(
                  selStart, txtSearchWord.Text.Length)
     frmEditor.Editor.ScrollToCaret()
 End Sub

Notice that both event handlers call the ScrollToCaret method to force the selected text to become visible — should the Find method locate the desired string outside the visible segment of the text.

The TreeView and ListView Controls

The TreeView and ListView controls are among the more advanced Windows controls and they certainly are more difficult to program than the others discussed. However, these two controls are the basic makings of unique user interfaces, as you will see in the examples in the following sections. The ListView and TreeView controls are discussed in detail in the tutorial "The ListView and TreeView controls," which is available for download from www.sybex.com/go/masteringvb2010. In this chapter, you will find an introduction to these two controls and their basic properties and methods. For more information on using these controls in your interface and interesting examples, please read the tutorial.

Figure 7.10 shows the TreeView and ListView controls used in tandem. What you see in Figure 7.10 is Windows Explorer, a utility for examining and navigating your hard disk's structure. The left pane, where the folders are displayed, is a TreeView control. The folder names are displayed in a manner that reflects their structure on the hard disk. You can expand and contract certain branches and view only the segment(s) of the tree structure you're interested in.

Windows Explorer is made up of a TreeView (left pane) and a ListView (right pane) control.

Figure 7.10. Windows Explorer is made up of a TreeView (left pane) and a ListView (right pane) control.

The right pane is a ListView control. The items on the ListView control can be displayed in five ways (as large or small icons, as a list, on a grid, or tiled). They are the various views you can set through the View menu of Windows Explorer. Although most people prefer to look at the contents of the folders as icons, the most common view is the Details view, which displays not only filenames, but also their attributes. In the Details view, the list can be sorted according to any of its columns, making it easy for the user to locate any item based on various criteria (file type, size, creation date, and so on).

Tree and List Structures

The TreeView control implements a data structure known as a tree. A tree is the most appropriate structure for storing hierarchical information. The organizational chart of a company, for example, is a tree structure. Every person reports to another person above him or her, all the way to the president or CEO. Figure 7.11 depicts a possible organization of continents, countries, and cities as a tree. Every city belongs to a country, and every country to a continent. In the same way, every computer file belongs to a folder that may belong to an even bigger folder, and so on up to the drive level. You can't draw large tree structures on paper, but it's possible to create a similar structure in the computer's memory without size limitations.

The world viewed as a tree

Figure 7.11. The world viewed as a tree

Each item in the tree of Figure 7.11 is called a node, and nodes can be nested to any level. Oddly, the top node is the root of the tree, and the subordinate nodes are called child nodes. If you try to visualize this structure as a real tree, think of it as an upside-down tree with the branches emerging from the root. The end nodes, which don't lead to any other nodes, are called leaf nodes or end nodes.

To locate a city, you must start at the root node and select the continent to which the city belongs. Then you must find the country (in the selected continent) to which the city belongs. Finally, you can find the city you're looking for. If it's not under the appropriate country node, it doesn't exist.

You can also start with a city and find its country. The country node is the city node's parent node. Notice that there is only one route from child nodes to their parent nodes, which means that you can instantly locate the country or continent of a city. The data shown in Figure 7.11 is shown in Figure 7.12 in a TreeView control. Only the nodes we're interested in are expanded. The plus sign indicates that the corresponding node contains child nodes. To view them, end users click the button with the plus sign and expand the node.

The nodes shown in Figure 7.11 implemented with a TreeView control

Figure 7.12. The nodes shown in Figure 7.11 implemented with a TreeView control

The tree structure is ideal for data with parent-child relations (relations that can be described as belongs to or owns). The continents-countries-cities data is a typical example. The folder structure on a hard disk is another typical example. Any given folder is the child of another folder or the root folder.

Maintaining a tree structure is a fundamental operation in software design; computer science students spend a good deal of their time implementing tree structures. Fortunately, with Visual Basic you don't have to implement tree structures on your own. The TreeView control is a mechanism for storing hierarchically structured data in a control with a visible interface. The TreeView control hides (or encapsulates, in object-oriented terminology) the details of the implementation and allows you to set up tree structures with a few lines of code — in short, all the gain without the pain (almost).

The ListView control implements a simpler structure, known as a list. A list's items aren't structured in a hierarchy; they are all on the same level and can be traversed serially, one after the other. You can also think of the list as a multidimensional array, but the list offers more features. A list item can have subitems and can be sorted according to any column. For example, you can set up a list of customer names (the list's items) and assign a number of subitems to each customer: a contact, an address, a phone number, and so on. Or you can set up a list of files with their attributes as subitems. Figure 7.13 shows a Windows folder mapped on a ListView control. Each file is an item, and its attributes are the subitems. As you already know, you can sort this list by filename, size, file type, and so on. All you have to do is click the header of the corresponding column.

The ListView control is a glorified ListBox control. If all you need is a control to store sorted objects, use a ListBox control. If you want more features, such as storing multiple items per row, sorting them in different ways, or locating them based on any subitem's value, you must consider the ListView control. You can also look at the ListView control as a view-only grid.

A folder's files displayed in a ListView control (Details view)

Figure 7.13. A folder's files displayed in a ListView control (Details view)

The TreeView and ListView controls are commonly used along with the ImageList control. The ImageList control is a simple control for storing images so they can be retrieved quickly and used at runtime. You populate the ImageList control with the images you want to use on your interface, usually at design time, and then you recall them by an index value at runtime.

The TreeView Control

Let's start our discussion of the TreeView control with a few simple properties that you can set at design time. To experiment with the properties discussed in this section, open the TreeViewDemo project, available for download from www.sybex.com/go/masteringvb2010. The project's main form is shown in Figure 7.14. After setting some properties (they are discussed next), run the project and click the Populate button to populate the control. After that, you can click the other buttons to see the effect of the various property settings on the control.

The TreeViewDemo project demonstrates the basic properties and methods of the TreeView control.

Figure 7.14. The TreeViewDemo project demonstrates the basic properties and methods of the TreeView control.

Here are the basic properties that determine the appearance of the control:

CheckBoxes

If this property is True, a check box appears in front of each node. If the control displays check boxes, you can select multiple nodes; otherwise, you're limited to a single selection.

FullRowSelect

This True/False value determines whether a node will be selected even if the user clicks outside the node's caption.

HideSelection

This property determines whether the selected node will remain highlighted when the focus is moved to another control. By default, the selected node doesn't remain highlighted when the control loses the focus.

HotTracking

This property is another True/False value that determines whether nodes are highlighted as the pointer hovers over them. When it's True, the TreeView control behaves like a web document with the nodes acting as hyperlinks — they turn blue while the pointer hovers over them. Use the NodeMouseHover event to detect when the pointer hovers over a node.

Indent

This property specifies the indentation level in pixels. The same indentation applies to all levels of the tree — each level is indented by the same number of pixels with respect to its parent level.

PathSeparator

A node's full name is made up of the names of its parent nodes separated by a backslash. To use a different separator, set this property to the desired symbol.

ShowLines

The ShowLines property is a True/False value that determines whether the control's nodes will be connected to its parent items with lines. These lines help users visualize the hierarchy of nodes, and it's customary to display them.

ShowPlusMinus

The ShowPlusMinus property is a True/False value that determines whether the plus/minus button is shown next to the nodes that have children. The plus button is displayed when the node is collapsed, and it causes the node to expand when clicked. Likewise, the minus sign is displayed when the node is expanded, and it causes the node to collapse when clicked. Users can also expand the current node by pressing the left-arrow button and collapse it with the right-arrow button.

ShowRootLines

This is another True/False property that determines whether there will be lines between each node and root of the tree view. Experiment with the ShowLines and ShowRootLines properties to find out how they affect the appearance of the control.

Sorted

This property determines whether the items in the control will be automatically sorted. The control sorts each level of nodes separately. In our globe example, it will sort the continents, then the countries within each continent, and then the cities within each country.

Adding Nodes at Design Time

Let's look now at the process of populating the TreeView control. Adding an initial collection of nodes to a TreeView control at design time is trivial. Locate the Nodes property in the Properties window, and you'll see that its value is Collection. To add items, click the ellipsis button, and the TreeNode Editor dialog box will appear, as shown in Figure 7.15. To add a root item, just click the Add Root button. The new item will be named Node0 by default. You can change its caption by selecting the item in the list and setting its Text property accordingly. You can also change the node's Name property, and you can change the node's appearance by using the NodeFont, FontColor, and ForeColor properties.

The TreeNode Editor dialog box

Figure 7.15. The TreeNode Editor dialog box

Follow these steps to enter the root node with the string Globe, a child node for Europe, and two more nodes under Europe: Germany and Italy. I'm assuming that you're starting with a clean control. If your TreeView control contains any items, clear them all by selecting one item at a time in the list and pressing the Delete key, or click the delete button (the one with the X icon) on the dialog box.

Click the Add Root button first to add the node Node0. Select it with the mouse, and its properties appear in the right pane of the TreeNode Editor window. Here you can change the node's Text property to Globe. You can specify the appearance of each node by setting its font and fore/background colors.

Then click the Add Child button, which adds a new node under the Globe root node. Select it with the mouse as before, and change its Text property to Europe. Then select the newly added node in the list and click the Add Child button again. Name the new node Germany. You've successfully added a small hierarchy of nodes. To add another node under Europe, select the Europe node in the list and click the Add Child button again. Name the new item Italy. Continue adding a few cities under each country to complete the tree.

Click the OK button to close the TreeNode Editor's window and return to your form. The nodes you added to the TreeView control are there, but they're collapsed. Only the root nodes are displayed with the plus sign in front of their names. Click the plus sign to expand the tree and see its child nodes. The TreeView control behaves the same at design time as it does at runtime — as far as navigating the tree goes, at least.

Adding Nodes at Runtime

Adding items to the control at runtime is a bit more involved. All the nodes belong to the control's Nodes collection, which is made up of TreeNode objects. To access the Nodes collection, use the following expression, where TreeView1 is the control's name and Nodes is a collection of TreeNode objects:

TreeView1.Nodes

This expression returns a collection of TreeNode objects and exposes the proper members for accessing and manipulating the individual nodes. The control's Nodes property is the collection of all root nodes.

The following statements print the strings shown highlighted below them (these strings are not part of the statements; they're the output that the statements produce):

Debug.WriteLine(TreeView1.Nodes(0).Text)
Globe
Debug.WriteLine(TreeView1.Nodes(0).Nodes(0).Text)
Europe
Debug.WriteLine(TreeView1.Nodes(0).Nodes(0).Nodes(1).Text)
Italy

Adding New Nodes

To add a new node to the Nodes collection use the Add method, which accepts as an argument a string or a TreeNode object, and returns a TreeNode object that represents the newly added node. The simplest form of the Add method is

newNode = Nodes.Add(nodeCaption)

where nodeCaption is a string that will be displayed on the control. Another form of the Add method allows you to add a TreeNode object directly (nodeObj is a properly initialized TreeNode variable):

newNode = Nodes.Add(nodeObj)

To use this form of the method, you must first declare and initialize a TreeNode object:

Dim nodeObj As New TreeNode
nodeObj.Text = "Tree Node"
nodeObj.ForeColor = Color.BlueViolet
TreeView1.Nodes.Add(nodeObj)

The last overloaded form of the Add method allows you to specify the index in the current Nodes collection, where the node will be added:

newNode = Nodes.Add(index, nodeObj)

The nodeObj TreeNode object must be initialized as usual.

To add a child node to the root node, use a statement such as the following:

TreeView1.Nodes(0).Nodes.Add("Asia")

To add a country under Asia, use a statement such as the following:

TreeView1.Nodes(0).Nodes(1).Nodes.Add("Japan")

The expressions can get quite lengthy. The proper way to add child items to a node is to create a TreeNode variable that represents the parent node, under which the child nodes will be added. Let's say that the ContinentNode variable in the following example represents the node Europe:

Dim ContinentNode As TreeNode
ContinentNode = TreeView1.Nodes(0).Nodes(2)

Then you can add child nodes to the ContinentNode node:

ContinentNode.Nodes.Add("France")
ContinentNode.Nodes.Add("Germany")

To add yet another level of nodes, the city nodes, create a new variable that represents a specific country. The Add method actually returns a TreeNode object that represents the newly added node, so you can add a country and a few cities by using statements such as the following:

Dim CountryNode As TreeNode
CountryNode = ContinentNode.Nodes.Add("Germany")
CountryNode.Nodes.Add("Berlin")
CountryNode.Nodes.Add("Frankfurt")

The ListView Control

The ListView control is similar to the ListBox control except that it can display its items in many forms, along with any number of subitems for each item. To use the ListView control in your project, place an instance of the control on a form and then set its basic properties, which are described in the following list:

View and Alignment

Two properties determine how the various items will be displayed on the control: the View property, which determines the general appearance of the items, and the Alignment property, which determines the alignment of the items on the control's surface. The View property can have one of the values shown in Table 7.3.

Table 7.3. View property settings

Setting

Description

LargeIcon

(Default) Each item is represented by an icon and a caption below the icon.

SmallIcon

Each item is represented by a small icon and a caption that appears to the right of the icon.

List

Each item is represented by a caption.

Details

Each item is displayed in a column with its subitems in adjacent columns.

Tile

Each item is displayed with an icon and its subitems to the right of the icon. This view is available only on Windows XP and Windows Server 2003.

The Alignment property can have one of the settings shown in Table 7.4.

Table 7.4. Alignment property settings

Setting

Description

Default

When an item is moved on the control, the item remains where it is dropped.

Left

Items are aligned to the left side of the control.

SnapToGrid

Items are aligned to an invisible grid on the control. When the user moves an item, the item moves to the closest grid point on the control.

Top

Items are aligned to the top of the control.

HeaderStyle

This property determines the style of the headers in Details view. It has no meaning when the View property is set to anything else because only the Details view has columns. The possible settings of the HeaderStyle property are shown in Table 7.5.

Table 7.5. HeaderStyle property settings

Setting

Description

Clickable

Visible column header that responds to clicking

Nonclickable

(Default) Visible column header that does not respond to clicking

None

No visible column header

AllowColumnReorder

This property is a True/False value that determines whether the user can reorder the columns at runtime, and it's meaningful only in Details view. If this property is set to True, the user can move a column to a new location by dragging its header with the mouse and dropping it in the place of another column.

Activation

This property, which specifies how items are activated with the mouse, can have one of the values shown in Table 7.6.

Table 7.6. Activation property settings

Setting

Description

OneClick

Items are activated with a single click. When the cursor is over an item, it changes shape and the color of the item's text changes.

Standard

(Default) Items are activated with a double-click. No change in the selected item's text color takes place.

TwoClick

Items are activated with a double-click and their text changes color as well.

FullRowSelect

This property is a True/False value, indicating whether the user can select an entire row or just the item's text, and it's meaningful only in Details view. When this property is False, only the first item in the selected row is highlighted.

GridLines

Another True/False property. If it's True, grid lines between items and subitems are drawn. This property is meaningful only in Details view.

Groups

The items of the ListView control can be grouped into categories. To use this feature, you must first define the groups by using the control's Groups property, which is a collection of strings. You can add as many members to this collection as you want. After that, as you add items to the ListView control, you can specify the group to which they belong. The control will group the items of the same category together and display the group title above each group. You can easily move items between groups at runtime by setting the Groups property for the corresponding item to the name of the desired group.

LabelEdit

The LabelEdit property lets you specify whether the user will be allowed to edit the text of the items. The default value of this property is False. Notice that the LabelEdit property applies to the item's Text property only; you can't edit the subitems (unfortunately, you can't use the ListView control as an editable grid).

MultiSelect

A True/False value, indicating whether the user can select multiple items from the control. To select multiple items, click them with the mouse while holding down the Shift or Ctrl key. If the control's ShowCheckboxes property is set to True, users can select multiple items by marking the check box in front of the corresponding item(s).

Scrollable

A True/False value that determines whether the scroll bars are visible. Even if the scroll bars are invisible, users can still bring any item into view. All they have to do is select an item and then press the arrow keys as many times as needed to scroll the desired item into view.

Sorting

This property determines how the items will be sorted, and its setting can be None, Ascending, or Descending. To sort the items of the control, call the Sort method, which sorts the items according to their caption. It's also possible to sort the items according to any of their subitems, as explained later in this chapter.

The Columns Collection

To display items in Details view, you must first set up the appropriate columns. The first column corresponds to the item's caption, and the following columns correspond to its subitems. If you don't set up at least one column, no items will be displayed in Details view. Conversely, the Columns collection is meaningful only when the ListView control is used in Details view.

The items of the Columns collection are of the ColumnHeader type. The simplest way to set up the appropriate columns is to do so at design time by using a visual tool. Locate and select the Columns property in the Properties window, and click the ellipsis button next to the property. The ColumnHeader Collection Editor dialog box, shown in Figure 7.16, will appear, and you can use it to add and edit the appropriate columns.

Adding columns to a ListView control and setting their properties through the dialog box shown in Figure 7.16 is quite simple. Don't forget to size the columns according to the data you anticipate storing in them and to set their headers. You can also add columns from within your code at runtime, a topic that's discussed in the tutorial "The ListView and TreeView Controls," available for download from www.sybex.com/go/masteringvb2010.

The ColumnHeader Collection Editor dialog box

Figure 7.16. The ColumnHeader Collection Editor dialog box

ListView Items and Subitems

As with the TreeView control, the ListView control can be populated either at design time or at runtime. To add items at design time, click the ellipsis button next to the ListItems property in the Properties window. When the ListViewItem Collection Editor dialog box pops up, you can enter the items, including their subitems, as shown in Figure 7.17.

The ListViewItem Collection Editor dialog box

Figure 7.17. The ListViewItem Collection Editor dialog box

Click the Add button to add a new item. Each item has subitems, which you can specify as members of the SubItems collection. To add an item with three subitems, you must populate the item's SubItems collection with the appropriate elements. Click the ellipsis button next to the SubItems property in the ListViewItem Collection Editor; the ListViewSubItem Collection Editor will appear. This dialog box is similar to the ListViewItem Collection Editor dialog box, and you can add each item's subitems. Assuming that you have added the item called Item 1 in the ListViewItem Collection Editor, you can add these subitems: Item 1-a, Item 1-b, and Item 1-c. The first subitem (the one with zero index) is actually the main item of the control.

Notice that you can set other properties such as the color and font for each item, the check box in front of the item that indicates whether the item is selected, and the image of the item. Use this window to experiment with the appearance of the control and the placement of the items, especially in Details view because subitems are visible only in this view. Even then, you won't see anything unless you specify headers for the columns. Note that you can add more subitems than there are columns in the control. Some of the subitems will remain invisible.

Unlike the TreeView control, the ListView control allows you to specify a different appearance for each item and each subitem. To set the appearance of the items, use the Font, BackColor, and ForeColor properties of the ListViewItem object.

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 as follows:

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, by using the following form of the Add method, where imageIndex is the index of the desired image on the associated ImageList control:

ListView1.Items.Add(caption, imageIndex)

Finally, you can create a ListViewItem object in your code and then add it to the ListView control by using 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.SubItems.Add("sub item 1a")
LItem.SubItems.Add("sub item 1b")
LItem.SubItems.Add("sub item 1c")
ListView1.Items.Add(LItem)
Count property

Returns the number of items in the collection.

Item property

Retrieves an item specified by an index value.

Clear method

Removes all the items from the collection.

Remove method

Removes an item from the collection.

The SubItems Collection

Each item in the ListView control may have one or more 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 Details 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 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
LItem = ListView1.Items.Add("Alfred's Futterkiste")
LItem.SubItems.Add("Maria Anders")
LItem.SubItems.Add("030-0074321")
LItem.SubItems.Add("030-0076545")

To access the SubItems collection, you need 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 preceding code segment.

Displaying the subitems on the control requires some overhead. Subitems are displayed only in Details view mode. However, setting the View property to Details is not enough. You must first create the columns of the Details 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 preceding 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, they will all have the same width. The width of individual columns is specified in pixels, and you can set it to 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 = ListView1.Width - 5
Dim headers =
    {
        New ColumnHeader() With {.Text = "Company", .Width = LWidth / 4},
        New ColumnHeader() With {.Text = "Contact", .Width = LWidth / 4},
        New ColumnHeader() With {.Text = "Phone", .Width = LWidth / 4},
        New ColumnHeader() With {.Text = "Fax", .Width = LWidth / 4}
    }
ListView1.Columns.AddRange(headers)
ListView1.View = View.Details

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. If the control is anchored to the vertical edges of the form, you must execute these statements from within the form's Resize event handler so that the columns are resized automatically as the control is resized.

You can also sort a ListView control with the Sort method, which sorts the list's items, and the Sorting property, which determines how the items will be sorted. For more information on sorting the control's items, see the tutorial "The ListView and TreeView Controls," available for download from www.sybex.com/go/masteringvb2010.

Processing Selected Items

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

The selected items form the SelectedListItemCollection collection, which is a property of the control. You can iterate through this collection with a For ... Next loop or through the enumerator object exposed by the collection. Listing 7.17 is the code behind the Selected Items button of the ListViewDemo project. It goes through the selected items with a For Each ... Next loop and displays each one of them, 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.

Example 7.17. Iterating the selected items on a ListView control

Private Sub bttnIterate_Click(...) Handles bttnIterate.Click
    Dim LItem As ListViewItem
    Dim LItems As ListView.SelectedListViewItemCollection
    LItems = ListView1.SelectedItems
    For Each LItem In LItems
        Debug.Write(LItem.Text & vbTab)
        Debug.Write(LItem.SubItems(0).ToString & vbTab)
        Debug.Write(LItem.SubItems(1).ToString & vbTab)
        Debug.WriteLine(LItem.SubItems(2).ToString & vbTab)
    Next
End Sub

VB 2010 at Work: The CustomExplorer Project

To demonstrate how to use the ListView and TreeView controls in tandem, which is how they commonly used, see the discussion of the CustomExplorer sample application, which is discussed in the tutorial "The ListView and TreeView controls." It's a fairly advanced example, but I included it for the most ambitious readers. It can also be used as the starting point for many custom applications, so give it a try.

The CustomExplorer project, shown in Figure 7.18, displays a structured list of folders in the left pane and the files in the selected folder in the right pane. The left pane is populated when the application starts. You can expand any folder in this pane and view its subfolders. To view the files in a folder, click the folder name and the right pane will be populated with the names of the selected folder's files along with other data, such as the file size, date of creation, and date of last modification.

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

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

The CustomExplorer 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 display their related data, such as invoices and payments, in the right pane. Or you can populate the left pane with product names and the right pane with the respective sales. In general, you can use the project 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.

The Bottom Line

Use the OpenFileDialog and SaveFileDialog controls to prompt users for filenames.

Windows applications use certain controls to prompt users for common information, such as filenames, colors, and fonts. Visual Studio provides a set of controls that are grouped in the Dialogs section of the Toolbox. All common dialog controls provide a ShowDialog method, which displays the corresponding dialog box in a modal way. The ShowDialog method returns a value of the DialogResult type, which indicates how the dialog box was closed, and you should examine this value before processing the data.

Master It

Your application needs to open an existing file. How will you prompt users for the file's name?

Master It

You're developing an application that encrypts multiple files (or resizes many images) in batch mode. How will you prompt the user for the files to be processed?

Use the ColorDialog and FontDialog controls to prompt users for colors and typefaces.

The Color and Font dialog boxes allow you to prompt users for a color value and a font, respectively. Before showing the corresponding dialog box, set its Color or Font property according to the current selection, and then call the control's ShowDialog method.

Master It

How will you display color attributes in the Color dialog box when you open it? How will you display the attributes of the selected text's font in the Font dialog box when you open it?

Use the RichTextBox control as an advanced text editor to present richly formatted text.

The RichTextBox control is an enhanced TextBox control that can display multiple fonts and styles, format paragraphs with different styles, and provide a few more-advanced text-editing features. Even if you don't need the formatting features of this control, you can use it as an alternative to the TextBox control. At the very least, the RichTextBox control provides more editing features, a more-useful undo function, and more-flexible search features.

Master It

You want to display a document with a title in large, bold type, followed by a couple of items in regular style. Which statements will you use to create a document like this on a RichTextBox control?

Document's Title

Item 1

Description for item 1

Item 2

Description for item 2

Create and present hierarchical lists by using the TreeView control.

The TreeView control is used to display a list of hierarchically structured items. Each item in the TreeView control is represented by a TreeNode object. To access the nodes of the TreeView control, use the TreeView.Nodes collection. The nodes under a specific node (in other words, the child nodes) form another collection of Node objects, which you can access by using the expression TreeView.Nodes(i).Nodes. The basic property of the Node object is the Text property, which stores the node's caption. The Node object exposes properties for manipulating its appearance (its foreground/background color, its font, and so on).

Master It

How will you set up a TreeView control with a book's contents at design time?

Create and present lists of structured items by using the ListView control.

The ListView control stores a collection of ListViewItem objects, which form the Items collection, and can display them in several modes, as specified by the View property. Each ListViewItem object has a Text property and the SubItems collection. The subitems are not visible at runtime unless you set the control's View property to Details and set up the control's Columns collection. There must be a column for each subitem you want to display on the control.

Master It

How will you set up a ListView control with three columns to display names, email addresses, and phone numbers at design time?

Master It

How would you populate the same control with the same data at runtime?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.189.67