Chapter 6. Working with Forms

In Visual Basic, the form is the container for all the controls that make up the user interface. When a Visual Basic application is executing, each window it displays on the Desktop is a form. The terms form and window describe the same entity. A window is what the user sees on the Desktop when the application is running. A form is the same entity at design time. The proper term is Windows form, as opposed to web form, but I will refer to them as forms. This term includes both typical Windows forms and dialog boxes, which are simple forms you use for very specific actions, such as to prompt the user for a particular piece of data or to display critical information. A dialog box is a form with a small number of controls, no menus, and usually an OK and a Cancel button to close it.

Forms have a built-in functionality that is always available without any programming effort on your part. You can move a form around, resize it, and even cover it with other forms. You do so with the mouse or with the keyboard through the Control menu.

In previous chapters, you concentrated on placing the elements of the user interface on forms, setting their properties, and adding code behind selected events. Now you'll look at forms themselves and at a few related topics. In this chapter, you'll learn how to do the following:

  • Use form properties

  • Design applications with multiple forms

  • Design dynamic forms

  • Design menus

Forms have many trivial properties that won't be discussed here. Instead, let's jump directly to the properties that are unique to forms and then look at how to manipulate forms from within an application's code.

The Appearance of Forms

Applications are made up of one or more forms—usually more than one. You should craft your forms carefully, make them functional, and keep them simple and intuitive. You already know how to place controls on the form, but there's more to designing forms than populating them with controls. The main characteristic of a form is the title bar on which the form's caption is displayed (see Figure 6.1).

The elements of the form

Figure 6.1. The elements of the form

Clicking the Control menu icon opens the Control menu, which contains the commands listed in Table 6.1. On the right end of the title bar are three buttons: Minimize, Maximize, and Close. Clicking these buttons performs the associated function. When a form is maximized, the Maximize button is replaced by the Restore button. When clicked, the Restore button resets the form to its size and position before it was maximized, and it's replaced by the Maximize button. To access the Control menu from the keyboard, press Alt and then the down arrow key.

Table 6.1. Commands of the Control menu

Command

Effect

Restore

Restores a maximized form to the size it was before it was maximized; available only if the form has been maximized.

Move

Lets the user move the form around with the arrow keys.

Size

Lets the user resize the form with the arrow keys.

Minimize

Minimizes the form.

Maximize

Maximizes the form.

Close

Closes the current form. (Closing the application's main form terminates the application.)

Properties of the Form Object

You're familiar with the appearance of forms, even if you haven't programmed in the Windows environment in the past; you have seen nearly all types of windows in the applications you're using every day. The floating toolbars used by many graphics applications, for example, are actually forms with a narrow title bar. The dialog boxes that prompt for critical information or prompt you to select the file to be opened are also forms. You can duplicate the look of any window or dialog box through the following properties of the Form object.

AcceptButton, CancelButton

These two properties let you specify the default Accept and Cancel buttons. The Accept button is the one that's automatically activated when you press Enter, no matter which control has the focus at the time; it is usually the button with the OK caption. Likewise, the Cancel button is the one that's automatically activated when you hit the Esc key; it is usually the button with the Cancel caption. To specify the Accept and Cancel buttons on a form, locate the AcceptButton and CancelButton properties of the form and select the corresponding controls from a drop-down list, which contains the names of all the buttons on the form. For more information on these two properties, see the section "Forms versus Dialog Boxes," later in this chapter.

AutoScaleMode

This property determines how the control is scaled, and its value is a member of the AutoScaleMode enumeration: None (automatic scaling is disabled); Font (the controls on the form are scaled relative to the size of the font); Dpi, which stands for dots per inch (the controls on the form are scaled relative to the display resolution); and Inherit (the controls are scaled according to the AutoScaleMode property of their parent class). The default value is Font; if you change the form's font size, the controls on it are scaled to the new font size. As a result, the entire form is resized.

AutoScroll

The AutoScroll property is a True/False value that indicates whether scroll bars (as shown in Figure 6.2) will be automatically attached to the form if the form is resized to a point that not all its controls are visible. Use this property to design large forms without having to worry about the resolution of the monitor on which they'll be displayed. Scrolling forms are not very common, but they're easy to implement. The AutoScroll property is used in conjunction with two other properties (described a little later in this section): AutoScrollMargin and AutoScrollMinSize. Note that the AutoScroll property applies to a few controls as well, including the Panel and SplitContainer controls. For example, you can create a form with a fixed and a scrolling pane by placing two Panel controls on it and setting the AutoScroll property of one of them (the Panel control you want to scroll) to True.

If the controls don't fit in a form's visible area, scroll bars can be attached automatically.

Figure 6.2. If the controls don't fit in a form's visible area, scroll bars can be attached automatically.

The AutoScroll property is rarely used with data-entry forms, but it's used routinely to display large images. You'll see how to create a scrolling form for displaying large images later in this chapter in the section on anchoring and docking controls.

AutoScrollPosition

This property is available from within your code only (you can't set this property at design time but it can be set at runtime from within your code), and it indicates the number of pixels that the form was scrolled up or down. Its initial value is zero, and it takes on a value when the user scrolls the form (provided that the AutoScroll property is True). Use this property to find out the visible controls from within your code or to scroll the form from within your application's code to bring a specific control into view.

AutoScrollMargin

This is a margin, expressed in pixels, that's added around all the controls on the form. If the form is smaller than the rectangle that encloses all the controls adjusted by the margin, the appropriate scroll bar(s) will be displayed automatically.

AutoScrollMinSize

This property lets you specify the minimum size of the form before the scroll bars are attached. If your form contains graphics that you want to be visible at all times, set the Width and Height members of the AutoScrollMinSize property to the dimensions of the graphics. (Of course, the graphics won't be visible at all times, but the scroll bars indicate that there's more to the form than can fit in the current window.) Notice that this isn't the form's minimum size; users can make the form even smaller. To specify a minimum size for the form, use the MinimumSize property, described later in this section.

Let's say the AutoScrollMargin property of the form is 180×150. If the form is resized to fewer than 180 pixels horizontally or 150 pixels vertically, the appropriate scroll bars will appear automatically as long as the AutoScroll property is True. If you want to enable the AutoScroll feature when the form's width is reduced to anything fewer than 250 pixels, set the AutoScrollMinSize property to (250, 0). In this example, setting AutoScrollMinSize.Width to anything less than 180, or AutoScrollMinSize.Height to anything less than 150, will have no effect on the appearance of the form and its scroll bars.

FormBorderStyle

The FormBorderStyle property determines the style of the form's border; its value is one of the FormBorderStyle enumeration members, which are shown in Table 6.2. You can make the form's title bar disappear altogether by setting the form's FormBorderStyle property to FixedToolWindow, the ControlBox property to False, and the Text property (the form's caption) to an empty string. However, a form like this can't be moved around with the mouse and will probably frustrate users.

Table 6.2. The FormBorderStyle enumeration

Value

Effect

Fixed3D

A window with a fixed visible border "raised" relative to the main area. Unlike the None setting, this setting allows users to minimize and close the window.

FixedDialog

A fixed window used to implement dialog boxes.

FixedSingle

A fixed window with a single-line border.

FixedToolWindow

A fixed window with a Close button only. It looks like a toolbar displayed by drawing and imaging applications.

None

A borderless window that can't be resized. This setting is rarely used.

Sizable

(default) A resizable window that's used for displaying regular forms.

SizableToolWindow

Same as the FixedToolWindow, but it's resizable. In addition, its caption font is smaller than the usual.

Create a simple form and try out the various settings of the FormBorderStyle property to find out how this property affects the appearance of the form.

ControlBox

This property is also True by default. Set it to False to hide the control box icon and disable the Control menu. Although the Control menu is rarely used, Windows applications don't disable it. When the ControlBox property is False, the three buttons on the title bar are also disabled. If you set the Text property to an empty string, the title bar disappears altogether.

MinimizeBox, MaximizeBox

These two properties, which specify whether the Minimize and Maximize buttons will appear on the form's title bar, are True by default. Set them to False to hide the corresponding buttons on a form's title bar.

MinimumSize, MaximumSize

These two properties read or set the minimum and maximum size of a form. When users resize the form at runtime, the form won't become any smaller than the dimensions specified by the MinimumSize property or any larger than the dimensions specified by the MaximumSize property. The MinimumSize property is a Size object, and you can set it with a statement like the following:

Me.MinimumSize = New Size(400, 300)

Or you can set the width and height separately:

Me.MinimumSize.Width = 400
Me.MinimumSize.Height = 300

The MinimumSize.Height property includes the height of the form's title bar; you should take that into consideration. If the minimum usable size of the form is 400×300, use the following statement to set the MinimumSize property:

Me.MinimumSize = New Size(400, 300 + SystemInformation.CaptionHeight)

The default value of both properties is (0, 0), which means that no minimum or maximum size is imposed on the form and the user can resize it as desired.

KeyPreview

This property enables the form to capture all keystrokes before they're passed to the control that has the focus. Normally, when you press a key, the KeyPress event of the control with the focus is triggered (as well as the KeyUp and KeyDown events), and you can handle the keystroke from within the control's appropriate handler. In most cases, you let the control handle the keystroke and don't write any form code for that.

Some forms perform certain actions when you hit a specific key (the F5 key for refreshing the form being a very common example), no matter which control on the form has the focus. If you want to use these keystrokes in your application, you must set the KeyPreview property to True. Doing so enables the form to intercept all keystrokes, so you can process them from within the form's keystroke event handlers. To handle a specific keystroke at the form's level, set the form's KeyPreview property to True and insert the appropriate code in the form's KeyDown or KeyUp event handler (the KeyPress event isn't fired for the function and other non-character keys).

The same keystrokes are then passed to the control with the focus, unless you kill the keystroke by setting its SuppressKeystroke property to True when you process it on the form's level. For more information on processing keystrokes at the form level and using special keystrokes throughout your application, see the Contacts project later in this chapter as well as the TextPad project discussed in Chapter 5, "The Basic Window Controls."

SizeGripStyle

This property gets or sets the style of the sizing handle to display in the lower-right corner of the form. You can set it to a member of the SizeGripStyle enumeration: Auto (the size grip is displayed as needed), Show (the size grip is displayed at all times), or Hide (the size grip is not displayed, but users can still resize the form with the mouse).

StartPosition, Location

The StartPosition property, which determines the initial position of the form when it's first displayed, can be set to one of the members of the FormStartPosition enumeration: CenterParent (the form is centered in the area of its parent form), CenterScreen (the form is centered on the monitor), Manual (the position of the form is determined by the Location property), WindowsDefaultLocation (the form is positioned at the Windows default location), and WindowsDefaultBounds (the form's location and bounds are determined by Windows defaults). The Location property allows you to set the form's initial position at design time or to change the form's location at runtime.

TopMost

This property is a True/False setting that lets you specify whether the form will remain on top of all other forms in your application. Its default value is False, and you should change it only on rare occasions. Some dialog boxes, such as the Find & Replace dialog box of any text-processing application, are always visible, even when they don't have the focus. For more information on using the TopMost property, see the discussion of the TextPad project in Chapter 5. You can also add a professional touch to your application by providing a CheckBox control that determines whether a form should remain on top of all other forms of the application.

Size

Use the Size property to set the form size at design time or at runtime. Normally, the form width and height are controlled by the user at runtime. This property is usually set from within the form Resize event handler to maintain a reasonable aspect ratio when the user resizes the form. The Form object also exposes the Width and Height properties for controlling its size.

Placing Controls on Forms

The first step in designing your application interface is, of course, the analysis and careful planning of the basic operations you want to provide through your interface. The second step is to design the forms. Designing a form means placing Windows controls on it and setting the control properties (and finally, of course, writing code to handle the events of interest). Visual Studio is a rapid application development (RAD) environment. This doesn't mean that you're expected to develop applications rapidly. It has come to mean that you can rapidly prototype an application and show something to the customer. And this is made possible through the visual tools that come with Visual Studio, especially the new Form Designer.

To place controls on your form, you select them in the Toolbox and then draw, on the form, the rectangle in which the control will be enclosed. Or you can double-click the control's icon to place an instance of the control on the form. Or you can just drag the desired control from the Toolbox and drop it on the form. All controls have a default size, and you can resize the control on the form by using the mouse.

Each control's dimensions can also be set in the Properties window through the Size property. The Size property is a composite property that exposes the Width and Height fields, which are expressed in pixels. Likewise, the Location property returns (or sets) the coordinates of the top-left corner of the control. In "Building Dynamic Forms at Runtime" later in this chapter, you'll see how to create new controls at runtime and place them in a specific location on a form from within your code.

As you place controls on the form, you can align them in groups with the relevant commands from the Format menu. Select multiple controls on the form by using the mouse and the Shift (or Ctrl) key, and then align their edges or center them vertically and horizontally with the appropriate command from the Format menu. To align the left edges of a column of TextBoxes, choose the Format

Placing Controls on Forms

As you move controls around with the mouse, a blue snap line appears when the controls become aligned with another control. Release the mouse while the snap line is visible to leave the control aligned with the one indicated by the snap lines. The blue snap lines indicate edge alignment. Most of the time, you need to align not the edges of two controls but their baselines (the baseline of the text on the control). The snap lines that indicate baseline alignment are red. Figure 6.3 shows both types of snap lines. When you're aligning a Label control with its matching TextBox control on a form, you want to align their baselines, not their frames (especially if you consider that the Label controls are always displayed without borders). If the control is aligned with other controls in both directions, two snap lines will appear—a horizontal one and a vertical one.

Edge alignment (vertical) and baseline alignment (horizontal)

Figure 6.3. Edge alignment (vertical) and baseline alignment (horizontal)

One of the most important (and most overlooked) aspects of designing forms is the alignment of the controls on the form. Whether the form contains a lot of controls or just a few, the application is more professional looking and easier for the end user to interact with when the programmer spends the time to align the controls one to another and group them functionally. Try to group controls together based on their functionality. Try to present an uncluttered interface to the end user. Once you have aligned the controls on the form as discussed in this section, you can select them all and lock them in place by setting their Locked property to True. When the Locked property is True, the designer won't allow you to move them around by mistake.

Setting the TabIndex Property

Another important issue in form design is the tab order of the controls on the form. As you know, by default, pressing the Tab key at runtime takes you to the next control on a form. The tab order of the controls is the order in which they were placed on the form, but this is hardly ever what we want. When you design the application, you can specify the order in which the controls receive the focus (the tab order, as it is known) with the help of the TabIndex property. Each control has its own TabIndex setting, which is an integer value. When the Tab key is pressed, the focus is moved to the control whose tab order immediately follows the tab order of the current control (the one with the next larger TabIndex property value).

To specify the tab order of the various controls, you can set their TabIndex property in the Properties window or you can choose the Tab Order command from the View menu. The tab order of each control will be displayed on the corresponding control, as shown in Figure 6.4. (The form shown in the figure is the Contacts application, which is discussed shortly.)

Setting the tab order of the controls on the main form of the Contacts project

Figure 6.4. Setting the tab order of the controls on the main form of the Contacts project

To set the tab order of the controls, click each control in the order in which you want them to receive the focus. You must click all of them in the desired order, starting with the first control in the tab order. Each control's index in the tab order appears in the upper-left corner of the control. When you're finished, choose the Tab Order command from the View menu again to hide the numbers. Note that Label controls never receive the focus, but they have their own TabIndex value. When the next control to receive the focus is a Label control, the focus is moved automatically to the next control in the tab order until a control that can actually receive the focus is reached.

VB 2010 at Work: The Contacts Project

I want to conclude with a simple data-entry application that demonstrates many of the topics discussed here as well as a few techniques for designing easy-to-use forms. Figure 6.5 shows a data-entry form for maintaining contact information, and I'm sure you will add your own fields to make this application more useful.

A simple data-entry screen

Figure 6.5. A simple data-entry screen

You can navigate through the contacts by clicking the arrow keys on the keyboard as well as add new contacts or delete existing ones by clicking the appropriate buttons. When you're entering a new contact, the buttons shown in Figure 6.5 are replaced by the usual OK and Cancel buttons. The action of adding a new contact, or editing an existing one, must end by clicking one of these two buttons. After a new contact is committed or the action is canceled, the usual navigation buttons appear again.

Now, it's your turn to design the Contacts project. Create a new VB project and place the controls you see in Figure 6.5 on the application's form, align them appropriately, and lock them in position. Or, if you prefer, open the Contacts sample project available for download from www.sybex.com/go/masteringvb2010. After the controls are on the form, the next step is to set their tab order. You must specify a value for the TabIndex property even for controls that never receive focus, such as the Label controls. In addition to setting the tab order of the controls, use shortcut keys to give the user quick access to the most common fields. The shortcut keys are displayed as underlined characters on the corresponding labels. Notice that the Label controls have shortcut keys, even though they don't receive the focus. When you press the shortcut key of a Label control, the focus is moved to the following control in the tab order, which (on this form) is the TextBox control next to it.

If you open the application and run it now, you'll see that the focus moves from one TextBox to the next with the Tab key and that the labels are skipped. After the last TextBox control, the focus is moved to the buttons and then back to the first TextBox control. To add a shortcut key for the most common fields, determine which fields will have shortcut keys and then which keys will be used for that purpose. Being the Internet buffs that we all are, let's assign shortcut keys to the Company, EMail, and URL fields. Locate each label's Text property in the Properties window and insert the & symbol in front of the character you want to act as a shortcut for each Label. The Text property of the three controls should be &Company, &EMail, and &URL.

Shortcut keys are activated at runtime by pressing the shortcut character while holding down the Alt key. The shortcut key will move the focus to the corresponding Label control, but because labels can't receive the focus, the focus is moved immediately to the next control in the tab order, which is the adjacent TextBox control.

The contacts are stored in an ArrayList object, which is similar to an array but a little more convenient. We'll discuss ArrayLists in Chapter 12, "Storing Data in Collections." For now, you can ignore the parts of the application that manipulate the contacts and focus on the design issues.

Start by loading the sample data included with the application that you downloaded from www.sybex.com/go/masteringvb2010. Open the File menu and choose Load. You won't be prompted for a filename; the application always opens the same file in its root folder (it's the CONTACTS.BIN file). After reading about the OpenFileDialog and SaveFileDialog controls, you can modify the code so that it prompts the user to choose the file to read from or write to. Then enter a new contact by clicking the Add button or edit an existing contact by clicking the Edit button. Both actions must end with the OK or Cancel button. In other words, users must explicitly end the operation and cannot switch to another contact while adding or editing a contact without committing or discarding the changes.

The code behind the various buttons is straightforward. The Add button hides all the navigational buttons at the bottom of the form and clears the TextBoxes in anticipation of a new contact record. The OK button saves the new contact to an ArrayList structure and redisplays the navigational buttons. The Cancel button ignores the data entered by the user and likewise displays the navigational buttons. In all cases, when the user switches back to the view mode, the code locks all the TextBoxes by setting their ReadOnly property to True.

Handling Keystrokes

Although the Tab key is the Windows method of moving to the next control on the form, most users will find it more convenient to use the Enter key for that purpose. The Enter key is the most important one on the keyboard, and applications should handle it intelligently. When the user presses Enter in a single-line TextBox, for example, the obvious action is to move the focus to the following control. I included a few statements in the KeyDown event handlers of the TextBox controls to move the focus to the following one:

Private Sub txtAddress1_KeyDown(...) Handles txtAddress1.KeyDown
    If e.KeyData = Keys.Enter Then
        e.SuppressKeyPress = True
        txtAddress2.Focus()
    End If
End Sub

If you use the KeyUp event handler instead, the result won't be any different, but an annoying beeping sound will be emitted with each keystroke. The beep occurs when the button is depressed, so you must intercept the Enter key as soon as it happens and not after the control receives the notification for the KeyDown event. The control will still catch the KeyUp event and it will beep because it's a single-line TextBox control (the beep is an audible warning that the specific key shouldn't be used in a single-line TextBox control). To avoid the beep sound, the code "kills" the keystroke by setting the SuppressKeystroke property to True.

The last topic demonstrated in this example is how to capture certain keystrokes regardless of the control that has the focus. We'll use the F10 keystroke to display the total number of contacts entered so far. Assuming that you have already set the form's KeyPreview property to True, enter the following code in the form's KeyDown event:

If e.Keycode = keys.F10 Then
   MsgBox("There are " & MyContacts.Count.ToString & " contacts in the database")
   e.Handled = True
End If

Listing 6.1 shows the complete handler for the form's KeyDown event, which also allows you to move to the next or previous contact by using the Alt+Plus or Alt+Minus keys, respectively.

Example 6.1. Handling keystrokes in the form's KeyDown event handler

Public Sub Form1_KeyDown(ByVal sender As Object,
            ByVal e As System.WinForms.KeyEventArgs)
                        Handles Form1.KeyUp
   If e.Keycode = Keys.F10 Then
      MsgBox("There are " & MyContacts.Count.ToString &
             " contacts in the database")
      e.Handled = True
   End If
   If e.KeyCode = Keys.Subtract And e.Modifiers = Keys.Alt Then
      bttnPrevious.PerformClick
   End If
   If e.KeyCode = Keys.Add And e.Modifiers = Keys.Alt Then
      bttnNext.PerformClick
   End If
   If e.KeyCode = Keys.Enter Then
       If Me.ActiveControl.GetType Is GetType(TextBox) Then
           e.SuppressKeyPress = True
           If e.Shift Then
               Me.ProcessTabKey(False)
           Else
               Me.ProcessTabKey(True)
End If
       End If
   End If
End Sub

Anchoring and Docking

A common issue in form design is the design of forms that can be properly resized. For instance, you might design a nice form for a given size, but when it's resized at runtime, the controls are all clustered in the upper-left corner. Or a TextBox control that covers the entire width of the form at design time suddenly "cringes" on the left when the user drags out the window. If the user makes the form smaller than the default size, part of the TextBox could be invisible because it's outside the form. You can attach scroll bars to the form, but that doesn't really help — who wants to type text and have to scroll the form horizontally? It makes sense to scroll vertically because you get to see many lines at once, but if the TextBox control is wider than the form, you can't read entire lines.

Visual Studio provides several techniques for designing forms that scale nicely. The two most important of them are the Anchor and Dock properties.

Anchoring Controls

The Anchor property lets you attach one or more edges of the control to corresponding edges of the form. The anchored edges of the control maintain the same distance from the corresponding edges of the form.

Place a TextBox control on a new form, set its MultiLine property to True, and then open the control's Anchor property in the Properties window. You will see a rectangle within a larger rectangle and four pegs that connect the small control to the sides of the larger box (see Figure 6.6). The large box is the form, and the small one is the control. The four pegs are the anchors, which can be either white or gray. The gray anchors denote a fixed distance between the control and the edge of the form. By default, the control is placed at a fixed distance from the upper-left corner of the form. When the form is resized, the control retains its size and its distance from the upper-left corner of the form.

The settings for the Anchor property

Figure 6.6. The settings for the Anchor property

Let's say you're designing a simple form with a TextBox control that must fill the width of the form, be anchored to the top of the form, and leave some space for a few buttons at the bottom. You also want your form to maintain this arrangement regardless of its size. Make the TextBox control as wide as the form (allowing, perhaps, a margin of a few pixels on either side). Then place a couple of buttons at the bottom of the form and make the TextBox control tall enough that it stops above the buttons. This is the form of the Anchor sample project.

Now open the TextBox control's Anchor property and make all four anchors gray by clicking them. This action tells the Form Designer to resize the control accordingly, so that the distances between the sides of the control and the corresponding sides of the form remain the same as those you set at design time. Select each button on the form and set their Anchor properties in the Properties window: Anchor the left button to the left and bottom of the form and the right button to the right and bottom of the form.

Resize the form at design time without running the project and you'll see that all the controls are resized and rearranged on the form at all times. Figure 6.7 shows the Anchor project's main form in two different sizes.

Use the Anchor property of the various controls to design forms that can be resized gracefully at runtime.

Figure 6.7. Use the Anchor property of the various controls to design forms that can be resized gracefully at runtime.

Yet, there's a small problem: If you make the form very narrow, there will be no room for both buttons across the form's width. The simplest way to fix this problem is to impose a minimum size for the form. To do so, you must first decide the form's minimum width and height and then set the MinimumSize property to these values. You can also use the AutoScroll properties, but it's not recommended that you add scroll bars to a small form like ours. Use the AutoScroll properties for large forms with many controls that can't be resized with the form.

Docking Controls

In addition to the Anchor property, most controls provide a Dock property, which determines how a control will dock on the form. The default value of this property is None.

Create a new form, place a multiline TextBox control on it, and then open the Dock property for the control. The various rectangular shapes are the settings of the property. If you click the middle rectangle, the control will be docked over the entire form: It will expand and shrink both horizontally and vertically to cover the entire form. This setting is appropriate for simple forms that contain a single control, usually a TextBox, and sometimes a menu. Try it out.

Let's create a more complicated form with two controls (see the Docking sample project at www.sybex.com/go/masteringvb2010). The form shown in Figure 6.8 contains a TreeView control on the left and a ListView control on the right. The two controls display folder and file data on an interface that's very similar to that of Windows Explorer. The TreeView control displays the directory structure, and the ListView control displays the selected folder's files.

Filling a form with two controls

Figure 6.8. Filling a form with two controls

Place a TreeView control on the left side of the form and a ListView control on the right side of the form. Then dock the TreeView to the left and the ListView to the right. If you run the application now, as you resize the form, the two controls remain docked to the two sides of the form—but their sizes don't change. If you make the form wider, there will be a gap between the two controls. If you make the form narrower, one of the controls will overlap the other.

End the application, return to the Form Designer, select the ListView control, and set its Dock property to Fill. This time, the ListView will change size to take up all the space to the right of the TreeView. The ListView control will attempt to fill the form, but it won't take up the space of another control that has been docked already. The TreeView and ListView controls are discussed in the tutorial "The TreeView and ListView Controls," which you can download from www.sybex.com/go/masteringvb2010. That's why I've populated them with some fake data at design time. In the tutorial, you'll learn how to populate these two controls at runtime with folder names and filenames, respectively, and build a custom Windows Explorer.

Splitting Forms into Multiple Panes

So far, the form for the Docking sample project you've designed behaves better than the initial design, but it's not what you really expect from a Windows application. The problem with the form in Figure 6.8 is that users can't change the relative widths of the controls. In other words, they can't make one of the controls narrower to make room for the other, which is a fairly common concept in the Windows interface.

The narrow bar that allows users to control the relative sizes of two controls is a splitter. When the cursor hovers over a splitter, it changes to a double arrow to indicate that the bar can be moved. By moving the splitter, you can enlarge one of the two controls while shrinking the other. The Form Designer provides a special control for creating resizable panes on a form: the SplitContainer control. We'll design a new form with two TextBoxes and a splitter between them so that users can change the relative size of the two controls.

Start by placing a SplitContainer control on the form. The SplitContainer consists of two Panel controls, the Panel1 and Panel2 controls, and a vertical splitter between them. This is the default configuration; you can change the orientation of the splitter by using the control's Orientation property. Also by default, the two panels of the Splitter control are resized proportionally as you resize the form. If you want to keep one of the panels fixed and have the other one take up the remaining space of the form, set the control's FixedPanel property to the name of the panel you want to retain its size.

Next, place a TextBox control in the left panel of the SplitContainer control and set its Multiline property to True. You don't need to do anything about its size because we'll dock it in the panel to which it belongs. With the TextBox control selected, locate its Dock property and set it to Fill. The TextBox control will fill the left panel of the SplitContainer control. Do the same with another TextBox control, which will fill the right panel of the SplitContainer control. Set this control's Multiline property to True and its Dock property to Fill.

Now run the project and check out the functionality of the SplitContainer. Paste some text on the two controls and then change their relative sizes by sliding the splitter between them, as shown in Figure 6.9. You will find this project, called Splitter1, at www.sybex.com/go/masteringvb2010 among the sample projects for this chapter.

The SplitContainer control lets you change the relative size of the controls on either side.

Figure 6.9. The SplitContainer control lets you change the relative size of the controls on either side.

Let's design a more elaborate form with two SplitContainer controls, such as the one shown in Figure 6.10. (It's the form in the Splitter2 sample project.) This form, which resembles the interface of Microsoft Office Outlook, consists of a TreeView control on the left (where the folders are displayed), a ListView control (where the selected folder's items are displayed), and a TextBox control (where the selected item's details are displayed). Because we haven't discussed the ListView and TreeView controls yet, I'm using three TextBox controls with different background colors; the process of designing the form is identical regardless of the controls you put on it.

Start by placing a SplitContainer control on the form. Then place a multiline TextBox control on the left panel of the SplitContainer control and set the TextBox control's Dock property to Fill. The TextBox control will fill the left panel of the SplitContainer control. Place another SplitContainer in the right panel of the first SplitContainer control. This control will be automatically docked in its panel and will fill it. Its orientation, however, is vertical, and the splitter will separate the panel into two smaller vertical panes. Select the second SplitContainer control, locate its Orientation property in the Properties window, and set it to Horizontal.

An elaborate form with two splitter controls

Figure 6.10. An elaborate form with two splitter controls

Now you can fill each of the panels with a TextBox control. Set each TextBox control's BackgroundColor to a different color, its MultiLine property to True, and its Dock property to Fill. The TextBox controls will fill their containers, which are the panels of the two SplitContainer controls, not the form. If you look up the properties of a SplitContainer control, you'll see that it's made up of two Panel controls, which are exposed as properties of the SplitContainer control, the Panel1 and Panel2 controls. You can set many of the properties of these two constituent controls, such as their font and color, their minimum size, and so on. They even expose an AutoScroll property so that users can scroll the contents of each one independently of the other. You can also set other properties of the SplitContainer control, such as the SplitterWidth property, which is the width of the splitter bar between the two panels in pixels, and the SplitterIncrement property, which is the smallest number of pixels that the splitter bar can be moved in either direction.

So far, you've seen what the Form Designer and the Form object can do for your application. Let's switch our focus to programming forms and explore the events triggered by the Form object.

Form Events

The Form object triggers several events. The most important are Activated, Deactivate, FormClosing, Resize, and Paint.

The Activated and Deactivate Events

When more than one form is displayed, the user can switch from one to the other by using the mouse or by pressing Alt+Tab. Each time a form is activated, the Activated event takes place. Likewise, when a form is activated, the previously active form receives the Deactivate event. Insert the code you want to execute when a form is activated (set certain control properties, for example) and when a form loses the focus or is deactivated in these two event handlers. These two events are the equivalents of the Enter and Leave events of the various controls. Notice that there's an inconsistency in the names of the two events: the Activated event takes place after the form has been activated, whereas the Deactivate event takes place right before the form is deactivated.

The FormClosing and FormClosed Events

The FormClosing event is fired when the user closes the form by clicking its Close button. If the application must terminate because Windows is shutting down, the same event will be fired. Users don't always quit applications in an orderly manner, and a professional application should behave gracefully under all circumstances. The same code you execute in the application Exit command must also be executed from within the FormClosing event. For example, you might display a warning if the user has unsaved data, you might have to update a database, and so on. Place the code that performs these tasks in a subroutine and call it from within your menu's Exit command as well as from within the FormClosing event's handler.

You can cancel the closing of a form by setting the e.Cancel property to True. The event handler in Listing 6.2 displays a message box informing the user that the data hasn't been saved and gives them a chance to cancel the action and return to the application.

Example 6.2. Canceling the closing of a form

Public Sub Form1_FormClosing (...) Handles Me.FormClosing
    Dim reply As MsgBoxResult
    reply = MsgBox("Document has been edited. " &
             "OK to terminate application, Cancel to " &
             "return to your document.", MsgBoxStyle.OKCancel)
    If reply = MsgBoxResult.Cancel Then
       e.Cancel = True
    End If
End Sub

The e argument of the FormClosing event provides the CloseReason property, which reports how the form is closing. Its value is one of the following members of the CloseReason enumeration: FormOwnerClosing, MdiFormClosing, None, TaskManagerClosing, WindowsShutDown, ApplicationExitCall, and UserClosing. The names of the members are self-descriptive, and you can query the CloseReason property to determine how the window is closing.

The Resize, ResizeBegin, and ResizeEnd Events

The Resize event is fired every time the user resizes the form by using the mouse. In the past, programmers had to insert quite a bit of code in the Resize event's handler to resize the controls and possibly rearrange them on the form. With the Anchor and Dock properties, much of this overhead can be passed to the form itself. If you want the two sides of the form to maintain a fixed ratio, however, you have to resize one of the dimensions from within the Resize event handler. Let's say the form's width-to-height ratio must be 3:4. Assuming that you're using the form's height as a guide, insert the following statement in the Resize event handler to make the width equal to three-fourths of the height:

Private Form1_Resize (...) Handles Me.Resize
   Me.Width = (0.75 * Me.Height)
End Sub

The Resize event is fired continuously while the form is being resized. If you want to keep track of the initial form's size and perform all the calculations after the user has finished resizing the form, you can use the ResizeBegin and ResizeEnd events, which are fired at the beginning and after the end of a resize operation, respectively. Store the form's width and height to two global variables in the ResizeBegin event and use these two variables in the ResizeEnd event handler to adjust the positions of the various controls on the form.

The Scroll Event

The Scroll event is fired by forms that have the AutoScroll property set to True when the user scrolls the form. The second argument of the Scroll event handler exposes the OldValue and NewValue properties, which are the displacements of the form before and after the scroll operation. This event can be used to keep a specific control in view when the form's contents are scrolled.

The AutoScroll property is handy for large forms, but it has a serious drawback: It scrolls the entire form. In most cases, you want to keep certain controls in view at all times. Instead of a scrollable form, you can create forms with scrollable sections by exploiting the AutoScroll properties of the Panel and/or the SplitContainer controls. You can also reposition certain controls from within the form's Scroll event handler. Let's say you have placed a few controls on a Panel container and you want to keep this panel at the top of a scrolling form. The following statements in the form's Scroll event handler reposition the panel at the top of the form every time the user scrolls the form:

Private Sub Form1_Scroll(...) Handles Me.Scroll
    Panel1.Top = Panel1.Top + (e.NewValue - e.OldValue)
End Sub

The Paint Event

This event takes place every time the form must be refreshed, and we use its handler to execute code for any custom drawing on the form. When you switch to another form that partially or totally overlaps the current one and then switch back to the first form, the Paint event will be fired to notify your application that it must redraw the form. The form will refresh its controls automatically, but any custom drawing on the form won't be refreshed automatically. This event is discussed in more detail in the tutorial "Drawing and Painting with Visual Basic 2008," where the Framework's drawing methods are presented. You can download the tutorial from www.sybex.com/go/masteringvb2010.

Loading and Showing Forms

Most practical applications are made up of multiple forms and dialog boxes. One of the operations you'll have to perform with multiform applications is to load and manipulate forms from within other forms' code. For example, you might want to display a second form to prompt the user for data specific to an application. You must explicitly load the second form and read the information entered by the user when the auxiliary form is closed. Or you might want to maintain two forms open at once and let the user switch between them. A text editor and its Find & Replace dialog box is a typical example.

You can access a form from within another form using its name. Let's say that your application has two forms, named Form1 and Form2, and that Form1 is the project's startup form. To show Form2 when an action takes place on Form1, call the Show method of the auxiliary form:

Form2.Show

This statement brings up Form2 and usually appears in a button's or menu item's Click event handler. To exchange information between two forms, use the techniques described in the section "Sharing Variables between Forms" later in this chapter.

The Show method opens a form in a modeless manner: The two forms are equal in stature on the desktop, and the user can switch between them. You can also display the second form in a modal manner, which means that users can't return to the form from which they invoked it without closing the second form. While a modal form is open, it remains on top of the desktop, and you can't move the focus to any other form of the same application (but you can switch to another application). To open a modal form, use the ShowDialog method:

Form2.ShowDialog

A dialog box is simply a modal form. When you display forms as dialog boxes, change the border of the forms to the setting FixedDialog and invoke them with the ShowDialog method. Modeless forms are more difficult to program because the user may switch among them at any time. Moreover, the two forms that are open at once must interact with one another. When the user acts on one of the forms, it might necessitate changes in the other, and you'll see shortly how this is done. If the two active forms don't need to interact, display one of them as a dialog box.

When you're finished with the second form, you can either close it by calling its Close method or hide it by calling its Hide method. The Close method closes the form, and its resources are returned to the system. The Hide method sets the form's Visible property to False; you can still access a hidden form's controls from within your code, but the user can't interact with it.

The Startup Form

A typical application has more than a single form. When an application starts, the main form is loaded. You can control which form is initially loaded by setting the startup object in the project Properties window. To open this dialog box, right-click the project's name in the Solution Explorer and select Properties. In the project's Properties pages, switch to the Application tab and select the appropriate item in the Startup Form combo box. By default, the IDE suggests the name of the first form it created, which is Form1. If you change the name of the form, Visual Basic will continue using the same form as the startup form with its new name.

You can also start an application by using a subroutine without loading a form. This subroutine is the MyApplication_Startup event handler, which is fired automatically when the application starts. To display the AuxiliaryForm object from within the Startup event handler, use the following statement:

Private Sub MyApplication_Startup (...) Handles Me.Startup
      System.Windows.Forms.Application.Run(New AuxiliaryForm())
End Sub

To view the MyApplication_Startup event handler, click the View Application Events button at the bottom of the Application pane in the project's Properties window. This action will take you to the MyApplication code window, where you can select the MyApplication Events item in the object list and the Startup item in the events list.

Controlling One Form from within Another

Loading and displaying a form from within another form's code is fairly trivial. In some situations, this is all the interaction you need between forms. Each form is designed to operate independently of the others, but they can communicate via public variables (see the following section). In most situations, however, you need to control one form from within another's code. Controlling the form means accessing its controls and setting or reading values from within another form's code.

Sharing Variables between Forms

The preferred method for two forms to communicate with each other is through public variables. These variables are declared in the form's declarations section, outside any procedure, with the keyword Public. If the following declarations appear in Form1, the variable NumPoints and the array DataValues can be accessed by any procedure in Form1 as well as from within the code of any form belonging to the same project:

Public NumPoints As Integer
Public DataValues(100) As Double

To access a public variable declared in Form1 from within another form's code, you must prefix the variable's name by the name of the form, as in the following:

Form1.NumPoints = 99
Form1.DataValues(0) = 0.3395022

In effect, the two public variables have become properties of the form in which they were declared. You can use the same notation to access the controls on another form. If Form1 contains the TextBox1 control, you can use the following statement to read its text:

Form1.TextBox1.Text

The controls on a form can be accessed by the code in another form because the default value of the Modifiers property of the controls on a form is Friend, which means that all components in a solution can access them. Other settings of the Modifiers property are Public (any application can access the control) and Private (the control is private to the form to which it belongs and cannot be accessed from code outside its own form). There are two more values, Protected and Protected Friend, which apply to inherited forms, a topic that's not covered in this book.

If a button on Form1 opens the auxiliary form Form2, you can set selected controls to specific values before showing the auxiliary form. The following statements should appear in a button's or menu item's Click event handler:

Form2.TextBox1.Text = "some text"
Form2.DateTimePicker1.Value = Today
Form2.Show()

You can also create a variable to represent another form and access the auxiliary form through this variable. Let's say you want to access the resources of Form2 from within the code of Form1. Declare the variable auxForm to represent Form2 and then access the resources of Form2 with the following statements:

Dim auxForm As Form2
auxForm.TextBox1.Text = "some text"
auxForm.DateTimePicker1.Value = Today
auxForm.Show

Forms versus Dialog Boxes

Dialog boxes are special types of forms with very specific functionality that prompt the user for data. The Open and Save dialog boxes are two of the most familiar dialog boxes in Windows. They're so common that they're actually known as common dialog boxes. Technically, a dialog box is a good old form with its FormBorderStyle property set to FixedDialog. Like forms, dialog boxes might contain a few simple controls, such as Labels, TextBoxes, and Buttons. Don't overload a dialog box with controls and functionality; you'll end up with a regular form. Dialog boxes are supposed to present a few options and perform very simple tasks. There are exceptions, of course, like the Printer Setup dialog box, but dialog boxes are usually simple forms with a pair of OK/Cancel buttons.

Figure 6.11 shows a couple of dialog boxes you have certainly seen while working with Windows applications. The Caption dialog box of Word is a modal dialog box: You must close it before switching to your document. The Find and Replace dialog box is modeless: It allows you to switch to your document, yet it remains visible while open even if it doesn't have the focus.

Typical dialog boxes used by Word

Figure 6.11. Typical dialog boxes used by Word

Notice that some dialog boxes, such as Open, Color, and even the humble MessageBox, come with the Framework, and you can incorporate them in your applications without having to design them.

A characteristic of dialog boxes is that they provide an OK and a Cancel button. The OK button tells the application that you're finished using the dialog box and the application can process the information in it. The Cancel button tells the application that it should ignore the information in the dialog box and cancel the current operation. As you will see, dialog boxes allow you to quickly find out which buttons were clicked to close them so that your application can take a different action in each case.

In short, the difference between forms and dialog boxes is artificial. If it were really important to distinguish between the two, they'd be implemented as two different objects—but they're the same object. So, without any further introduction, let's look at how to create and use dialog boxes.

To create a dialog box, start with a Windows form, set its FormBorderStyle property to FixedDialog, and set the ControlBox, MinimizeBox, and MaximizeBox properties to False. Then add the necessary controls on the form and code the appropriate events, as you would do with a regular Windows form.

Figure 6.12 shows a simple dialog box that prompts the user for an ID and a password (see the Password sample project available for download from www.sybex.com/go/masteringvb2010). The dialog box contains two TextBox controls (next to the appropriate labels) and the usual OK and Cancel buttons.

Now, to design your own Password main form, start a new project, rename the form MainForm, and place a button on it. This is the application main form, and we'll invoke the dialog box from within the button's Click event handler. Then add a new form to the project, name it PasswordForm, and place on it the controls shown in Figure 6.12.

A simple dialog box that prompts users for a username and password

Figure 6.12. A simple dialog box that prompts users for a username and password

To display a modal form, you call the ShowDialog method instead of the Show method. You already know how to read the values entered on the controls of the dialog box. You also need to know which button was clicked to close the dialog box. To convey this information from the dialog box back to the calling application, the Form object provides the DialogResult property. This property can be set to one of the values shown in Table 6.3 to indicate which button was clicked. The DialogResult.OK value indicates that the user has clicked the OK button on the form. There's no need to place an OK button on the form; just set the form's DialogResult property to DialogResult.OK.

Table 6.3. The DialogResult enumeration

Value

Description

Abort

The dialog box was closed with the Abort button.

Cancel

The dialog box was closed with the Cancel button.

Ignore

The dialog box was closed with the Ignore button.

No

The dialog box was closed with the No button.

None

The dialog box hasn't been closed yet. Use this option to find out whether a modeless dialog box is still open.

OK

The dialog box was closed with the OK button.

Retry

The dialog box was closed with the Retry button.

Yes

The dialog box was closed with the Yes button.

The dialog box need not contain any of the buttons mentioned here. It's your responsibility to set the value of the DialogResult property from within your code to one of the settings shown in the table. This value can be retrieved by the calling application. The code behind the two buttons in the dialog box is quite short:

Private Sub bttnOK_Click(...) Handles bttnOK.Click
    Me.DialogResult = DialogResult.OK
Me.Close
End Sub

Private Sub bttnCancel_Click(...) Handles bttnCancel.Click
    Me.DialogResult = DialogResult.Cancel
    Me.Close
End Sub

The event handler of the button that displays this dialog box should contain an If statement that examines the value returned by the ShowDialog method:

If PasswordForm.ShowDialog = DialogResult.OK Then
   ' { process the user selection }
End If

Depending on your application, you might allow the user to close the dialog box by clicking more than two buttons. Some of them must set the DialogResult property to DialogResult.OK, others to DialogResult.Cancel.

If the form contains an Accept and a Cancel button, you don't have to enter a single line of code in the modal form. The user can enter values on the various controls and then close the dialog box by pressing the Enter or Cancel key. The dialog box will close and will return the DialogResult.OK or DialogResult.Cancel value. The Accept button sets the form's DialogResult property to DialogResult.OK automatically, and the Cancel button sets the same property to DialogResult.Cancel. Any other button must set the DialogResult property explicitly. Listing 6.3 shows the code behind the Log In button on the sample project's main form.

Example 6.3. Prompting the user for an ID and a password

Private Sub Button1_Click(...) Handles Button1.Click
   If PasswordForm.ShowDialog() = DialogResult.OK Then
      If PasswordForm.txtUserID.Text = "" Or
              PasswordForm.txtPassword.Text = "" Then
         MsgBox("Please specify a user ID and a password to connect")
         Exit Sub
      End If
      MsgBox("You were connected as " & PasswordForm.txtUserID.Text)
   Else
      MsgBox("Connection failed for user " & PasswordForm.txtPassword.Text)
   End If
End Sub

VB 2010 at Work: The MultipleForms Project

It's time to write an application that puts together the topics discussed in this section. The MultipleForms project, available for download from www.sybex.com/go/masteringvb2010, consists of a main form, an auxiliary form, and a dialog box. All three components of the application interface are shown in Figure 6.13. The buttons on the main form display both the auxiliary form and the dialog box.

The MultipleForms project interface

Figure 6.13. The MultipleForms project interface

Let's review the various operations you want to perform — they're typical for many situations, not for only this application. At first, you must be able to invoke both the auxiliary form and the dialog box from within the main form; the Show Auxiliary Form and Show Dialog Box buttons do this. The main form contains a variable declaration: strProperty. This variable is, in effect, a property of the main form and is declared as public with the following statement:

Public strProperty As String = "Mastering VB 2010"

The main form calls the auxiliary form's Show method to display it in a modeless manner. The auxiliary form button with the caption Read Shared Variable In Main Form reads the strProperty variable of the main form with the following statement:

Private Sub bttnReadShared_Click(...) Handles bttnReadShared.Click
   MsgBox(MainForm.strProperty, MsgBoxStyle.OKOnly,
               "Public Variable Value")
End Sub

Using the same notation, you can set this variable from within the auxiliary form. The following event handler prompts the user for a new value and assigns it to the shared variable of the main form:

Private Sub bttnSetShared_Click(...) Handles bttnSetShared.Click
   Dim str As String
   str = InputBox("Enter a new value for strProperty")
   MainForm.strProperty = str
End Sub

The two forms communicate with each other through public properties. Let's make this communication a little more elaborate by adding an event. Every time the auxiliary form sets the value of the strProperty variable, it raises an event to notify the main form. The main form, in turn, uses this event to display the new value of the string on the TextBox control as soon as the code in the auxiliary form changes the value of the variable and before it's closed.

To raise an event, you must declare the event name in the form's declaration section. Insert the following statement in the auxiliary form's declarations section:

Event strPropertyChanged()

Now add a statement that fires the event. To raise an event, call the RaiseEvent statement and pass the name of the event as an argument. This statement must appear in the Click event handler of the Set Shared Variable In Main Form button, right after setting the value of the shared variable. As soon as the user clicks the button, the auxiliary form notifies the main form by raising the strPropertyChanged event. Listing 6.4 shows the revised event handler.

Example 6.4. Raising an event

Private Sub bttnSetShared_Click(...) Handles bttnSetShared.Click
   Dim str As String
   str = InputBox("Enter a new value for strProperty")
   MainForm.strProperty = str
   RaiseEvent strPropertyChanged
End Sub

The event will be raised, but it will go unnoticed if you don't handle it from within the main form's code. To handle the event, you must create a variable that represents the auxiliary form with the WithEvents keyword:

Dim WithEvents FRM As New AuxiliaryForm()

The WithEvents keyword tells VB that the variable is capable of raising events and that VB should listen for events from the specific form. If you expand the drop-down list with the objects in the code editor, you will see the name of the FRM variable, along with the other controls you can program. Select FRM in the list and then expand the list of events for the selected item. In this list, you will see the strPropertyChanged event. Select it and the definition of an event handler will appear. Enter these statements in this event's handler:

Private Sub FRM_strPropertyChanged() Handles FRM.strPropertyChanged
   TextBox1.Text = strProperty
   Beep()
End Sub

It's a simple handler, but it's adequate for demonstrating how to raise and handle custom events on the form level. If you want, you can pass arguments to the event handler by including them in the declaration of the event. To pass the original and the new value through the strPropertyChanged event, use the following declaration:

Event strPropertyChanged(ByVal oldValue As String,
                         ByVal newValue As String)

If you run the application now, you'll see that the value of the TextBox control in the main form changes as soon as you change the property's value in the auxiliary form. You can actually change the value of the variable several times before closing the auxiliary form, and each time the current value will be displayed on the main form.

Of course, you can update the TextBox control on the main form directly from within the auxiliary form's code. Use the expression MainForm.TextBox1 to access the control and then manipulate it as usual. Events are used to perform some actions on a form when an action takes place. The benefit of using events, as opposed to accessing members of another form from within your code, is that the auxiliary form need not know anything about the form that called it. The auxiliary form raises the event, and it's the calling form's responsibility to handle it. Moreover, the event handler in the main form may perform other actions in addition to setting a control's value; it may submit something to a database, log the action, and perform any other operation suited for the application at hand.

Let's see now how the main form interacts with the dialog box. What goes on between a form and a dialog box is not exactly interaction; it's a more timid type of behavior. The form displays the dialog box and waits until the user closes the dialog box. Then it looks at the value of the DialogResult property to find out whether it should even examine the values passed back by the dialog box. If the user has closed the dialog box with the Cancel (or an equivalent) button, the application ignores the dialog box settings. If the user closed the dialog box with the OK button, the application reads the values and proceeds accordingly.

Before showing the dialog box, the code of the Show Dialog Box button sets the values of certain controls on the dialog box. In the course of the application, it usually makes sense to suggest a few values in the dialog box so that the user can accept the default values by pressing the Enter key. The main form reads a date on the dialog box's controls and then displays the dialog box with the statements given in Listing 6.5.

Example 6.5. Displaying a dialog box and reading its values

Protected Sub Button3_Click(...) Handles Button3.Click
' Preselects the date 4/11/1980
   AgeDialog.cmbMonth.Text = "4"
   AgeDialog.cmbDay.Text = "11"
   AgeDialog.CmbYear.Text = "1980"
   AgeDialog.ShowDialog()
   If AgeDialog.DialogResult = DialogResult.OK Then
      MsgBox(AgeDialog.cmbMonth.Text & " " &
             AgeDialog.cmbDay.Text & "," &
             AgeDialog.cmbYear.Text)
   Else
      MsgBox("OK, we'll protect your vital personal data")
End If
End Sub

To close the dialog box, you can click the OK or Cancel button. Each button sets the DialogResult property to indicate the action that closed the dialog box. The code behind the two buttons is shown in Listing 6.6.

Example 6.6. Setting a dialog box DialogResult property

Protected Sub bttnOK_Click(...) Handles bttnOK.Click
   Me.DialogResult = DialogResult.OK
End Sub

Protected Sub bttnCancel_Click(...) Handles bttnCancel.Click
   Me.DialogResult = DialogResult.Cancel
End Sub

Because the dialog box is modal, the code in the Show Dialog Box button is suspended at the line that shows the dialog box. As soon as the dialog box is closed, the code in the main form resumes with the statement following the one that called the ShowDialog method of the dialog box. This is the If statement in Listing 6.5 that examines the value of the DialogResult property and acts accordingly.

Building Dynamic Forms at Runtime

Sometimes you won't know in advance how many instances of a given control might be required on a form. Let's say you're designing a form for displaying the names of all tables in a database. It's practically impossible to design a form that will accommodate every database users might throw at your application. Another typical example is a form for entering family-related data, which includes the number of children in the family and their ages. As soon as the user enters (or changes) the number of children, you should display as many TextBox controls as there are children to collect their ages.

For these situations, it is possible to design dynamic forms, which are populated at runtime. The simplest approach is to create more controls than you'll ever need and set their Visible properties to False at design time. At runtime, you can display the controls by switching their Visible properties to True. As you know already, quick-and-dirty methods are not the most efficient ones. You must still rearrange the controls on the form to make it look nice at all times. The proper method to create dynamic forms at runtime is to add controls to and remove them from your form as needed from within your code using the techniques discussed in the following sections.

Just as you can create new instances of forms, you can also create new instances of any control and place them on a form. The Form object exposes the Controls property, which is a collection that contains all the controls on the form. This collection is created automatically as you place controls on the form at design time, and you can access the members of this collection from within your code. It is also possible to add new members to the collection, or remove existing members, with the Add and Remove methods of the Form object accordingly.

The Form's Controls Collection

All the controls on a form are stored in the Controls collection, which is a property of the Form object. The Controls collection exposes members for accessing and manipulating the controls at runtime, and they're the usual members of a collection:

Add Method

The Add method adds a new element to the Controls collection. In effect, it adds a new control on the current form. The Add method accepts a reference to a control as an argument and adds it to the collection. Its syntax is the following, where controlObj is an instance of a control:

Controls.Add(controlObj)

To place a new Button control on the form, declare a variable of the Button type, set its properties, and then add it to the Controls collection:

Dim bttn As New  System.Windows.Forms.Button
bttn.Text = "New Button"
bttn.Left = 100
bttn.Top = 60
bttn.Width = 80
Me.Controls.Add(bttn)
Remove Method

The Remove method removes an element from the Controls collection. It accepts as an argument either the index of the control to be removed or a reference to the control to be removed (a variable of the Control type that represents one of the controls on the form). The syntax of these two forms of the Remove method is as follows:

Me.Controls.Remove(index)
Me.Controls.Remove(controlObj)
Count Property and All Method

The Count property returns the number of elements in the Controls collection. Notice that if there are container controls, such as a Panel control, the controls in the containers are not included in the count. The Panel control has its own Controls collection. The All method returns all controls on a form (or a container control) as an array of the System.WinForms.Control type.

Clear Method

The Clear method removes all the elements of the Controls array and effectively clears the form.

The Controls collection is also a property of any control that can host other controls. As you recall from our discussion of the Anchor and Dock properties, it's customary to place controls on a panel and handle them collectively as a section of the form. They are moved along with the panel at design time, and they're rearranged as a group at runtime. The Panel belongs to the form's Controls collection, and it provides its own Controls collection, which lets you access the controls on the panel.

VB 2010 at Work: The ShowControls Project

The ShowControls project (shown in Figure 6.14) demonstrates the basic methods of the Controls property. Download the ShowControls project from www.sybex.com/go/masteringvb2010, open it, and add any number of controls on the main form. You can place a panel to act as a container for other controls as well. Just don't remove the button at the top of the form (the Scan Controls On This Form button); it contains the code to list all the controls.

Accessing the controls on a form at runtime

Figure 6.14. Accessing the controls on a form at runtime

The code behind the Scan Controls On This Form button enumerates the elements of the form's Controls collection. The code doesn't take into consideration containers within containers. This would require a recursive routine, which would scan for controls at any depth. The code that iterates through the form's Controls collection and prints the names of the controls in the Output window is shown in Listing 6.7.

Example 6.7. Iterating the Controls collection

Private Sub Button1_Click(...) Handles Button1.Click
    Dim Control As Windows.Forms.Control
    For Each Control In Me.Controls
        Debug.WriteLine(Control.ToString)
        If Control.GetType Is GetType(System.Windows.Forms.Panel) Then
            Dim nestedControl As Windows.Forms.Control
            For Each nestedControl In Control.Controls
                Debug.WriteLine("   " & nestedControl.ToString)
            Next
        End If
    Next
End Sub

The form shown in Figure 6.15 produced the following (partial) output (the controls on the Panel are indented to stand out in the listing):

Panel1:          System.Windows.Forms.Panel,
BorderStyle:     System.Windows.Forms.BorderStyle.FixedSingle
    CheckBox4:   System.Windows.Forms.CheckBox, CheckState: 0
    CheckBox3:   System.Windows.Forms.CheckBox, CheckState: 0
HScrollBar1:     System.Windows.Forms.HScrollBar,
                 Minimum: 0, Maximum: 100, Value: 0
CheckedListBox1: System.Windows.Forms.CheckedListBox,
                 Items.Count: 3, Items[0]: Item 1
TextBox2:        System.Windows.Forms.TextBox,
                 Text: TextBox2

To find out the type of individual controls, call the GetType method. The following statement examines whether the control in the first element of the Controls collection is a TextBox:

If Me.Controls(0).GetType Is GetType(System.Windows.Forms.TextBox) Then
   MsgBox("It's a TextBox control")
End If

Notice the use of the Is operator in the preceding statement. The equals operator would cause an exception because objects can be compared only with the Is operator. (You're comparing instances, not values.)

To access other properties of the control represented by an element of the Controls collection, you must first cast it to the appropriate type. If the first control of the collection is a TextBox control, use the CType() function to cast it to a TextBox variable and then request its SelectedText property:

If Me.Controls(0).GetType Is GetType(System.Windows.Forms.TextBox) Then
   Debug.WriteLine(CType(Me.Controls(0), TextBox).SelectedText)
End If

The If statement is necessary, unless you can be sure that the first control is a TextBox control. If you omit the If statement and attempt to convert the control to a TextBox, a runtime exception will be thrown if the object Me.Controls(0) isn't a TextBox control.

VB 2010 at Work: The DynamicForm Project

To demonstrate how to handle controls at runtime from within your code, I included the DynamicForm project (Figure 6.15) on www.sybex.com/go/masteringvb2010. It's a simple data-entry window for a small number of data points. The user can specify at runtime the number of data points she wants to enter, and the number of TextBoxes on the form is adjusted automatically.

The control you see at the top of the form is the NumericUpDown control. All you really need to know about this control is that it displays an integer in the range specified by its Minimum and Maximum properties and allows users to select a value. It also fires the ValueChanged event every time the user clicks one of the two arrows or types another value in its edit area. This event handler's code adds or removes controls on the form so that the number of text boxes (as well as the number of corresponding labels) matches the value on the control. Listing 6.8 shows the handler for the ValueChanged event of the NumericUpDown1 control.

The DynamicForm project

Figure 6.15. The DynamicForm project

Example 6.8. Adding and removing controls at runtime

Private Sub NumericUpDown1_ValueChanged(...)  Handles NumericUpDown1.ValueChanged
    Dim TB As New TextBox()
    Dim LBL As New Label()
    Dim i, TBoxes As Integer
    '   Count all TextBox controls on the Form
    For i = 0 To Me.Controls.Count - 1
        If Me.Controls(i).GetType Is
                    GetType(System.Windows.Forms.TextBox) Then
            TBoxes = TBoxes + 1
        End If
    Next
    ' Add new controls if number of controls on the Form is less
    ' than the number specified with the NumericUpDown control
    If TBoxes < NumericUpDown1.Value Then
        TB.Left = 100: TB.Width = 120
        TB.Text = ""
        For i = TBoxes To CInt(NumericUpDown1.Value) - 1
            TB = New TextBox()
            LBL = New Label()
            If NumericUpDown1.Value = 1 Then
                TB.Top = 20: TB.TabIndex = 0
            Else
                TB.Top = Me.Controls(Me.Controls.Count - 2).Top + 25
            End If
           ' Set the trivial properties of the new controls
           LBL.Left = 20: LBL.Width = 80
           LBL.Text = "Data Point " & i
           LBL.Top = TB.Top + 3
           TB.Left = 100: TB.Width = 120
TB.Text = ""
           ' add controls to the form
           Me.Controls.Add(TB)
           Me.Controls.Add(LBL)
           TB.TabIndex = Convert.ToInt32(NumericUpDown1.Value)
           ' and finally connect their GotFocus/LostFocus events
           ' to the appropriate handler
           AddHandler TB.Enter,
                     New System.EventHandler(AddressOf TBox_Enter)
           AddHandler TB.Leave,
                     New System.EventHandler(AddressOf TBox_Leave)
        Next
    Else
        For i = Me.Controls.Count - 1 To Me.Controls.Count -
                    2 * (TBoxes - CInt(NumericUpDown1.Value)) Step −2
            Me.Controls.Remove(Controls(i))
            Me.Controls.Remove(Controls(i - 1))
        Next
    End If
End Sub

The code is lengthy but straightforward; most of the statements just set the basic properties of the Label and TextBox controls on the form. Ignore the AddHandler statements for now; they're discussed in the following section. First, the code counts the number of TextBoxes on the form; then it figures out whether it should add or remove elements from the Controls collection. To remove controls, the code iterates through the last n controls on the form and removes them. The number of controls to be removed is the following, where TBoxes is the total number of controls on the form minus the value specified in the NumericUpDown control:

2 * (TBoxes - NumericUpDown1.Value)

If the value entered in the NumericUpDown control is less than the number of TextBox controls on the form, the code removes the excess controls from within a loop. At each step, it removes two controls, one of them a TextBox and the other a Label control with the matching caption. (That's why the loop variable is decreased by two.) The code also assumes that the first two controls on the form are the Button and the NumericUpDown controls. If the value entered by the user exceeds the number of TextBox controls on the form, the code adds the necessary pairs of TextBox and Label controls to the form.

To add controls, the code initializes a TextBox (TB) and a Label (LBL) variable. Then, it sets their locations and the label's caption. The left coordinate of all labels is 20, their width is 80, and their Text property (the label's caption) is the order of the data item. The vertical coordinate is 20 pixels for the first control, and all other controls are 3 pixels below the control on the previous row. After a new control is set up, it's added to the Controls collection with one of the following statements:

Me.Controls.Add(TB)    ' adds a TextBox control
Me.Controls.Add(LBL)   ' adds a Label control

To use the values entered by the user on the dynamic form, we must iterate the Controls collection, extract the values in the TextBox controls, and read their values. Listing 6.9 shows how the Process Values button scans the TextBox controls on the form and performs some basic calculations with them (counting the number of data points and summing their values).

Example 6.9. Reading the controls on the form

Private Sub Button1_Click(...) Handles Button1.Click
    Dim TBox As TextBox
    Dim Sum As Double = 0, points As Integer = 0
    Dim iCtrl As Integer
    For iCtrl = 0 To Me.Controls.Count - 1
        If Me.Controls(iCtrl).GetType Is
                GetType(System.Windows.Forms.TextBox) Then
            TBox = CType(Me.Controls(iCtrl), TextBox)
            If IsNumeric(TBox.Text) Then
                Sum = Sum + Val(TBox.Text)
                points = points + 1
            End If
        End If
    Next
    MsgBox("The sum of the " & points.ToString &
           " data points is " & Sum.ToString)
End Sub

Creating Event Handlers at Runtime

You saw how to add controls on your forms at runtime and how to access the properties of these controls from within your code. In many situations, this is all you need: a way to access the properties of the controls (the text on a TextBox control or the status of a CheckBox or RadioButton control). What good is a Button control, however, if it can't react to the Click event? The only problem with the controls you add to the Controls collection at runtime is that they don't react to events. It's possible, though, to create event handlers at runtime, and this is what you'll learn in this section.

To create an event handler at runtime, create a subroutine that accepts two arguments — the usual sender and e arguments — and enter the code you want to execute when a specific control receives a specific event. The type of the e argument must match the definition of the second argument of the event for which you want to create a handler. Let's say that you want to add one or more buttons at runtime on your form and these buttons should react to the Click event. Create the ButtonClick() subroutine and enter the appropriate code in it. The name of the subroutine can be anything; you don't have to make up a name that includes the control's or the event's name.

After the subroutine is in place, you must connect it to an event of a specific control. The ButtonClick() subroutine, for example, must be connected to the Click event of a Button control. The statement that connects a control's event to a specific event handler is the AddHandler statement, whose syntax is as follows:

AddHandler control.event, New System.EventHandler(AddressOf ButtonClick)

Consider, for example, an application that performs certain calculations with an existing subroutine. To connect the ProcessNow() subroutine to the Click event of the Calculate button, use the following statement:

AddHandler Calculate.Click,
     New System.EventHandler(AddressOf ProcessNow)

You can use similar statements to connect the same subroutine to other control event handlers. You can also associate multiple controls' Click event handler with the ProcessNow() subroutine.

Let's add a little more complexity to the DynamicForm application. I'll program the Enter and Leave events of the TextBox controls added to the form at runtime. When a TextBox control receives the focus, I'll change its background color to a light yellow, and when it loses the focus, I'll restore the background to white so the user knows which box has the focus at any time. I'll use the same handlers for all TextBox controls. (The code for the two handlers is shown in Listing 6.10.)

Example 6.10. Event handlers added at runtime

Private Sub TBox_Enter(ByVal sender As Object,
                       ByVal e As System.EventArgs)
   CType(sender, TextBox).BackColor = color.LightCoral
End Sub

Private Sub TBox_Leave(ByVal sender As Object,
                       ByVal e As System.EventArgs)
   CType(sender, TextBox).BackColor = color.White
End Sub

The two subroutines use the sender argument to find out which TextBox control received or lost the focus, and they set the appropriate control's background color. (These subroutines are not event handlers yet because they're not followed by the Handles keyword — at least, not before we associate them with an actual control and a specific event.) This process is done in the same segment of code that sets the properties of the controls we create dynamically at runtime. After adding the control to the Me.Controls collection, call the following statements to connect the new control's Enter and Leave events to the appropriate handlers:

AddHandler TB.Enter, New System.EventHandler(AddressOf TBox_Enter)
AddHandler TB.Leave, New System.EventHandler(AddressOf TBox_Leave)

Note that you don't have to raise the event from within your code; neither do you specify the arguments to the event. Since you've associated the two routines with the Click event handler, the compiler knows that they're Click event handlers and passes the appropriate arguments to them. All you have to do is make sure the signatures of the two routines match the signature of the Click event handler.

Run the DynamicForm application and see how the TextBox controls handle the focus-related events. With a few statements and a couple of subroutines, we were able to create event handlers at runtime from within our code.

Designing Menus

Menus are among the most common and most characteristic elements of the Windows user interface. Even in the old days of character-based displays, menus were used to display methodically organized choices and guide the user through an application. Despite the visually rich interfaces of Windows applications and the many alternatives, menus are still the most popular means of organizing a large number of options. Many applications duplicate some or all of their menus in the form of toolbar icons, but the menu is a standard fixture of a form. You can turn the toolbars on and off, but not the menus.

The Menu Editor

Menus can be attached only to forms, and they're implemented through the MenuStrip control. The items that make up the menu are ToolStripMenuItem objects, which belong to a MenuStrip control (they're the menu options) or to another ToolStripMenuItem (they form submenus). As you will see, the MenuStrip control and ToolStripMenuItem objects give you absolute control over the structure and appearance of the menus of your application. The MenuStrip control is a variation of the Strip control, which is the base of menus, toolbars, and status bars.

You can design menus visually and then program their Click event handlers. In principle, that's all there is to a menu: You specify its items (the menu commands) and then you program each command's actions, as if the menu items were buttons. Depending on the needs of your application, you might want to enable and disable certain commands, add context menus to some of the controls on your form, and so on. Because each item in a menu is represented by a ToolStripMenuItem object, you can control the application's menus from within your code by manipulating the properties of the ToolStripMenuItem objects. Let's start by designing a simple menu, and I'll show you how to manipulate the menu objects from within your code as we go along.

Double-click the MenuStrip icon in the Toolbox. (You'll find the MenuStrip control in the Menus & Toolbars tab of the Toolbox.) An instance of the MenuStrip control will be added to the form, and a single menu command will appear on your form. Its caption will be Type Here. If you don't see the first menu item on the form right away, select the MenuStrip control in the Components tray below the form. Do as the caption says: Click it and enter the first command caption, File, as shown in Figure 6.16. To add items under the File menu, press Enter. To enter another command in the main menu, press Tab. Depending on your action, another box will be added and you can type the caption of the next command in it. Press Enter to move to the next item vertically and Tab to move to the next item horizontally. To insert a separator enter a hyphen (-) as the item's caption.

When you hover the pointer over a menu item, a drop-down button appears to the right of the item. Click this button to select the type of item you'll place on the menu. This item can be a MenuItem object, a separator, a ComboBox, or a TextBox. In this chapter, I'll focus on menu items, which are by far the most common elements on a menu. The last two options, however, allow you to build elaborate menus, reminiscent of the Office menus.

Designing a menu on the form

Figure 6.16. Designing a menu on the form

Enter the items you wish to include in the File menu — New, Open, Save, SaveAs, and Exit — and then click somewhere on the form. All the temporary items (the ones with the Type Here caption) will disappear, and the menu will be finalized on the form.

To add the Edit menu, select the MenuStrip icon to activate the visual menu editor and then click the File item. In the new item that appears next to the File item on the control, enter the string Edit. Press Enter and you'll switch to the first item of the Edit menu. Fill the Edit menu with the usual editing commands. Table 6.4 shows the captions (property Text) and names (property Name) for each menu and each command. You can also insert a standard menu with the Insert Standard Items command of the MenuStrip object's context menu.

Table 6.4. The captions and names of the File and Edit menus

Caption

Name

Caption

Name

File

FileMenu

Tools

ToolsMenu

  New

FileNew

Edit

EditMenu

  Open

FileOpen

Undo

EditCopy

  Save

FileSave

Redo

EditRedo

  Save As

FileSaveAs

Cut

EditCut

  Print

FilePrint

Copy

EditCopy

  Print Preview

FilePrintPreview

Paste

EditPaste

  Exit

FileExit

Select All

EditSelectAll

  

 

Help

HelpMenu

The bold items in Table 6.4 are the names of the first-level menus (File and Edit); the captions that are indented in the table are the commands on these two menus. The default names of the menu items you add visually to the application's menu are based on the item's caption followed by the suffix ToolStripMenuItem (FileToolStripMenuItem, NewToolStripMenuItem, and so on). You'll probably want to change the default names to something less redundant. To do so, change the Name property in the Properties window. To view the properties of a menu item, right-click it and select Properties from the context menu. One of the properties you should try out is the LayoutStyle property, which determines the orientation of the menu.

The most convenient method of editing a menu is to use the Items Collection Editor window, which is shown in Figure 6.17. This isn't a visual editor, but you can set all the properties of each menu item in the dialog box without having to switch to the Properties window.

Editing a menu with the Items Collection Editor

Figure 6.17. Editing a menu with the Items Collection Editor

The Add button adds to the menu an item of the type specified in the combo box next to it (a menu item, combo box, or text box). To insert an item at a different location, add it to the menu and then use the arrow buttons to move it up or down. As you add new items, you can set their Text and Name properties on the right pane of the editor. You can also set their font, set the alignment and orientation of the text, and specify an image to be displayed along with the text. To add an image to a menu item, locate the Image property and click the ellipsis button. A dialog box in which you can select the appropriate resource will appear. Notice that all the images you use on your form are stored as resources of the project. You can add all the images and icons you might need in a project to the same resource file and reuse them at will. The TextImageRelation property allows you to specify the relative positions of the text and the image. You can also select to display text only, images only, or text and images for each menu item with the DisplayStyle property.

If the menu item leads to a submenu, you must also specify the submenu's items. Locate the DropDownItems property and click the ellipsis button. An identical window will appear, in which you can enter the drop-down items of the current menu item. Notice that the menu on the form is continuously updated while you edit it in the Items Collection Editor window, so you can see the effects of your changes on the form. Personally, I'm more productive with the editor than with the visual tools, mainly because all the properties are right there and I don't have to switch between the design surface and the Properties window.

Note that except for MenuItems, you can add ComboBoxes and TextBoxes to a menu. The TextBox control can be used to facilitate search operations, similar to the Search box of the browsers. You can also display a number of options in a ComboBox control on the menu. The advantage of the ComboBox menu item is that the selected option is visible at all times. ComboBoxes are used in the menus of Office applications a lot (a typical example is the Font name and size ComboBoxes that allow you to change the current selections' font name and size).

The ToolStripMenuItem Properties

The ToolStripMenuItem class represents a menu command, at any level. If a command leads to a submenu, it's still represented by a ToolStripMenuItem object, which has its own collection of ToolStripMenuItem objects: the DropDownItems property, which is a collection and it's made up of ToolStripMenuItem objects. The ToolStripMenuItem class provides the following properties, which you can set in the Properties window at design time or manipulate from within your code:

Checked

Some menu commands act as toggles, and they are usually selected (checked) to indicate that they are on or deselected (unchecked) to indicate that they are off. To initially display a check mark next to a menu command, set its Checked property to True. You can also access this property from within your code to change the checked status of a menu command at runtime. For example, to toggle the status of a menu command called FntBold, use this statement:

FntBold.Checked = Not FntBold.Checked
Enabled

Some menu commands aren't always available. The Paste command, for example, has no meaning if the Clipboard is empty (or if it contains data that can't be pasted in the current application). To indicate that a command can't be used at the time, you set its Enabled property to False. The command then appears grayed out in the menu, and although it can be highlighted, it can't be activated.

IsOnDropDown

If the menu command represented by a ToolStripMenuItem object belongs to a submenu, its IsOnDropDown property is True; otherwise, it's False. The IsOnDropDown property is read-only and False for the items on the first level of the menu.

Visible

To remove a command temporarily from the menu, set the command's Visible property to False. The Visible property isn't used frequently in menu design. In general, you should prefer to disable a command to indicate that it can't be used at the time (some other action is required to enable it). Making a command invisible frustrates users, who might spend time trying to locate the command in another menu.

Programming Menu Commands

When a menu item is selected by the user, it triggers a Click event. To program a menu item, insert the appropriate code in the item's Click event handler. The Exit command's code would be something like the following:

Sub menuExit(...) Handles menuExit.Click
   End
End Sub

If you need to execute any cleanup code before the application ends, place it in the CleanUp() subroutine and call this subroutine from within the Exit item's Click event handler:

Sub menuExit(...) Handles menuExit.Click
   CleanUp()
   End
End Sub

The same subroutine must also be called from within the FormClosing event handler of the application's main form because some users might terminate the application by clicking the form's Close button.

An application's Open menu command contains the code that prompts the user to select a file and then open it. You will see many examples of programming menu commands in the following chapters. All you really need to know now is that each menu item is a ToolStripMenuItem object and each fires the Click event every time it's selected with the mouse or the keyboard. In most cases, you can treat the Click event handler of a ToolStripMenuItem object just like the Click event handler of a Button.

Another interesting event of the ToolStripMenuItem is the DropDownOpened event, which is fired when the user opens a menu or submenu (in effect, when the user clicks a menu item that leads to a submenu). In this event's handler, you can insert code to modify the submenu. The Edit menu of just about any application contains the ubiquitous Cut/Copy/Paste commands. These commands are not meaningful at all times. If the Clipboard doesn't contain text, the Paste command should be disabled. If no text is selected, the Copy and Cut commands should also be disabled. Here's how you could change the status of the Paste command from within the DropDownOpened event handler of the Edit menu:

If My.Computer.Clipboard.ContainsText Then
    PasteToolStripMenuItem.Enabled = True
Else
    PasteToolStripMenuItem.Enabled = True
End If

Likewise, to change the status of the Cut and Copy commands, use the following statements in the DropDownOpened event of the ToolStripMenuItem that represents the Edit menu:

If txtEditor.SelectedText.Trim.Length > 0 Then
    CopyToolStripMenuItem.Enabled = True
    CutToolStripMenuItem.Enabled = True
Else
    CopyToolStripMenuItem.Enabled = False
    CutToolStripMenuItem.Enabled = False
End If

Using Access and Shortcut Keys

Menus provide a convenient way to display a large number of choices to the user. They allow you to organize commands in groups, according to their functions, and are available at all times. Opening menus and selecting commands with the mouse, however, can be an inconvenience. When using a word processor, for example, you don't want to have to take your hands off the keyboard and reach for the mouse. To simplify menu access, Windows forms support access keys and shortcut keys.

Access Keys

Access keys allow the user to open a menu by pressing the Alt key and a letter key. To open the Edit menu in all Windows applications, for example, you can press Alt+E. E is the Edit menu's access key. After the menu is open, the user can select a command with the arrow keys or by pressing another key, which is the command's access key, without holding down the Alt key.

Access keys are designated by the designer of the application and are marked with an underline character. To assign an access key to a menu item, insert the ampersand symbol (&) in front of the character you want to use as an access key in the ToolStripMenuItem's Text property.

Because the & symbol has a special meaning in menu design, you can't use it in a menu item's caption. To actually display the & symbol in a caption, prefix it with another & symbol. For example, the caption &Drag produces a command with the caption Drag (the first character is underlined because it's the access key). The caption Drag && Drop will create another command whose caption will be Drag & Drop. Finally, the string &Drag && Drop will create another command with the caption Drag & Drop (note the underline character in front of the first uppercase D in the string).

Shortcut Keys

Shortcut keys are similar to access keys, but instead of opening a menu, they run a command when pressed. Assign shortcut keys to frequently used menu commands so that users can reach them with a single keystroke. Shortcut keys are combinations of the Ctrl key and a function or character key. For example, the usual access key for the Close command (after the File menu is opened with Alt+F) is C, but the usual shortcut key for the Close command is Ctrl+W.

To assign a shortcut key to a menu command, drop down the ShortcutKeys list in the ToolStripMenuItem's Properties window and select a keystroke. Specify a modifier (Shift, Ctrl, or Alt) and a key. When assigning access and shortcut keys, take into consideration the well-established Windows standards. Users expect Alt+F to open the File menu, so don't use Alt+F for the Format menu. Likewise, pressing Ctrl+C universally performs the Copy command; don't use Ctrl+C as a shortcut for the Cut command.

Manipulating Menus at Runtime

Dynamic menus change at runtime to display more or fewer commands, depending on the current status of the program. The following sections explore two techniques for implementing dynamic menus:

  • Creating short and long versions of the same menu

  • Adding and removing menu commands at runtime

Creating Short and Long Menus

A common technique in menu design is to create long and short versions of a menu. If a menu contains many commands and most of the time only a few of them are needed, you can create one menu with all the commands and another with the most common ones. The first menu is the long one, and the second is the short one. The last command in the long menu should be Short Menu, and when selected, it should display the short version. The last command in the short menu should be Long Menu (or Full Menu), and it should display the long version.

Figure 6.18 shows a long and a short version of the same menu from the LongMenu project. The short version omits infrequently used commands and is easier to handle.

The two versions of the Format menu of the LongMenu application

Figure 6.18. The two versions of the Format menu of the LongMenu application

To implement the LongMenu command, start a new project and create a menu with the options shown in Figure 6.18. Listing 6.11 is the code that shows/hides the long menu in the MenuSize command's Click event.

Example 6.11. The MenuSize menu item's Click event

Private Sub mnuSize_Click(...) Handles mnuSize.Click
    If mnuSize.Text = "Short Menu" Then
        mnuSize.Text = "Long Menu"
    Else
        mnuSize.Text = "Short Menu"
    End If
    mnuUnderline.Visible = Not mnuUnderline.Visible
    mnuStrike.Visible = Not mnuStrike.Visible
    mnuSmallCaps.Visible = Not mnuSmallCaps.Visible
    mnuAllCaps.Visible = Not mnuAllCaps.Visible
End Sub

The subroutine in Listing 6.11 doesn't do much. It simply toggles the Visible property of certain menu commands and changes the command's caption to Short Menu or Long Menu, depending on the menu's current status.

Adding and Removing Commands at Runtime

I conclude the discussion of menu design with a technique for building dynamic menus, which grow and shrink at runtime. Many applications maintain a list of the most recently opened files in the File menu. When you first start the application, this list is empty, and as you open and close files, it starts to grow.

The RunTimeMenu project, available for download from www.sybex.com/go/masteringvb2010, demonstrates how to add items to and remove items from a menu at runtime. The main menu of the application's form contains the Run Time Menu submenu, which is initially empty.

The two buttons on the form add commands to and remove commands from the Run Time Menu. Each new command is appended at the end of the menu, and the commands are removed from the bottom of the menu first (the most recently added commands are removed first). To change this order and display the most recent command at the beginning of the menu, use the Insert method instead of the Add method to insert the new item. Listing 6.12 shows the code behind the two buttons that add and remove menu items.

Example 6.12. Adding and removing menu items at runtime

Private Sub bttnAddItem_Click(...) Handles bttnAddItem.Click
   Dim Item As New ToolStripMenuItem
   Item.Text = "Run Time Option" &
             RunTimeMenuToolStripMenuItem.DropDownItems.Count.ToString
   RunTimeMenuToolStripMenuItem.DropDownItems.Add(Item)
   AddHandler Item.Click, New System.EventHandler(AddressOf OptionClick)
End Sub

Private Sub bttnRemoveItem_Click(...) Handles bttnRemoveItem.Click
   If RunTimeMenuToolStripMenuItem.DropDownItems.Count > 0 Then
       Dim mItem As ToolStripItem
       Dim items As Integer =
                   RunTimeMenuToolStripMenuItem.DropDownItems.Count
       mItem = RunTimeMenuToolStripMenuItem.DropDownItems(items - 1)
   RunTimeMenuToolStripMenuItem.DropDownItems.Remove(mItem)
   End If
End Sub

The Remove button's code uses the Remove method to remove the last item in the menu by its index after making sure the menu contains at least one item. The Add button adds a new item and sets its caption to Run Time Option n, where n is the item's order in the menu. In addition, it assigns an event handler to the new item's Click event. This event handler is the same for all the items added at runtime; it's the OptionClick() subroutine. All the runtime options invoke the same event handler — it would be quite cumbersome to come up with a separate event handler for different items. In the single event handler, you can examine the name of the ToolStripMenuItem object that invoked the event handler and act accordingly. The OptionClick() subroutine used in Listing 6.13 displays the name of the menu item that invoked it. It doesn't do anything, but it shows you how to figure out which item of the Run Time Menu was clicked.

Example 6.13. Programming dynamic menu items

Private Sub OptionClick(...)
    Dim itemClicked As New ToolStripMenuItem
    itemClicked = CType(sender, ToolStripMenuItem)
    MsgBox("You have selected the item " & itemClicked.Text)
End Sub

Creating Context Menus

Nearly every Windows application provides a context menu that the user can invoke by right-clicking a form or a control. (It's sometimes called a shortcut menu or pop-up menu.) This is a regular menu, but it's not anchored on the form. It can be displayed anywhere on the form or on specific controls. Different controls can have different context menus, depending on the operations you can perform on them at the time.

To create a context menu, place a ContextMenuStrip control on your form. The new context menu will appear on the form just like a regular menu, but it won't be displayed there at runtime. You can create as many context menus as you need by placing multiple instances of the ContextMenuStrip control on your form and adding the appropriate commands to each one. To associate a context menu with a control on your form, set the ContextMenu property for that control to the name of the corresponding context menu.

Designing a context menu is identical to designing a regular menu. The only difference is that the first command in the menu is always ContextMenuStrip and it's not displayed along with the menu.

The Bottom Line

Visual form design

Forms expose a lot of trivial properties for setting their appearance. In addition, they expose a few properties that simplify the task of designing forms that can be resized at runtime. The Anchor property causes a control to be anchored to one or more edges of the form to which it belongs. The Dock property allows you to place on the form controls that are docked to one of its edges. To create forms with multiple panes that the user can resize at runtime, use the SplitContainer control. If you just can't fit all the controls in a reasonably sized form, use the AutoScroll properties to create a scrollable form.

Master It

You've been asked to design a form with three distinct sections. You should also allow users to resize each section. How will you design this form?

Design applications with multiple forms.

Typical applications are made up of multiple forms: the main form and one or more auxiliary forms. To show an auxiliary form from within the main form's code, call the auxiliary form's Show method, or the ShowDialog method if you want to display the auxiliary form modally (as a dialog box).

Master It

How will you set the values of selected controls in a dialog box, display them, and then read the values selected by the user from the dialog box?

Design dynamic forms.

You can create dynamic forms by populating them with controls at runtime through the form's Controls collection. First, create instances of the appropriate controls by declaring variables of the corresponding type. Then set the properties of each of these variables that represent controls. Finally, place the control on the form by adding the corresponding variable to the form's Controls collection.

Master It

How will you add a TextBox control to your form at runtime and assign a handler to the control's TextChanged event?

Design menus.

Both form menus and context menus are implemented through the MenuStrip control. The items that make up the menu are ToolStripMenuItem objects. The ToolStripMenuItem objects give you absolute control over the structure and appearance of the menus of your application.

Master It

What are the two basic events fired by the ToolStripMenuItem object?

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

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