Chapter 9. Using Windows Forms Controls

As Chapter 8 mentions, a control is a programming entity that has a graphical component. Text boxes, labels, list boxes, check boxes, menus, and practically everything else that you see in a Windows application is a control.

A component is similar to a control, except it is not visible at runtime. When you add a component to a form at design time, it appears in the component tray below the bottom of the form. You can select the component and use the Properties window to view and change its properties. At runtime, the component is invisible to the user, although it may display a visible object such as a menu, dialog box, or status icon.

This chapter explains controls and components in general terms. It describes different kinds of controls and components. It explains how your program can use them at design time and runtime to give the user information and to allow the user to control your application. It also explains in general terms how a control's properties, methods, and events work, and it lists some of the most useful properties, methods, and events provided by the Control class. Other controls that are derived from this class inherit those properties, methods, and events unless they are explicitly overridden.

Appendix G, "Windows Forms Controls and Components," describes some of the most commonly used controls in greater detail.

CONTROLS AND COMPONENTS

Controls are graphic by nature. Buttons, text boxes, and labels provide graphical input and feedback for the user. They display data and let the user trigger program actions. Some controls (such as grid controls, tree view controls, and calendar controls) are quite powerful and provide a rich variety of tools for interacting with the user.

In contrast, components are represented by graphical icons at design time and are hidden at runtime. They may display some other object (such as a dialog box, menu, or graphical indicator), but the component itself is hidden from the user.

Many components display information to the user. Others provide information needed by graphical controls. For example, a program can use connection, data adapter, and data set components to define data that should be selected from a database. Then a grid control could display the data to the user. Because the connection, data adapter, and data set objects are components, you can define their properties at design time without writing code.

Figure 9-1 shows a form at design time that contains several components. The components appear in the component tray at the bottom of the form, not on the form's graphical surface.

Some components provide data for graphical controls

Figure 9.1. Some components provide data for graphical controls

This example contains four components. Timer1 fires an event periodically so the program can take some action at specified time intervals. ErrorProvider1 displays an error icon and error messages for certain controls on the form such as TextBoxes. BackgroundWorker1 performs tasks asynchronously while the main program works independently. ImageList1 contains a series of images for use by another control. Usually an ImageList is associated with a control such as a Button, ListView, or TreeView, and provides images for that control. For example, a ListView control can use the images in an ImageList to display icons for the items it contains.

Aside from the lack of a graphical component on the form, working with components is very similar to working with controls. You use the Properties window to set components' properties, the code editor to define event handlers, and code to call their methods. The rest of this chapter focuses on controls, but the same concepts apply just as well to components.

CREATING CONTROLS

Usually you add controls to a form graphically at design time. In some cases, however, you may want to add new controls to a form when the program is running. This gives you a bit more flexibility so that you can change the program's appearance at runtime in response to the program's needs or the user's commands.

For example, suppose an application might need between 1 and 100 text boxes. Most of the time it needs only a few, but depending on the user's input, it might need a lot. You could give the form 100 text boxes and then hide the ones it didn't need, but that would be a waste of memory most of the time. By creating only the number of text boxes actually needed, you can conserve memory in the most common cases.

The following sections explain how to create controls both at design time and at runtime.

Creating Controls at Design Time

To create a control at design time, double-click a form in Solution Explorer to open it in the form editor. Decide which control you want to use from the Toolbox. If the Toolbox tab you are using is in List View mode, it displays the controls' names. If the tab displays only control icons, you can hover the mouse over a tool to see a tooltip that gives the control's name and a brief description. For example, Figure 9-2 shows the Button control's tooltip.

Tools summarize controls

Figure 9.2. Tools summarize controls

After you have chosen a control, you have several ways to add it to the form. First, you can double-click the tool to place an instance of the control on the form at a default size in a default location. After adding the control to the form, the IDE deselects the tool and selects the pointer tool (the upper leftmost tool in the Toolbox's current tab).

Second, you can select the tool in the Toolbox, and then click and drag to place it on the form. If you click the form without dragging, the IDE adds a new control at that position with a default size. After you add the control, the IDE deselects the tool and selects the pointer tool.

Third, if you click and drag a tool from the Toolbox onto the form, Visual Basic makes a new control with a default size at the position where you dropped the tool.

Fourth, if you plan to add many copies of the same type of control to the form, hold down the Ctrl key and click the tool. Now the tool remains selected even after you add a control to the form. When you click and drag on the form, the IDE creates a new control at that position and keeps the tool selected so that you can immediately create another control. When you click the form without dragging the mouse, the IDE adds a new control at that position with a default size. When you are finished adding instances of that control type, click the pointer tool to stop adding new controls.

Adding Controls to Containers

Some controls can contain other controls. For example, the GroupBox and Panel controls can hold other controls.

You can place a control in a container in several ways. If you select the container and then double-click a control's tool in the Toolbox, Visual Basic places the new control inside the container.

When you select a tool and click and drag inside a container, Visual Basic also places the new control inside the container, whether or not it is selected.

You can also click and drag a Toolbox tool onto the container, or click and drag controls from one part of the form onto the container. If you hold down the Ctrl key when you drop the controls, Visual Basic makes new copies of the controls instead of moving the existing controls.

Two common mistakes programmers make with containers are placing a control above a container when they want it inside the container, and vice versa. For example, you can place groups of controls inside different Panel controls and then hide or display the Panels to show different controls at different times. If a control lies above a Panel but is not inside it, the control remains visible even if the Panel is not.

To tell if a control is inside a container, move the container slightly. If the control also moves, it is inside the container. If the control doesn't move, it is above the container but not inside it. (When you're finished with this test, you can press Ctrl+Z or use the Edit menu's Undo command to undo the move and put the container back where it was.)

Creating Controls at Runtime

Normally, you create controls interactively at design time. Sometimes, however, it's more convenient to create new controls at runtime. For example, you may not know how many pieces of data you will need to display until runtime. Sometimes you can display unknown amounts of data using a list, grid, or other control that can hold a variable number of items, but other times you might like to display the data in a series of labels or text boxes. In cases such as these, you need to create new controls at runtime.

The following code shows how a program might create a new Label control. First it declares a variable of type Label and initializes it with the New keyword. It uses the label's SetBounds method to position the label and sets its Text property to "Hello World!" The code then adds the label to the current form's Controls collection.

Dim lbl As New Label
lbl.SetBounds(10, 50, 100, 25)
lbl.Text = "Hello World!"
Me.Controls.Add(lbl)

Usually, a label just displays a message so you don't need to catch its events. Other controls such as buttons and scroll bars, however, are not very useful if the program cannot respond to their events.

You can take two approaches to catching a new control's events. First, you can use the WithEvents keyword when you declare the control's variable. Then you can open the form in the code editor, select the variable's name from the left drop-down list, and select an event from the right drop-down list to give the control an event handler.

The following code demonstrates this approach. It declares a class-level variable btnHi using the WithEvents keyword. When you click the btnMakeHiButton button, its event handler initializes the variable. It sets the control's position and text, and adds it to the form's Controls collection. When the user clicks this button, the btnHi_Click event handler executes and displays a message.

' Declare the btnHi button WithEvents.
Private WithEvents btnHi As Button

' Make the new btnHi button.
Private Sub btnMakeHiButton_Click() Handles btnMakeHiButton.Click
    btnHi = New Button
    btnHi.SetBounds(16, 16, 80, 23)
    btnHi.Text = "Say Hi"
    Me.Controls.Add(btnHi)
End Sub

' The user clicked the btnHi button.
Private Sub btnHi_Click() Handles btnHi.Click
    MessageBox.Show("Hi")
End Sub

                                                  
CHANGING CONTAINERS

This first approach works if you know the number and types of the controls you will need. Then you can define variables for them all using the WithEvents keyword. If you don't know how many controls you need to create, however, this isn't practical. For example, suppose that you want to create a button for each file in a directory. When the user clicks a button, the file should open. If you don't know how many files the directory will hold, you don't know how many variables you'll need.

One solution to this dilemma is to use the AddHandler statement to add event handlers to the new controls. The following code demonstrates this approach. When you click the btnMakeHelloButton button, its Click event handler creates a new Button object, storing it in a locally declared variable. It sets the button's position and text and adds it to the form's Controls collection as before. Next, the program uses the AddHandler statement to make subroutine Hello_Click an event handler for the button's Click event. When the user clicks the new button, subroutine Hello_Click displays a message.

' Make a new Hello button.
Private Sub btnMakeHelloButton_Click() Handles btnMakeHelloButton.Click
    ' Make the button.
    Dim btnHello As New Button
    btnHello.SetBounds(240, 64, 80, 23)
    btnHello.Text = "Say Hello"
    Me.Controls.Add(btnHello)

    ' Add a Click event handler to the button.
    AddHandler btnHello.Click, AddressOf Hello_Click
End Sub

' The user clicked the Hello button.
Private Sub Hello_Click()
    MessageBox.Show("Hello")
End Sub

                                                  
CHANGING CONTAINERS

You can use the same routine as an event handler for more than one button. In that case, the code can convert the sender parameter into a Button object and use the button's Name, Text, and other properties to determine which button was pressed.

To remove a control from the form, simply remove it from the form's Controls collection. To free the resources associated with the control, set any variables that refer to it to Nothing. For example, the following code removes the btnHi control created by the first example:

Me.Controls.Remove(btnHi)
btnHi = Nothing

This code can remove controls that you created interactively at design time, as well as controls you create during runtime.

Example program MakeButtons, available on the book's web site, demonstrates techniques for adding and removing buttons.

PROPERTIES

A property is some value associated with a control. Often, a property corresponds in an obvious way to the control's appearance or behavior. For example, the Text property represents the text that the control displays, BackColor represents the control's background color, Top and Left represent the control's position, and so forth.

Many properties, including Text, BackColor, Top, and Left, apply to many kinds of controls. Other properties work only with certain specific types of controls. For example, the ToolStrip control has an ImageList property that indicates the ImageList control containing the images the ToolStrip should display. Only a few controls such as the ToolStrip have an ImageList property.

The following sections explain how you can manipulate a control's properties interactively at design time or using code at runtime.

Properties at Design Time

To modify a control's properties at design time, open its form in the Windows Forms Designer and click the control. The Properties window displays the control's properties. Figure 9-3 shows the Properties window displaying a Button control's properties. For example, the control's Text property has the value "Make Hi Button," and its TextAlign property (which determines where the button displays its text) is set to MiddleCenter.

The Properties window lets you change a control's properties at design time

Figure 9.3. The Properties window lets you change a control's properties at design time

The drop-down list at the top of the Properties window, just below the Properties title, indicates that this control is named btnMakeHiButton and that it is of the System.Windows.Forms.Button class.

You can set many properties by clicking a property's value in the Properties window and then typing the new value. This works with simple string and numeric values such as the controls' Name and Text properties, and it works with some other properties where typing a value makes some sense.

For example, the HScrollBar control (horizontal scrollbar) has Minimum, Maximum, and Value properties that determine the control's minimum, maximum, and current values. You can click those properties in the Properties window and enter new values. When you press the Enter key or move to another property, the control validates the value you typed. If you entered a value that doesn't make sense (for example, if you typed ABC instead of a numeric value), the IDE reports the error and lets you fix it.

Compound Properties

A few properties have compound values. The Location property includes the X and Y coordinates of the control's upper-left corner. The Size property contains the control's width and height. The Font property is an object that has its own font name, size, boldness, and other font properties.

The Properties window displays these properties with a plus sign on the left. When you click the plus sign, the window expands the property to show the values that it contains. Figure 9-4 shows the same Properties window shown in Figure 9-3 with the Font property expanded. You can click the Font property's subvalues and set them independently just as you can set any other property value.

When you expand a compound property, a minus sign appears to the left (see the Font property in Figure 9-4). Click this minus sign to collapse the property and hide its members.

The Properties window lets you change complex properties at design time

Figure 9.4. The Properties window lets you change complex properties at design time

Some compound properties provide more sophisticated methods for setting the property's values. If you click the ellipsis button to the right of the Font property shown in Figure 9-4, the IDE presents a font selection dialog that lets you set many of the font's properties.

Restricted Properties

Some properties allow more restricted values. For example, the Visible property is a Boolean, so it can only take the values True and False. When you click the property, a drop-down arrow appears on the right. When you click this arrow, a drop-down list lets you select one of the choices, True or False.

Many properties have enumerated values. The Button control's FlatStyle property allows the values Flat, Popup, Standard, and System. When you click the drop-down arrow to the right of this property, a drop-down list appears to let you select one of those values.

You can also double-click the property to cycle through its allowed values. After you select a property, you can use the up and down arrows to move through the values.

Some properties allow different values at different times. For example, some properties contain references to other controls. The Button control's ImageList property is a reference to an ImageList component that contains the picture that the Button should display. If you click the drop-down arrow to the right of this value, the Properties window displays a list of the ImageList components on the form that you might use for this property. This list also contains the entry (none), which you can select to remove any previous control reference in the property.

Many properties take very specialized values and provide specialized property editors to let you select values easily. For example, the Anchor property lets you anchor a control's edges to the edges of its container. Normally, a control is anchored to the top and left edges of the container so that it remains in the same position even if the container is resized. If you also anchor the control on the right, its right edge moves in or out as the container gets wider or narrower. This lets you make controls that resize with their containers in certain useful ways.

If you select the Anchor property and click the drop-down arrow on the right, the Properties window displays the small graphical editor shown in Figure 9-5. Click the skinny rectangles on the left, top, right, or bottom to anchor or unanchor (sometimes called float) the control on those sides. Press the Enter key to accept your choices or press Escape to cancel them.

Some properties, such as Anchor, provide specialized editors to let you select their values

Figure 9.5. Some properties, such as Anchor, provide specialized editors to let you select their values

Other complex properties may provide other editors. These are generally self-explanatory. Click the ellipsis or drop-down arrow to the right of a property value to open the editor, and experiment to see how these editors work.

You can right-click any property's name and select Reset to reset the property to a default value. Many complex properties can take the value "(none)," and for those properties, selecting Reset usually sets the value to "(none)."

Collection Properties

Some properties represent collections of objects. For example, the ListBox control displays a list of items. Its Items property is a collection containing those items. The Properties window displays the value of this property as "(Collection)." If you select this property and click the ellipsis to the right, the Properties window displays a simple dialog box where you can edit the text displayed by the control's items. This dialog box is quite straightforward: Enter the items' text on separate lines and click OK.

Other properties are much more complex. For example, to create a TabControl that displays images on its tabs, you must also create an ImageList component. Select the ImageList component's Images property, and click the ellipsis to the right to display the dialog box shown in Figure 9-6. When you click the Add button, the dialog box displays a file selection dialog box that lets you add an image file to the control. The list on the left shows you the images you have loaded and includes a small thumbnail picture of each image. The values on the right show you the images' properties.

This dialog box lets you load images into an ImageList control at design time

Figure 9.6. This dialog box lets you load images into an ImageList control at design time

After you add pictures to the ImageList control, create a TabControl. Select its ImageList property, click the drop-down arrow on the right, and select the ImageList control you created. Next, select the TabControl's TabPages property, and click the ellipsis on the right to see the dialog box shown in Figure 9-7.

This dialog box lets you edit a TabControl's tab pages

Figure 9.7. This dialog box lets you edit a TabControl's tab pages

Use the Add button to add tab pages to the control. Select a tab page, click its ImageIndex property, click the drop-down arrow to the right, and pick the number of the image in the ImageList that you want to use for this tab. Figure 9-8 shows the resulting ImageTabs example program, which is available for download on the book's web site.

Some properties even contain a collection of objects, each of which contains a collection of objects. For example, the ListView control has an Items property that is a collection. Each item in that collection is an object that has a SubItems property, which is itself a collection. When you display the ListView control as a list with details, an object in the Items collection represents a row in the view and its SubItems values represent the secondary values in the row.

A TabControl displays the images stored in an ImageList component on its tabs

Figure 9.8. A TabControl displays the images stored in an ImageList component on its tabs

To set these values at design time, select the ListView control and click the ellipsis to the right of the control's Items property in the Properties window. Create an item in the editor, and click the ellipsis to the right of the item's SubItems property.

Other complicated properties provide similarly complex editors. Although they may implement involved relationships among various controls and components, they are usually easy enough to figure out with a little experimentation.

Properties at Runtime

Visual Basic lets you set most control properties at design time, but often you will need to get and modify property values at runtime. For example, you might need to change a label's text to tell the user that something has changed, disable a button because it is not applicable at a particular moment, or read the value selected by the user from a list.

As far as your code is concerned, a property is just like any other public variable defined by an object. You get or set a property by using the name of the control, followed by a dot, followed by the name of the property. For example, the following code examines the text in the TextBox named txtPath. If the text doesn't end with a / character, the code adds one. This code both reads and sets the Text property:

If Not txtPath.Text.EndsWith("/") Then txtPath.Text &= "/"

If a property contains a reference to an object, you can use the object's properties and methods in your code. The following code displays a message box indicating whether the txtPath control's font is bold. It examines the TextBox control's Font property. That property returns a reference to a Font object that has a Bold property.

If txtPath.Font.Bold Then
    MessageBox.Show("Bold")
Else
    MessageBox.Show("Not Bold")
End If

If a property represents a collection or array, you can loop through or iterate over the property just as if it were declared as a normal collection or array. The following code lists the items the user has selected in the ListBox control named 1stChoices:

For Each selected_item As Object In lstChoices.SelectedItems()
    Debug.WriteLine(selected_item.ToString())
Next selected_item

A few properties are read-only at runtime, so your code can examine them but not change their values. For example, a Panel control's Controls property returns a collection holding references to the controls inside the Panel. This property is read-only at runtime so you cannot set it equal to a new collection. (The collection provides methods for adding and removing controls so you don't really need to replace the whole collection; you can change the controls that it contains instead.)

Note also that at design time, this collection doesn't appear in the Properties window. Instead of explicitly working with the collection, you add and remove controls interactively by moving them in and out of the Panel control.

A control's Bottom property is also read-only and not shown in the Properties window. It represents the distance between the top of the control's container and the control's bottom edge. This value is really just the control's Top property plus its Height property (control.Bottom = control.Top + control.Height), so you can modify it using those properties instead of setting the Bottom property directly.

Useful Control Properties

This section describes some of the most useful properties provided by the Control class. Appendix A, "Useful Control Properties, Methods, and Events," summarizes these and other Control properties for quick reference. Appendix A doesn't cover every property, just those that are most useful.

All controls (including the Form control) inherit directly or indirectly from the Control class. That means they inherit the Control class's properties, methods, and events (unless they take explicit action to override the Control class's behavior).

Although these properties are available to all controls that inherit from the Control class, many are considered advanced, so they are not shown by the IntelliSense pop-up's Common tab. For example, a program is intended to set a control's position by using its Location property not its Left and Top properties, so Location is in the Common tab whereas Left and Top are only in the Advanced tab.

Figure 9-9 shows the Common tab on the IntelliSense pop-up for a Label control. It shows the Location property but not the Left property. If you click the All tab, you can see Left and the other advanced properties.

The Location property is on the IntelliSense Common tab but the Left property is not

Figure 9.9. The Location property is on the IntelliSense Common tab but the Left property is not

When you type the control's name and enough of the string Left to differentiate it from the Location property (in this case "lblDirectory.Le"), the pop-up automatically switches to show a smaller version of the IntelliSense pop-up listing only properties that contain "Le" such as Left, RightToLeft, and TopLevelControl.

Many of the Control class's properties are straightforward, but a few deserve special attention. The following sections describe some of the more confusing properties in greater detail.

Anchor

The Anchor property allows a control to automatically resize itself when its container is resized. Anchor determines which of the control's edges should remain a fixed distance from the corresponding edges of the container.

For example, normally a control's Anchor property is set to Top, Left. That means the control's top and left positions remain fixed when the container resizes. If the control's upper-left corner is at the point (8, 16) initially, it remains at the position (8, 16) when you resize the container. This is the normal control behavior, and it makes the control appear fixed on the container.

For another example, suppose that you set a control's Anchor property to Top, Right, and you place the control in the container's upper-right corner. When you resize the container, the control moves, so it remains in the upper-right corner.

If you set two opposite Anchor values, the control resizes itself to satisfy them both. For example, suppose that you make a button that starts 8 pixels from its container's left, right, and top edges. Then suppose that you set the control's Anchor property to Top, Left, Right. When you resize the container, the control resizes itself so that it is always 8 pixels from the container's left, right, and top edges.

In a more common scenario, you can place Label controls on the left with Anchor set to Top, Left so they remain fixed on the form. On the right, you can place TextBoxes and other controls with Anchor set to Top, Left, Right, so they resize themselves to take advantage of the resizing form's new width.

Similarly, you can make controls that stretch vertically as the form resizes. For example, if you set a ListBox control's Anchor property to Top, Left, Bottom, the control stretches vertically to take advantage of the form's height and display as much of its list of items as possible.

If you do not provide any Anchor value for either the vertical or horizontal directions, the control anchors its center to the container's center. For example, suppose you position a button in the bottom middle of the form and you set its Anchor property to Bottom only. Because you placed the control in the middle of the form, the control's center coincides with the form's center. When you resize the form, the control moves so it remains centered horizontally.

If you place other controls on either side of the centered one, they will all move so they remain together centered as a group as the form resizes. You may want to experiment with this property to see the effect.

At runtime, you can set a control's Anchor property to AnchorStyles.None or to a Boolean combination of the values AnchorStyles.Top, AnchorStyles.Bottom, AnchorStyles.Left, and AnchorStyles.Right. For example, Example program AnchorButton, available for download on the book's web site, uses the following code to move the btnAnchored control to the form's lower-right corner and set its Anchor property to Bottom, Right, so it stays there:

Private Sub Form1_Load() Handles MyBase.Load
    btnAnchored.Location = New Point( _
        Me.ClientRectangle.Width - Button1.Width, _
        Me.ClientRectangle.Height - Button1.Height)
    btnAnchored.Anchor = AnchorStyles.Bottom Or AnchorStyles.Right
End Sub

                                                  
Anchor

Dock

The Dock property determines whether a control attaches itself to one or more of its container's sides. For example, if you set a control's Dock property to Top, the control docks to the top of its container. It fills the container from lef t to right and is flush with the top of the container. If the container is resized, the control remains at the top, keeps its height, and resizes itself to fill the container's width. This is how a typical toolbar behaves. The effect is similar to placing the control at the top of the container so that it fills the container's width and then setting its Anchor property to Top, Left, Right.

You can set a control's Dock property to Top, Bottom, Left, Right, Fill, or None. The value Fill makes the control dock to all of its container's remaining interior space. If it is the only control in the container, it fills the whole container.

If the container holds more than one control with Dock set to a value other than None, the controls are arranged according to their stacking order (also called the Z-order). The control that is first in the stacking order (would normally be drawn first at the back) is positioned first using its Dock value. The control that comes next in the stacking order is arranged second, and so on until all of the controls are positioned.

Figure 9-10 shows example program Docking, which is available for download on the book's web site. It contains four TextBoxes with Dock set to different values. The first in the stacking order has Dock set to Left so it occupies the left edge of the form. The next control has Dock set to Top, so it occupies the top edge of the form's remaining area. The third control has Dock set to Right, so it occupies the right edge of the form's remaining area. Finally, the last control has Dock set to Fill so it fills the rest of the form.

Docked controls are arranged according to their stacking order

Figure 9.10. Docked controls are arranged according to their stacking order

Controls docked to an edge resize to fill the container in one dimension. For example, a control with Dock set to Top fills whatever width the container has available. A control with Dock set to Fill resizes to fill all of the form's available space.

Other than that, the Dock property does not arrange controls very intelligently when you resize the container. For example, suppose that you have two controls, one above the other. The first has Dock set to Top and the second has Dock set to Fill. You can arrange the controls so that they evenly divide the form vertically. When you make the form taller, however, the second control, with Dock set to Fill, takes up all of the new space, and the other control remains the same size.

You cannot use the Dock property to make the controls divide the form evenly when it is resized. You cannot use the Anchor property to evenly divide the form either. Instead, you need to use code similar to the following. When the form resizes, this code moves and sizes the two controls TextBox1 and TextBox2 to fill the form, evenly dividing it vertically.

Private Sub Form1_Load() Handles Me.Load
    ArrangeTextBoxes()
End Sub
Private Sub Form1_Resize() Handles Me.Resize
    ArrangeTextBoxes()
End Sub
Private Sub ArrangeTextBoxes()
    Dim wid As Integer = Me.ClientRectangle.Width
    Dim hgt1 As Integer = Me.ClientRectangle.Height  2
    Dim hgt2 As Integer = Me.ClientRectangle.Height - hgt1
    txtTop.SetBounds(0, 0, wid, hgt1)
    txtBottom.SetBounds(0, hgt1, wid, hgt2)
End Sub

                                                  
Docked controls are arranged according to their stacking order

Example program DivideForm, available for download on the book's web site, uses similar code to divide its form between two text boxes.

When you want to divide a form, the SplitterContainer control can also be useful. The SplitterContainer contains two panels that can hold other controls. The user can drag the divider between the two panels to adjust the size allocated to each.

Position and Size Properties

Controls contain many position and size properties, and the differences among them can be confusing. Some of the more bewildering aspects of controls are client area, non-client area, and display area.

A control's client area is the area inside the control where you can draw things or place other controls. A control's non-client area is everything else. In a typical form, the borders and title bar are the non-client area. The client area is the space inside the borders and below the title bar where you can place controls or draw graphics.

A control's display area is the client area minus any internal decoration. For example, a GroupBox control displays an internal border and a title. Although you can place controls over these, you normally wouldn't. The display area contains the space inside the GroupBox's borders and below the space where the title sits.

The following table summarizes properties related to the control's size and position.

PROPERTY

DATA TYPE

READ/WRITE

PURPOSE

Bounds

Rectangle

Read/Write

The control's size and position within its container including non-client areas.

ClientRectangle

Rectangle

Read

The size and position of the client area within the control.

ClientSize

Size

Read/Write

The size of the client area. If you set this value, the control adjusts its size to make room for the non-client area, while giving you this client size.

DisplayRectangle

Rectangle

Read

The size and position of the area within the control where you would normally draw or place other controls.

Location

Point

Read/Write

The position of the control's upper-left corner within its container.

Size

Point

Read/Write

The control's size including non-client areas.

Left, Top, Width, Height

Integer

Read/Write

The control's size and position within its container including non-client areas.

Bottom, Right

Integer

Read

The position of the control's lower-right corner within its container.

METHODS

A method executes code associated with a control. The method can be a function that returns a value or a subroutine that does something without returning a value.

Because methods execute code, you cannot invoke them at design time. You can only invoke them by using code at runtime.

Appendix A summarizes the Control class's most useful methods. Controls that inherit from the Control class also inherit these methods unless they have overridden the Control class's behavior.

EVENTS

A control or other object raises an event to let the program know about some change in circumstances. Sometimes raising an event is also called firing or throwing the event. Specific control classes provide events that are relevant to their special purposes. For example, the Button control provides a Click event to let the program know when the user clicks the button.

The program responds to an event by creating an event handler that catches the event and takes whatever action is appropriate. Each event defines its own event-handler format and determines the parameters that the event handler will receive. Often, these parameters give additional information about the event.

For example, when part of the form is covered and exposed, the form raises its Paint event. The Paint event handler takes as a parameter an object of type PaintEventArgs named e. That object's Graphics property is a reference to a Graphics object that the program can use to redraw the form's contents.

Some event handlers take parameters that are used to send information about the event back to the object that raised it. For example, the Form class's FormClosing event handler has a parameter of type FormClosingEventArgs. That parameter is an object that has a property named Cancel. If the program sets Cancel to True, the Form cancels the FormClosing event and remains open. For example, the event handler can verify that the data entered by the user was properly formatted. If the values didn't make sense, the program can display an error message and keep the form open.

Although many of a control's most useful events are specific to the control type, controls do inherit some common events from the Control class. Appendix A summarizes the Control class's most important events. Controls that inherit from the Control class also inherit these events unless they have overridden the Control class's behavior.

Creating Event Handlers at Design Time

You can create an event handler at design time in a couple of ways. If you open a form in the Windows Forms Designer and double-click a control, the code editor opens and displays the control's default event handler. For example, a TextBox control opens its TextChanged event handler, a Button control opens its Click event handler, and the form itself opens its Load event handler.

To create some other non-default event handler for a control, select the control and then click the Properties window's Events button (which looks like a lightning bolt). This makes the Properties window list the control's most commonly used events. If you have defined event handlers already, possibly for other controls, you can select them from the events' drop-down lists. Double-click an event's entry to create a new event handler.

To create other non-default event handlers or to create event handlers inside the code editor, open the code window, select the control from the left drop-down list, and then select the event handler from the right drop-down list, as shown in Figure 9-11. To create an even handler for the form itself, select "(Form1 Events)" from the left dropdown and then select an event from the right dropdown.

To create an event handler in the code window, select a control from the left dropdown, and then select an event from the right dropdown

Figure 9.11. To create an event handler in the code window, select a control from the left dropdown, and then select an event from the right dropdown

The code window creates an event handler with the correct parameters and return value. For example, the following code shows an empty TextBox control's Click event handler (note that the first two lines are wrapped in this text but appear on one line in the code editor). Now you just need to fill in the code that you want to execute when the event occurs.

Private Sub TextBox1_Click(ByVal sender As Object, _
 ByVal e As System.EventArgs) Handles TextBox1.Click
End Sub

WithEvents Event Handlers

If you declare an object variable using the WithEvents keyword, you can catch its events. After you declare the variable, you can select it in the code designer's left dropdown, just as you can select any other control. Then you can pick one of the object's events from the right dropdown.

When the code assigns an instance of an object to the variable, any event handlers defined for the variable receive the object's events. Later, if you set the variable to Nothing, the event handlers no longer receive events.

Usually, you don't need to create WithEvents variables for controls because Visual Basic does it for you. However, using a variable declared WithEvents lets you enable and disable events quickly and easily. For example, suppose a program wants to track a PictureBox's mouse events at some times, but not at others. It declares a PictureBox variable as shown in the following code:

Private WithEvents m_Canvas As PictureBox

When the program wants to receive events, it sets this variable equal to its PictureBox control as in the following code. Now the variable's event handlers such as m_Canvas_MouseDown, m_Canvas_MouseMove, and m_Canvas_MouseUp are enabled.

m_Canvas = PictureBox1

When it no longer wants to receive these events, the program sets m_Canvas to Nothing as in the following statement. While m_Canvas is Nothing, it has no associated control to generate events for it.

m_Canvas = Nothing

Setting Event Handlers at Runtime

Not only can you create event handlers at design time, but you can also assign them at runtime. First create the event handler. You must get the routine's parameters exactly correct for the type of event handler you want to create. For example, a TextBox control's Click event handler must take two parameters with types System.Object and System.EventArgs.

After you build the event handler, you can use the AddHandler and RemoveHandler statements to add and remove the event handler from a control. The following code shows how example program SwitchEventHandlers switches the event handler that a button executes when it is clicked:

' Add or remove event handler 1.
Private Sub radEventHandler1_CheckedChanged() _
 Handles radEventHandler1.CheckedChanged
    If radEventHandler1.Checked Then
        AddHandler btnClickMe.Click, AddressOf EventHandler1
    Else
        RemoveHandler btnClickMe.Click, AddressOf EventHandler1
    End If
End Sub

' Add or remove event handler 2.
Private Sub radEventHandler2_CheckedChanged() _
 Handles radEventHandler2.CheckedChanged
    If radEventHandler2.Checked Then
        AddHandler btnClickMe.Click, AddressOf EventHandler2
    Else
        RemoveHandler btnClickMe.Click, AddressOf EventHandler2
    End If
End Sub

' Display a message telling which event handler this is.
Private Sub EventHandler1(ByVal sender As System.Object, _
 ByVal e As System.EventArgs)
    MessageBox.Show("EventHandler1")
End Sub

Private Sub EventHandler2(ByVal sender As System.Object, _
 ByVal e As System.EventArgs)
    MessageBox.Show("EventHandler2")
End Sub

                                                  
EASY EVENT PARAMETERS

When the user selects or clears radio button radEventHandler1, the control's CheckedChanged event handler adds or removes the EventHandler1 event handler from the btnClickMe control's Click event. Similarly, when the user selects or clears radEventHandler2, its CheckedChanged event handler adds or removes the EventHandler2 event handler from the btnClickMe control's Click event.

The EventHandler1 and EventHandler2 event handlers simply display a message telling you which is executing.

AddHandler and RemoveHandler allow you to switch one or two events relatively easily. If you must switch many event handlers for the same control all at once, however, it may be easier to use a variable declared using the WithEvents keyword.

Control Array Events

Visual Basic 6 and earlier versions allowed you to use control arrays. A control array was an array of controls with the same name that shared the same event handlers. A parameter to the event handlers gave the index of the control in the array that fired the event. If the controls perform closely related tasks, the common event handler may be able to share a lot of code for all of the controls.

Visual Basic .NET does not support control arrays, but you can get similar effects in a couple of ways.

First, suppose that you add a control to a form and give it event handlers. Then you copy and paste the control to make other controls on the form. By default, all of these controls share the event handlers that you created for the first control. If you look at the event handlers' code, you'll see the Handles statements list all of the copied controls. You can also modify an event handler's Handles clause manually to attach it to more than one control.

Another way to make controls share event handlers is to attach them to the controls by using the AddHandler statement.

An event handler's first parameter is a variable of the type System.Object that contains a reference to the object that raised the event. The program can use this object and its properties (for example, its Name or Text property) to determine which control raised the event and take appropriate action.

Validation Events

Data validation is an important part of many applications. Visual Basic provides two events to make validating data easier: Validating and Validated. The following sections describe three approaches to using those events to validate data.

Integrated Validation

The Validating event fires when the code should validate a control's data. This happens when the control has the input focus and the form tries to close, or when focus moves from the control to another control that has its CausesValidation property set to True. Integrated validation uses the Validating event to perform all validation.

The Validating event handler can verify that the data in a control has a legal value and take appropriate action if it doesn't. For example, consider the FiveDigits example program, which is shown in Figure 9-12. The first TextBox control's Validating event handler checks that the control's value contains exactly five digits. If the value does not contain five digits, as is the case in the figure, the program uses an ErrorProvider control to flag the TextBox's value as being in error and moves the input focus back to the TextBox. The ErrorProvider displays the little exclamation mark icon to the right of the control and makes the icon blink several times to get the user's attention. When the user hovers the mouse over the icon, the ErrorProvider displays the error text in a tooltip.

The Validating event fires when the focus moves to a control that has CausesValidation set to True

Figure 9.12. The Validating event fires when the focus moves to a control that has CausesValidation set to True

The second TextBox control in this example has a CausesValidation property value of False. When the user moves from the first TextBox control to the second one, the Validating event does not fire and the TextBox control is not flagged. The third TextBox control has CausesValidation set to True, so when the user moves into that TextBox control, the first TextBox's Validating event fires, and the value is flagged if it is invalid. The Validating event also fires if the user tries to close the form.

The following code shows the Validating event handler used by this example. Notice that the Handles clause lists all three TextBoxes' Validating events so this event handler catches the Validating event for all three controls.

' Validate the TextBox's contents.
Private Sub txtNumber_Validating(ByVal sender As Object, _
 ByVal e As System.ComponentModel.CancelEventArgs) _
 Handles txtNumber1.Validating, txtNumber2.Validating, txtNumber3.Validating
    ' Get the TextBox.
    Dim text_box As TextBox = DirectCast(sender, TextBox)

    ' Validate the control's value.
    ValidateFiveDigits(text_box, e.Cancel)
End Sub

' Verify that the TextBox contains five digits.
Private Sub ValidateFiveDigits(ByVal text_box As TextBox, _
 ByRef cancel_event As Boolean)
    If text_box.Text.Length = 0 Then
        ' Allow a zero-length string.
        cancel_event = False
    Else
        ' Allow five digits.
        cancel_event = Not (text_box.Text Like "#####")
    End If

    ' See if we're going to cancel the event.
    If cancel_event Then
        ' Invalid. Set an error.
        errBadDigits.SetError(text_box, _
            text_box.Name & " must contain exactly five digits")
    Else
        ' Valid. Clear any error.
        errBadDigits.SetError(text_box, "")
    End If
End Sub

                                                  
The Validating event fires when the focus moves to a control that has CausesValidation set to True

The event handler receives the control that raised the event in its sender parameter. It uses DirectCast to convert that generic Object into a TextBox object and passes it to the ValidateFiveDigits subroutine to do all of the interesting work. It also passes the e.Cancel parameter, so the subroutine can cancel the action that caused the event if necessary.

ValidateFiveDigits checks the TextBox control's contents and sets its cancel_event parameter to True if the text has nonzero length and is not exactly five digits. This parameter is passed by reference, so this changes the original value of e.Cancel in the calling event handler. That will restore focus to the TextBox that raised the event and that contains the invalid data.

If cancel_event is True, the value is invalid, so the program uses the ErrorProvider component named errBadDigits to assign an error message to the TextBox control.

If cancel_event is False, the value is valid so the program blanks the ErrorProvider component's error message for the TextBox.

Separated Validation

A control's Validated event fires after the focus successfully leaves the control, either to another control with CausesValidation set to True or when the form closes. The control should have already validated its contents in its Validating event, hence the event name Validated.

This event doesn't really have anything directly to do with validation, however, and it fires whether or not the code has a Validating event handler and even if the control's value is invalid. The only time it will not execute is if the validation does not complete. That happens if the Validating event handler cancels the event causing the validation.

The previous section shows how to set or clear a control's error in its Validating event handler. An alternative strategy is to set errors in the Validating event handler and clear them in the Validated event handler, as shown in the following code. If the control's value is invalid, the Validating event handler cancels the event causing the validation so the Validated event does not occur. If the control's value is valid, the Validating event handler does not cancel the event and the Validated event handler executes, clearing any previous error.

' Validate the TextBox's contents.
Private Sub txtNumber_Validating(ByVal sender As Object, _
 ByVal e As System.ComponentModel.CancelEventArgs) _
 Handles txtNumber1.Validating, txtNumber2.Validating, txtNumber3.Validating
    ' Validate the control's value.
    ValidateFiveDigits(DirectCast(sender, TextBox), e.Cancel)
End Sub

' Verify that the TextBox contains five digits.
 Private Sub ValidateFiveDigits(ByVal text_box As TextBox, _
 ByRef cancel_event As Boolean)
     ' Cancel if nonzero length and not five digits.
     cancel_event = (text_box.Text.Length <> 0) And _
         Not (text_box.Text Like "#####")
     ' See if we're going to cancel the event.
     If cancel_event Then
         ' Invalid. Set an error.
         ErrorProvider1.SetError(text_box, _
             text_box.Name & " must contain exactly five digits")
     End If
End Sub

' Validation succeeded. Clear any error.
Private Sub txtNumber_Validated(ByVal sender As Object, _
 ByVal e As System.EventArgs) _
 Handles txtNumber1.Validated, txtNumber2.Validated, txtNumber3.Validated
    ' Valid. Clear any error.
    ErrorProvider1.SetError(DirectCast(sender, TextBox), "")
End Sub

                                                  
Separated Validation

Example program FiveDigitsSeparate, available for download on the book's web site, demonstrates this approach.

Deferred Validation

By keeping focus in the control that contains the error, the previous approaches force the user to fix problems as soon as possible. In some applications, it may be better to let the user continue filling out other fields and fix the problems later. For example, a user who is touch-typing data into several fields may not look up to see the error until much later, after entering a series of invalid values in the first field and wasting a lot of time.

The following code shows one way to let the user continue entering values in other fields:

' Validate the TextBox's contents.
Private Sub txtNumber_Validating(ByVal sender As Object, _
 ByVal e As System.ComponentModel.CancelEventArgs) _
 Handles txtNumber1.Validating, txtNumber2.Validating, txtNumber3.Validating
    ' Validate the control's value.
    ValidateFiveDigits(DirectCast(sender, TextBox))
End Sub

' Verify that the TextBox contains five digits.
Private Sub ValidateFiveDigits(ByVal text_box As TextBox)
    ' See if the data is valid.
    If (text_box.Text.Length <> 0) And _
        Not (text_box.Text Like "#####") _
    Then
        ' Invalid. Set an error.
        errBadDigits.SetError(text_box, _
            text_box.Name & " must contain exactly five digits")
    Else
        ' Valid. Clear the error.
        errBadDigits.SetError(text_box, "")
    End If
End Sub

' See if any fields have error messages.
Private Sub Form1_FormClosing(ByVal sender As Object, _
 ByVal e As System.Windows.Forms.FormClosingEventArgs) _
 Handles Me.FormClosing
    ' Assume we will cancel the close.
    e.Cancel = True
' Check for errors.
    If IsInvalidField(txtNumber1) Then Exit Sub
    If IsInvalidField(txtNumber3) Then Exit Sub

    ' If we got this far, the data's okay.
    e.Cancel = False
End Sub

' If this control has an error message assigned to it,
' display the message, set focus to the control,
' and return True.
Private Function IsInvalidField(ByVal ctl As Control) As Boolean
    ' See if the control has an associated error message.
    If errBadDigits.GetError(ctl).Length = 0 Then
        ' No error message.
        Return False
    Else
        ' There is an error message.
        ' Display the message.
        MessageBox.Show(errBadDigits.GetError(ctl))

        ' Set focus to the control.
        ctl.Focus()
        Return True
    End If
End Function

                                                  
Deferred Validation

The Validating event handler calls the ValidateFiveDigits subroutine much as before, but this time ValidateFiveDigits does not take the cancel_event parameter. If the TextBox object's value has an error, the routine uses the ErrorProvider to assign an error message to it and exits.

When the user tries to close the form, the FormClosing event handler executes. This routine assumes that some field contains invalid data, so it sets e.Cancel to True. It then calls the function IsInvalidField for each of the controls that it wants to validate. If any call to IsInvalidField returns True, the event handler exits, e.Cancel remains True, and the form refuses to close. If all of the fields pass validation, the event handler sets e.Cancel to False, and the form closes.

The function IsInvalidField uses the ErrorProvider's GetError method to get a control's assigned error message. If the message is blank, the function returns False to indicate that the control's data is valid. If the message is not blank, the function displays it in a message box, sets focus to the control, and returns True to indicate that the form's data is invalid.

If the focus is in a TextBox when the form tries to close, its Validating event fires before the form's FormClosing event so the TextBox control has a chance to validate its contents before the FormClosing event fires.

Example program FiveDigitsDeferred, available for download on the book's web site, demonstrates this approach.

SUMMARY

This chapter describes controls, components, and objects in general terms. It tells how to create controls and how to use their properties, methods, and events. It spends some extra time explaining how to add and remove event handlers, and data-validation events and strategies.

Appendix A, "Useful Control Properties, Methods, and Events," describes the most useful properties, methods, and events provided by the Control class. All controls that inherit from this class also inherit these properties, methods, and events, unless they take action to override the Control class's behavior.

Appendix G, "Windows Forms Controls and Components," describes the standard Windows controls and components in greater detail. This appendix can help you understand the controls to get the most out of them.

The Form class inherits from the Control class all of that class's properties, methods, and events. In some sense a Form is just another control, but it does have special needs and provides special features that are not shared with other controls. To help you use these objects effectively, Chapter 10, "Windows Forms," describes the Form class in greater detail.

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

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