14. Graphical User Interfaces with Windows Forms: Part 1

Objectives

In this chapter you’ll learn:

• Design principles of graphical user interfaces (GUIs).

• How to create graphical user interfaces.

• How to process events in response to user interactions with GUI controls.

• The namespaces that contain the classes for GUI controls and event handling.

• How to create and manipulate various controls.

• How to add descriptive ToolTips to GUI controls.

• How to process mouse and keyboard events.

... the wisest prophets make sure of the event first.

Horace Walpole

...The user should feel in control of the computer; not the other way around. This is achieved in applications that embody three qualities: responsiveness, permissiveness, and consistency.

Inside Macintosh, Volume 1 Apple Computer, Inc. 1985

All the better to see you with my dear.

The Big Bad Wolf to Little Red Riding Hood

Outline

14.1 Introduction

14.2 Windows Forms

14.3 Event Handling

14.3.1 A Simple Event-Driven GUI

14.3.2 Visual Studio Generated GUI Code

14.3.3 Delegates and the Event-Handling Mechanism

14.3.4 Another Way to Create Event Handlers

14.3.5 Locating Event Information

14.4 Control Properties and Layout

14.5 Labels, TextBoxes and Buttons

14.6 GroupBoxes and Panels

14.7 CheckBoxes and RadioButtons

14.8 PictureBoxes

14.9 ToolTips

14.10 NumericUpDown Control

14.11 Mouse-Event Handling

14.12 Keyboard-Event Handling

14.13 Wrap-Up

14.1 Introduction

A graphical user interface (GUI) allows a user to interact visually with a program. A GUI (pronounced “GOO-ee”) gives a program a distinctive “look” and “feel.” Providing different applications with a consistent set of intuitive user-interface components enables users to become productive with each application faster.

Look-and-Feel Observation 14.1

image

Consistent user interfaces enable a user to learn new applications more quickly because the applications have the same “look” and “feel.”

As an example of a GUI, consider Fig. 14.1, which shows a Visual C# Express Edition window containing various GUI controls. Near the top of the window, there’s a menu bar containing the menus File, Edit, View, Project, Build, Debug, Data, Tools, Window, and Help. Below the menu bar is a tool bar of buttons, each with a defined task, such as creating a new project or opening an existing project. There are two tabs below the tool bar—these present information in a tabbed view and allow users to switch between them. These controls form a user-friendly interface through which you have been interacting with the IDE.

Fig. 14.1. GUI controls in the Visual C# IDE window.

image

GUIs are built from GUI controls (which are sometimes called components or widgets—short for window gadgets). GUI controls are objects that can display information on the screen or enable users to interact with an application via the mouse, keyboard or some other form of input (such as voice commands). Several common GUI controls are listed in Fig. 14.2—in the sections that follow and in Chapter 15, we discuss each of these in detail. Chapter 15 also explores the features and properties of additional GUI controls.

Fig. 14.2. Some basic GUI controls.

image

14.2 Windows Forms

Windows Forms are used to create the GUIs for programs. A Form is a graphical element that appears on your computer’s desktop; it can be a dialog, a window or an MDI window (multiple document interface window)—discussed in Chapter 15. A component is an instance of a class that implements the IComponent interface, which defines the behaviors that components must implement, such as how the component is loaded. A control, such as a Button or Label, has a graphical representation at runtime. Some components lack graphical representations (e.g., class Timer of namespace System.Windows.Forms—see Chapter 15). Such components are not visible at run time.

Figure 14.3 displays the Windows Forms controls and components from the C# Toolbox. The controls and components are organized into categories by functionality. Selecting the category All Windows Forms at the top of the Toolbox allows you to view all the controls and components from the other tabs in one list (as shown in Fig. 14.3). In this chapter and the next, we discuss many of these controls and components. To add a control or component to a Form, select that control or component from the Toolbox and drag it on the Form. To deselect a control or component, select the Pointer item in the Toolbox (the icon at the top of the list). When the Pointer item is selected, you cannot accidentally add a new control to the Form.

Fig. 14.3. Components and controls for Windows Forms.

image

When there are several windows on the screen, the active window is the frontmost and has a highlighted title bar. A window becomes the active window when the user clicks somewhere inside it. The active window is said to “have the focus.” For example, in Visual Studio the active window is the Toolbox when you’re selecting an item from it, or the Properties window when you’re editing a control’s properties.

A Form is a container for controls and components. When you drag items from the Toolbox onto the Form, Visual Studio generates code that creates the object and sets its basic properties. This code is updated when the control or component’s properties are modified in the IDE. Removing a control or component from the Form deletes the corresponding generated code. The IDE maintains the generated code in a separate file using partial classes—classes that are split among multiple files and assembled into a single class by the compiler. We could write this code ourselves, but it’s much easier to allow Visual Studio to handle the details. We introduced visual programming concepts in Chapter 2. In this chapter and the next, we use visual programming to build more substantial GUIs.

Each control or component we present in this chapter is located in namespace System.Windows.Forms. To create a Windows application, you generally create a Windows Form, set its properties, add controls to the Form, set their properties and implement event handlers (methods) that respond to events generated by the controls. Figure 14.4 lists common Form properties, methods and a common event.

Fig. 14.4. Common Form properties, methods and an event.

image

When we create controls and event handlers, Visual Studio generates much of the GUI-related code. In visual programming, the IDE maintains GUI-related code and you write the bodies of the event handlers to indicate what actions the program should take when particular events occur.

14.3 Event Handling

Normally, a user interacts with an application’s GUI to indicate the tasks that the application should perform. For example, when you write an e-mail in an e-mail application, clicking the Send button tells the application to send the e-mail to the specified e-mail addresses. GUIs are event driven. When the user interacts with a GUI component, the interaction—known as an event—drives the program to perform a task. Common events (user interactions) that might cause an application to perform a task include clicking a Button, typing in a TextBox, selecting an item from a menu, closing a window and moving the mouse. All GUI controls have events associated with them. Objects of other types can also have associated events as well. A method that performs a task in response to an event is called an event handler, and the overall process of responding to events is known as event handling.

14.3.1 A Simple Event-Driven GUI

The Form in the application of Fig. 14.5 contains a Button that a user can click to display a MessageBox. In line 6, notice the namespace declaration, which is inserted for every class you create. We’ve been removing these from earlier simple examples because they were unnecessary. Namespaces organize groups of related classes. Each class’s name is actually a combination of its namespace name, a dot (.) and the class name. This is known as the class’s fully qualified class name. You can use the class’s simple name (the unqualified class name—SimpleEventExample) in the application. If you were to reuse this class in another application, you’d use the fully qualified name or write a using directive so that you could refer to the class by its simple name. We’ll use namespaces like this in Chapters 15 and Chapters 21. If another namespace also contains a class with the same name, the fully qualified class names can be used to distinguish between the classes in the application and prevent a name conflict (also called a name collision).

Fig. 14.5. Simple event-handling example.

image

Using the techniques presented in Chapter 2, create a Form containing a Button. First, create a new Windows application. Next, rename the Form1.cs file to SimpleEventExample.cs in the Solution Explorer. Click the Form in the designer, then use the Properties window to set the Form’s Text property to "Simple Event Example". Set the Form’s Font property to Segoe UI, 9pt. To do so, select the Font property in the Properties window, then click the ellipsis (...) button in the property’s value field to display a font dialog.

Drag a Button from the Toolbox onto the Form. In the Properties window for the Button, set the (Name) property to clickButton and the Text property to Click Me. You’ll notice that we use a convention in which each variable name we create for a control ends with the control’s type. For example, in the variable name clickButton, “Button” is the control’s type.

When the user clicks the Button in this example, we want the application to respond by displaying a MessageBox. To do this, you must create an event handler for the Button’s Click event. You can create this event handler by double clicking the Button on the Form, which declares the following empty event handler in the program code:

private void clickButton_Click( object sender, EventArgs e )
{
}

By convention, the IDE names the event-handler method as objectName_eventName (e.g., clickButton_Click). The clickButton_Click event handler executes when the user clicks the clickButton control.

Each event handler receives two parameters when it’s called. The first—an object reference typically named sender—is a reference to the object that generated the event. The second is a reference to an event arguments object of type EventArgs (or one of its derived classes), which is typically named e. This object contains additional information about the event that occurred. EventArgs is the base class of all classes that represent event information.

To display a MessageBox in response to the event, insert the statement

MessageBox.Show( "Button was clicked." );

in the event handler’s body. The resulting event handler appears in lines 18–21 of Fig. 14.5. When you execute the application and click the Button, a MessageBox appears displaying the text "Button was clicked".

14.3.2 Visual Studio Generated GUI Code

Visual Studio places the auto-generated GUI code in the Designer.cs file of the Form (SimpleEventExampleForm.Designer.cs in this example). You can open this file by expanding the node in the Solution Explorer window for the file you’re currently working in (SimpleEventExampleForm.cs) and double clicking the file name that ends with Designer.cs. Figs. 14.6 and 14.7 show this file’s contents. The IDE collapses the code in lines 23–57 of Fig. 14.7 by default—you can click the + icon next to line 23 to expand the code, then click the icon next to that line to collapse it.

Fig. 14.6. First half of the Visual Studio generated code file.

image

Now that you have studied classes and objects in detail, this code will be easier to understand. Since this code is created and maintained by Visual Studio, you generally don’t need to look at it. In fact, you do not need to understand most of the code shown here to build GUI applications. However, we now take a closer look to help you understand how GUI applications work.

The auto-generated code that defines the GUI is actually part of the Form’s class—in this case, SimpleEventExample. Line 3 of Fig. 14.6 (and line 9 of Fig. 14.5) uses the partial modifier, which allows this class to be split among multiple files, including the files that contain auto-generated code and those in which you write your own code. Line 59 of Fig. 14.7 declares the clickButton that we created in Design mode. It’s declared as an instance variable of class SimpleEventExampleForm. By default, all variable declarations for controls created through C#’s design window have a private access modifier. The code also includes the Dispose method for releasing resources (lines 14–21) and method InitializeComponent (lines 29–55), which contains the code that creates the Button, then sets some of the Button’s and the Form’s properties. The property values correspond to the values set in the Properties window for each control. Visual Studio adds comments to the code that it generates, as in lines 33–35. Line 42 was generated when we created the event handler for the Button’s Click event.

Fig. 14.7. Second half of the Visual Studio generated code file.

image

Method InitializeComponent is called when the Form is created, and establishes such properties as the Form title, the Form size, control sizes and text. Visual Studio also uses the code in this method to create the GUI you see in design view. Changing the code in InitializeComponent may prevent Visual Studio from displaying the GUI properly.

Error-Prevention Tip 14.1

image

The code generated by building a GUI in Design mode is not meant to be modified directly, which is why this code is placed in a separate file. Modifying this code can prevent the GUI from being displayed correctly in Design mode and might cause an application to function incorrectly. Modify control properties only through the Properties window.

14.3.3 Delegates and the Event-Handling Mechanism

The control that generates an event is known as the event sender. An event-handling method—known as the event handler—responds to a particular event that a control generates. When the event occurs, the event sender calls its event handler to perform a task (i.e., to “handle the event”).

The .NET event-handling mechanism allows you to choose your own names for event-handling methods. However, each event-handling method must declare the proper parameters to receive information about the event that it handles. Since you can choose your own method names, an event sender such as a Button cannot know in advance which method will respond to its events. So, we need a mechanism to indicate which method is the event handler for an event.

Delegates

Event handlers are connected to a control’s events via special objects called delegates. A delegate object holds a reference to a method with a signature that is specified by the delegate type’s declaration. GUI controls have predefined delegates that correspond to every event they can generate. For example, the delegate for a Button’s Click event is of type EventHandler (namespace System). If you look at this type in the online help documentation, you’ll see that it’s declared as follows:

public delegate void EventHandler( object sender, EventArgs e );

This uses the delegate keyword to declare a delegate type named EventHandler, which can hold references to methods that return void and receive two parameters—one of type object (the event sender) and one of type EventArgs. If you compare the delegate declaration with clickButton_Click’s header (Fig. 14.5, line 18), you’ll see that this event handler indeed meets the requirements of the EventHandler delegate. The preceding declaration actually creates an entire class for you. The details of this special class’s declaration are handled by the compiler.

Indicating the Method that a Delegate Should Call

An event sender calls a delegate object like a method. Since each event handler is declared as a delegate, the event sender can simply call the appropriate delegate when an event occurs—a Button calls the EventHandler delegate that corresponds to its Click event in response to a click. The delegate’s job is to invoke the appropriate method. To enable the clickButton_Click method to be called, Visual Studio assigns clickButton_Click to the delegate, as shown in line 42 of Fig. 14.7. This code is added by Visual Studio when you double click the Button control in Design mode. The expression

new System.EventHandler(this.clickButton_Click);

creates an EventHandler delegate object and initializes it with the clickButton_Click method. Line 42 uses the += operator to add the delegate to the Button’s Click event. This indicates that clickButton_Click will respond when a user clicks the Button. The += operator is overloaded by the delegate class that is created by the compiler.

You can actually specify that several different methods should be invoked in response to an event by adding other delegates to the Button’s Click event with statements similar to line 42 of Fig. 14.7. Event delegates are multicast—they represent a set of delegate objects that all have the same signature. Multicast delegates enable several methods to be called in response to a single event. When an event occurs, the event sender calls every method referenced by the multicast delegate. This is known as event multicasting. Event delegates derive from class MulticastDelegate, which derives from class Delegate (both from namespace System).

14.3.4 Another Way to Create Event Handlers

For the GUI application in Fig. 14.5, you double clicked the Button control on the Form to create its event handler. This technique creates an event handler for a control’s default event—the event that is most frequently used with that control. Controls can generate many different events, and each one can have its own event handler. For instance, your application can also provide an event handler for a Button’s MouseHover event, which occurs when the mouse pointer remains positioned over the Button for a short period of time. We now discuss how to create an event handler for an event that is not a control’s default event.

Using the Properties Window to Create Event Handlers

You can create additional event handlers through the Properties window. If you select a control on the Form, then click the Events icon (the lightning bolt icon in Fig. 14.8) in the Properties window, all the events for that control are listed in the window. You can double click an event’s name to display the event handler in the editor, if the event handler already exists, or to create the event handler. You can also select an event, then use the drop-down list to its right to choose an existing method that should be used as the event handler for that event. The methods that appear in this drop-down list are the Form class’s methods that have the proper signature to be an event handler for the selected event. You can return to viewing the properties of a control by selecting the Properties icon (Fig. 14.8).

Fig. 14.8. Viewing events for a Button control in the Properties window.

image

A single method can handle multiple events from multiple controls. For example, the Click events of three Buttons could all be handled by the same method. You can specify an event handler for multiple events by selecting multiple controls and selecting a single method in the Properties window. If you create a new event handler this way, you should rename it appropriately. You could also select each control individually and specify the same method for each one’s event.

14.3.5 Locating Event Information

Read the Visual Studio documentation to learn about the different events raised by each control. To do this, select a control in the IDE and press the F1 key to display that control’s online help (Fig. 14.9). The web page that is displayed contains basic information about the control’s class. In the left column of the page are several links to more information about the class—Members, Constructor, Methods, Properties and Events. This list may vary by class. The Members link displays a complete list of the class’s members. This list includes the events that the class can generate. Each of the other links displays a subset of the class’s members. Click the link to the list of events for that control (Button Events in this case) to display the supported events for that control.

Fig. 14.9. List of Button events.

image

Next, click the name of an event to view its description and examples of its use. We selected the Click event to display the information in Fig. 14.10. The Click event is a member of class Control, an indirect base class of class Button. The Remarks section of the page discusses the details of the selected event. Alternatively, you could use the Object Browser to look up this information. The Object Browser shows only the members originally defined in a given class. The Click event is originally defined in class Control and inherited into Button. For this reason, you must look at class Control in the Object Browser to see the documentation for the Click event. See Section 10.15 for more information regarding the Object Browser.

Fig. 14.10. Click event details.

image

14.4 Control Properties and Layout

This section overviews properties that are common to many controls. Controls derive from class Control (namespace System.Windows.Forms). Figure 14.11 lists some of class Control’s properties and methods. The properties shown here can be set for many controls. For example, the Text property specifies the text that appears on a control. The location of this text varies depending on the control. In a Form, the text appears in the title bar, but the text of a Button appears on its face.

Fig. 14.11. Class Control properties and methods.

image

The Select method transfers the focus to a control and makes it the active control. When you press the Tab key in an executing Windows application, controls receive the focus in the order specified by their TabIndex property. This property is set by Visual Studio based on the order in which controls are added to a Form, but you can change the tabbing order. TabIndex is helpful for users who enter information in many controls, such as a set of TextBoxes that represent a user’s name, address and telephone number. The user can enter information, then quickly select the next control by pressing the Tab key.

The Enabled property indicates whether the user can interact with a control to generate an event. Often, if a control is disabled, it’s because an option is unavailable to the user at that time. For example, text editor applications often disable the “paste” command until the user copies some text. In most cases, a disabled control’s text appears in gray (rather than in black). You can also hide a control from the user without disabling the control by setting the Visible property to false or by calling method Hide. In each case, the control still exists but is not visible on the Form.

Anchoring and Docking

You can use anchoring and docking to specify the layout of controls inside a container (such as a Form). Anchoring causes controls to remain at a fixed distance from the sides of the container even when the container is resized. Anchoring enhances the user experience. For example, if the user expects a control to appear in a particular corner of the application, anchoring ensures that the control will always be in that corner—even if the user re-sizes the Form. Docking attaches a control to a container such that the control stretches across an entire side or fills an entire area. For example, a button docked to the top of a container stretches across the entire top of that container, regardless of the width of the container.

When parent containers are resized, anchored controls are moved (and possibly resized) so that the distance from the sides to which they’re anchored does not vary. By default, most controls are anchored to the top-left corner of the Form. To see the effects of anchoring a control, create a simple Windows application that contains two Buttons. Anchor one control to the right and bottom sides by setting the Anchor property as shown in Fig. 14.12. Leave the other control with its default anchoring (top, left). Execute the application and enlarge the Form. Notice that the Button anchored to the bottom-right corner is always the same distance from the Form’s bottom-right corner (Fig. 14.13), but that the other control stays its original distance from the top-left corner of the Form.

Fig. 14.12. Manipulating the Anchor property of a control.

image

Fig. 14.13. Anchoring demonstration.

image

Sometimes, it’s desirable for a control to span an entire side of the Form, even when the Form is resized. For example, a control such as a status bar typically should remain at the bottom of the Form. Docking allows a control to span an entire side (left, right, top or bottom) of its parent container or to fill the entire container. When the parent control is resized, the docked control resizes as well. In Fig. 14.14, a Button is docked at the top of the Form (spanning the top portion). When the Form is resized, the Button is resized to the Form’s new width. Forms have a Padding property that specifies the distance between the docked controls and the Form edges. This property specifies four values (one for each side), and each value is set to 0 by default. Some common control layout properties are summarized in Fig. 14.15.

Fig. 14.14. Docking a Button to the top of a Form.

image

Fig. 14.15. Control layout properties.

image

The Anchor and Dock properties of a Control are set with respect to the Control’s parent container, which could be a Form or another parent container (such as a Panel; discussed in Section 14.6). The minimum and maximum Form (or other Control) sizes can be set via properties MinimumSize and MaximumSize, respectively. Both are of type Size, which has properties Width and Height to specify the size of the Form. Properties MinimumSize and MaximumSize allow you to design the GUI layout for a given size range. The user cannot make a Form smaller than the size specified by property MinimumSize and cannot make a Form larger than the size specified by property MaximumSize. To set a Form to a fixed size (where the Form cannot be resized by the user), set its minimum and maximum size to the same value.

Look-and-Feel Observation 14.2

image

For resizable Forms, ensure that the GUI layout appears consistent across various Form sizes.

Using Visual Studio To Edit a GUI’s Layout

Visual Studio helps you with GUI layout. When you drag a control across a Form, blue snap lines appear to help you position the control with respect to others (Fig. 14.16) and the Form’s edges. This feature makes the control you’re dragging appear to “snap into place” alongside other controls. Visual Studio also provides the Format menu, which contains options for modifying your GUI’s layout. The Format menu does not appear in the IDE unless you select one or more controls in design view. When you select multiple controls, you can align them with the Format menu’s Align submenu. The Format menu also enables you to modify the space between controls or to center a control on the Form.

Fig. 14.16. Snap lines for aligning controls.

image

14.5 Labels, TextBoxes and Buttons

Labels provide text information (as well as optional images) and are defined with class Label (a derived class of Control). A Label displays text that the user cannot directly modify. A Label’s text can be changed programmatically by modifying the Label’s Text property. Figure 14.17 lists common Label properties.

Fig. 14.17. Common Label properties.

image

A textbox (class TextBox) is an area in which either text can be displayed by a program or the user can type text via the keyboard. A password TextBox is a TextBox that hides the information entered by the user. As the user types characters, the password TextBox masks the user input by displaying a password character. If you set the property UseSystemPasswordChar to true, the TextBox becomes a password TextBox. Users often encounter both types of TextBoxes, when logging into a computer or website—the username TextBox allows users to input their usernames; the password TextBox allows users to enter their passwords. Figure 14.18 lists the common properties and a common event of TextBoxes.

Fig. 14.18. TextBox properties and an event.

image

A button is a control that the user clicks to trigger a specific action or to select an option in a program. As you’ll see, a program can use several types of buttons, such as checkboxes and radio buttons. All the button classes derive from class ButtonBase (namespace System.Windows.Forms), which defines common button features. In this section, we discuss class Button, which typically enables a user to issue a command to an application. Figure 14.19 lists common properties and a common event of class Button.

Fig. 14.19. Button properties and an event.

image

Figure 14.20 uses a TextBox, a Button and a Label. The user enters text into a password box and clicks the Button, causing the text input to be displayed in the Label. Normally, we would not display this text—the purpose of password TextBoxes is to hide the text being entered by the user. When the user clicks the Show Me Button, this application retrieves the text that the user typed in the password TextBox and displays it in a Label.

Fig. 14.20. Program to display hidden text in a password box.

image

First, create the GUI by dragging the controls (a TextBox, a Button and a Label) on the Form. Once the controls are positioned, change their names in the Properties window from the default values—textBox1, button1 and label1—to the more descriptive displayPasswordLabel, displayPasswordButton and inputPasswordTextBox. The (Name) property in the Properties window enables us to change the variable name for a control. Visual Studio creates the necessary code and places it in method InitializeComponent of the partial class in the file LabelTextBoxButtonTestForm.Designer.cs.

We set displayPasswordButton’s Text property to “Show Me” and clear the Text of displayPasswordLabel so that it’s blank when the program begins executing. The BorderStyle property of displayPasswordLabel is set to Fixed3D, giving our Label a three-dimensional appearance. We also changed its TextAlign property to MiddleLeft so that the Label’s text is displayed centered between its top and bottom. The password character for inputPasswordTextBox is determined by the user’s system settings when you set UseSystemPasswordChar to true. This property accepts only one character.

We create an event handler for displayPasswordButton by double clicking this control in Design mode. We added line 24 to the event handler’s body. When the user clicks the Show Me Button in the executing application, line 24 obtains the text entered by the user in inputPasswordTextBox and displays the text in displayPasswordLabel.

14.6 GroupBoxes and Panels

GroupBoxes and Panels arrange controls on a GUI. GroupBoxes and Panels are typically used to group several controls of similar functionality or several controls that are related in a GUI. All of the controls in a GroupBox or Panel move together when the GroupBox or Panel is moved. Furthermore, a GroupBoxes and Panels can also be used to show or hide a set of controls at once. When you modify a container’s Visible property, it toggles the visibility of all the controls within it.

The primary difference between these two controls is that GroupBoxes can display a caption (i.e., text) and do not include scrollbars, whereas Panels can include scrollbars and do not include a caption. GroupBoxes have thin borders by default; Panels can be set so that they also have borders by changing their BorderStyle property. Figures 14.2114.22 list the common properties of GroupBoxes and Panels, respectively.

Look-and-Feel Observation 14.3

image

Panels and GroupBoxes can contain other Panels and GroupBoxes for more complex layouts.

Fig. 14.21. GroupBox properties.

image

Fig. 14.22. Panel properties.

image

Look-and-Feel Observation 14.4

image

You can organize a GUI by anchoring and docking controls inside a GroupBox or Panel. The GroupBox or Panel then can be anchored or docked inside a Form. This divides controls into functional “groups” that can be arranged easily.

To create a GroupBox, drag its icon from the Toolbox onto a Form. Then, drag new controls from the Toolbox into the GroupBox. These controls are added to the GroupBox’s Controls property and become part of the GroupBox. The GroupBox’s Text property specifies the caption.

To create a Panel, drag its icon from the Toolbox onto the Form. You can then add controls directly to the Panel by dragging them from the Toolbox onto the Panel. To enable the scrollbars, set the Panel’s AutoScroll property to true. If the Panel is resized and cannot display all of its controls, scrollbars appear (Fig. 14.23). The scrollbars can be used to view all the controls in the Panel—both at design time and at execution time. In Fig. 14.23, we set the Panel’s BorderStyle property to FixedSingle so that you can see the Panel in the Form.

Fig. 14.23. Creating a Panel with scrollbars.

image

Look-and-Feel Observation 14.5

image

Use Panels with scrollbars to avoid cluttering a GUI and to reduce the GUI’s size.

The program in Fig. 14.24 uses a GroupBox and a Panel to arrange Buttons. When these Buttons are clicked, their event handlers change the text on a Label.

Fig. 14.24. Using GroupBoxes and Panels to arrange Buttons.

image

image

The mainGroupBox has two Buttons—hiButton (which displays the text Hi) and bye-Button (which displays the text Bye). The Panel (named mainPanel) also has two Buttons, leftButton (which displays the text Far Left) and rightButton (which displays the text Far Right). The mainPanel has its AutoScroll property set to true, allowing scrollbars to appear when the contents of the Panel require more space than the Panel’s visible area. The Label (named messageLabel) is initially blank. To add controls to mainGroupBox or mainPanel, Visual Studio calls method Add of each container’s Controls property. This code is placed in the partial class located in the file GroupBoxPanelExample.Designer.cs.

The event handlers for the four Buttons are located in lines 18–39. Lines 20, 26, 32 and 38 change the text of messageLabel to indicate which Button the user pressed.

14.7 CheckBoxes and RadioButtons

C# has two types of state buttons that can be in the on/off or true/false states—CheckBoxes and RadioButtons. Like class Button, classes CheckBox and RadioButton are derived from class ButtonBase.

CheckBoxes

A CheckBox is a small square that either is blank or contains a check mark. When the user clicks a CheckBox to select it, a check mark appears in the box. If the user clicks the Check-Box again to deselect it, the check mark is removed. You can also configure a CheckBox to toggle between three states (checked, unchecked and indeterminate) by setting its Three-State property to true. Any number of CheckBoxes can be selected at a time. A list of common CheckBox properties and events appears in Fig. 14.25.

Fig. 14.25. CheckBox properties and events.

image

The program in Fig. 14.26 allows the user to select CheckBoxes to change a Label’s font style. The event handler for one CheckBox applies bold and the event handler for the other applies italic. If both CheckBoxes are selected, the font style is set to bold and italic. Initially, neither CheckBox is checked.

Fig. 14.26. Using CheckBoxes to change font styles.

image

The boldCheckBox has its Text property set to Bold. The italicCheckBox has its Text property set to Italic. The Text property of outputLabel is set to Watch the font style change. After creating the controls, we define their event handlers. Double clicking the CheckBoxes at design time creates empty CheckedChanged event handlers.

To change a Label’s font style, set its Font property to a new Font object (lines 23–24 and 32–33). Class Font is in the System.Drawing namespace. The Font constructor that we use here takes the current font and new style as arguments. The first argument—output-Label.Font—uses outputLabel’s original font name and size. The style is specified with a member of the FontStyle enumeration, which contains Regular, Bold, Italic, Strikeout and Underline. (The Strikeout style displays text with a line through it.) A Font object’s Style property is read-only, so it can be set only when the Font object is created.

Combining Font Styles with Bitwise Operators

Styles can be combined via bitwise operators—operators that perform manipulation on bits of information. Recall from Chapter 1 that all data is represented in the computer as combinations of 0s and 1s. Each 0 or 1 represents a bit. The FontStyle (namespace System.Drawing) is represented as a set of bits that are selected in a way that allows us to combine different FontStyle elements to create compound styles, using bitwise operators. These styles are not mutually exclusive, so we can combine different styles and remove them without affecting the combination of previous FontStyle elements. We can combine these various font styles, using either the logical OR (|) operator or the logical exclusive OR (^) operator (also called XOR). When the logical OR operator is applied to two bits, if at least one bit of the two has the value 1, then the result is 1. Combining styles using the logical OR operator works as follows. Assume that FontStyle.Bold is represented by bits 01 and that FontStyle.Italic is represented by bits 10. When we use the logical OR (|) to combine the styles, we obtain the bits 11.

01   =   Bold
10   =   Italic
--
11   =   Bold and Italic

The logical OR operator helps create style combinations. However, what happens if we want to undo a style combination, as we did in Fig. 14.26?

The logical exclusive OR operator enables us to combine styles and to undo existing style settings. When logical exclusive OR is applied to two bits, if both bits have the same value, then the result is 0. If both bits are different, then the result is 1.

Combining styles using logical exclusive OR works as follows. Assume, again, that FontStyle.Bold is represented by bits 01 and that FontStyle.Italic is represented by bits 10. When we use logical exclusive OR (^) on both styles, we obtain the bits 11.

01   =   Bold
10   =   Italic
--
11   =   Bold and Italic

Now, suppose that we would like to remove the FontStyle.Bold style from the previous combination of FontStyle.Bold and FontStyle.Italic. The easiest way to do so is to reapply the logical exclusive OR (^) operator to the compound style and Font-Style.Bold.

11   =   Bold and Italic
01   =   Bold
--
10   =   Italic

This is a simple example. The advantages of using bitwise operators to combine FontStyle values become more evident when we consider that there are five FontStyle values (Bold, Italic, Regular, Strikeout and Underline), resulting in 16 FontStyle combinations. Using bitwise operators to combine font styles greatly reduces the amount of code required to check all possible font combinations.

In Fig. 14.26, we need to set the FontStyle so that the text appears in bold if it was not bold originally, and vice versa. Line 24 uses the bitwise logical exclusive OR operator to do this. If outputLabel.Font.Style is bold, then the resulting style is not bold. If the text is originally italic, the resulting style is bold and italic, rather than just bold. The same applies for FontStyle.Italic in line 33.

If we didn’t use bitwise operators to compound FontStyle elements, we’d have to test for the current style and change it accordingly. In boldCheckBox_CheckedChanged, we could test for the regular style and make it bold; test for the bold style and make it regular; test for the italic style and make it bold italic; and test for the italic bold style and make it italic. This is cumbersome because, for every new style we add, we double the number of combinations. Adding a CheckBox for underline would require testing eight additional styles. Adding a CheckBox for strikeout would require testing 16 additional styles.

RadioButtons

Radio buttons (defined with class RadioButton) are similar to CheckBoxes in that they also have two states—selected and not selected (also called deselected). However, RadioButtons normally appear as a group, in which only one RadioButton can be selected at a time. Selecting one RadioButton in the group forces all the others to be deselected. Therefore, RadioButtons are used to represent a set of mutually exclusive options (i.e., a set in which multiple options cannot be selected at the same time).

Look-and-Feel Observation 14.6

image

Use RadioButtons when the user should choose only one option in a group.

Look-and-Feel Observation 14.7

image

Use CheckBoxes when the user should be able to choose multiple options in a group.

All RadioButtons added to a container become part of the same group. To divide RadioButtons into several groups, they must be added to separate containers, such as GroupBoxes or Panels. The common properties and a common event of class RadioButton are listed in Fig. 14.27.

Fig. 14.27. RadioButton properties and an event.

image

Software Engineering Observation 14.1

image

Forms, GroupBoxes, and Panels can act as logical groups for RadioButtons. The RadioButtons within each group are mutually exclusive to each other, but not to RadioButtons in different logical groups.

The program in Fig. 14.28 uses RadioButtons to enable users to select options for a MessageBox. After selecting the desired attributes, the user presses the Display Button to display the MessageBox. A Label in the lower-left corner shows the result of the MessageBox (i.e., which Button the user clicked—Yes, No, Cancel, etc.).

Fig. 14.28. Using RadioButtons to set message-window options.

image

image

image

image

image

image

To store the user’s choices, we create and initialize the iconType and buttonType objects (lines 13–14). Object iconType is of type MessageBoxIcon, and can have values Asterisk, Error, Exclamation, Hand, Information, None, Question, Stop and Warning. The sample output shows only Error, Exclamation, Information and Question icons.

Object buttonType is of type MessageBoxButtons, and can have values Abort-RetryIgnore, OK, OKCancel, RetryCancel, YesNo and YesNoCancel. The name indicates the options that are presented to the user in the MessageBox. The sample output windows show MessageBoxes for all of the MessageBoxButtons enumeration values.

We created two GroupBoxes, one for each set of enumeration values. The GroupBox captions are Button Type and Icon. The GroupBoxes contain RadioButtons for the corresponding enumeration options, and the RadioButtons’ Text properties are set appropriately. Because the RadioButtons are grouped, only one RadioButton can be selected from each GroupBox. There’s also a Button (displayButton) labeled Display. When a user clicks this Button, a customized MessageBox is displayed. A Label (displayLabel) displays which Button the user pressed within the MessageBox.

The event handler for the RadioButtons handles the CheckedChanged event of each RadioButton. When a RadioButton contained in the Button Type GroupBox is checked, the corresponding event handler sets buttonType to the appropriate value. Lines 23–48 contain the event handling for these RadioButtons. Similarly, when the user checks the RadioButtons belonging to the Icon GroupBox, the corresponding event handler associated with these events (lines 51–83) sets iconType to the appropriate value.

The Click event handler for displayButton (lines 86–120) creates a MessageBox (lines 90–93). The MessageBox options are specified with the values stored in iconType and buttonType. When the user clicks one of the MessageBox’s buttons, the result of the message box is returned to the application. This result is a value from the DialogResult enumeration that contains Abort, Cancel, Ignore, No, None, OK, Retry or Yes. The switch statement in lines 96–119 tests for the result and sets displayLabel.Text appropriately.

14.8 PictureBoxes

A PictureBox displays an image. The image can be one of several formats, such as bitmap, GIF (Graphics Interchange Format) and JPEG. A PictureBox’s Image property specifies the image that is displayed, and the SizeMode property indicates how the image is displayed (Normal, StretchImage, Autosize, CenterImage or Zoom). Figure 14.29 describes common PictureBox properties and a common event.

Fig. 14.29. PictureBox properties and an event.

image

Figure 14.30 uses a PictureBox named imagePictureBox to display one of three bitmap images—image0.bmp, image1.bmp or image2.bmp. These images are provided in the Images subdirectory of this chapter’s examples directory. Whenever a user clicks the Next Image Button, the image changes to the next image in sequence. When the last image is displayed and the user clicks the Next Image Button, the first image is displayed again.

Fig. 14.30. Using a PictureBox to display images.

image

image

Using Resources Programmatically

In this example, we added the images to the project as resources. This causes the compiler to embed the images in the application’s executable file and enables the application to access the images through the project’s Properties namespace. By embedding the images in the application, you don’t need to worry about wrapping the images with the application when you move it to another location or computer.

If you’re creating a new project, use the following steps to add images to the project as resources:

  1. After creating your project, right click the project’s Properties node in the Solution Explorer and select Open to display the project’s properties.
  2. From the tabs on the left, click the Resources tab.
  3. At the top of the Resources tab, click the down arrow next to Add Resource and select Add Existing File... to display the Add existing file to resources dialog.
  4. Locate the image files you wish to add as resources and click the Open button. We provided three sample images in the Images folder with this chapter’s examples.
  5. Save your project.

The files now appear in a folder named Resources in the Solution Explorer. We’ll use this technique in most examples that use images going forward.

A project’s resources are stored in its Resources class (of the project’s Properties namespace). The Resources class contains a ResourceManager object for interacting with the resources programmatically. To access an image, you can use the method GetObject, which takes as an argument the resource name as it appears in the Resources tab (e.g., "image0") and returns the resource as an Object. Lines 27–28 invoke GetObject with the result of the expression

string.Format( "image{0}", imageNum )

which builds the name of the resource by placing the index of the next picture (imageNum, which was obtained earlier in line 23) at the end of the word "image". You must convert this Object to type Image (namespace System.Drawing) to assign it to the PictureBox’s Image property (line 26).

The Resources class also provides direct access to the resources you define with expressions of the form Resources.resourceName, where resourceName is the name you provided to the resource when you created it. When using such an expression, the resource returned already has the appropriate type. For example, Properties.Resources.image0 is an Image object representing the first image.

14.9 ToolTips

In Chapter 2, we demonstrated tool tips—the helpful text that appears when the mouse hovers over an item in a GUI. Recall that the tool tips displayed in Visual Studio help you become familiar with the IDE’s features and serve as useful reminders for each toolbar icon’s functionality. Many programs use tool tips to remind users of each control’s purpose. For example, Microsoft Word has tool tips that help users determine the purpose of the application’s icons. This section demonstrates how to use the ToolTip component to add tool tips to your applications. Figure 14.31 describes common properties and a common event of class ToolTip.

Fig. 14.31. ToolTip properties and an event.

image

When you add a ToolTip component from the Toolbox, it appears in the component tray—the gray region below the Form in Design mode. Once a ToolTip is added to a Form, a new property appears in the Properties window for the Form’s other controls. This property appears in the Properties window as ToolTip on, followed by the name of the ToolTip component. For instance, if our Form’s ToolTip were named helpfulToolTip, you would set a control’s ToolTip on helpfulToolTip property value to specify the control’s tool tip text. Figure 14.32 demonstrates the ToolTip component. For this example, we create a GUI containing two Labels, so we can demonstrate different tool tip text for each Label. To make the sample outputs clearer, we set the BorderStyle property of each Label to FixedSingle, which displays a solid border. Since there’s no event-handling code in this example, we did not show the code for the Form class.

Fig. 14.32. Demonstrating the ToolTip component.

image

a)

image

b)

In this example, we named the ToolTip component labelsToolTip. Figure 14.33 shows the ToolTip in the component tray. We set the tool tip text for the first Label to “First Label” and the tool tip text for the second Label to “Second Label”. Figure 14.34 demonstrates setting the tool tip text for the first Label.

Fig. 14.33. Demonstrating the component tray.

image

Fig. 14.34. Setting a control’s tool tip text.

image

14.10 NumericUpDown Control

At times, you’ll want to restrict a user’s input choices to a specific range of numeric values. This is the purpose of the NumericUpDown control. This control appears as a TextBox, with two small Buttons on the right side—one with an up arrow and one with a down arrow. By default, a user can type numeric values into this control as if it were a TextBox or click the up and down arrows to increase or decrease the value in the control, respectively. The largest and smallest values in the range are specified with the Maximum and Minimum properties, respectively (both of type decimal). The Increment property (also of type decimal) specifies by how much the current value changes when the user clicks the arrows. Property DecimalPlaces specifies the number of decimal places that the control should display as an integer. Figure 14.35 describes common NumericUpDown properties and an event.

Fig. 14.35. NumericUpDown properties and an event.

image

Figure 14.36 demonstrates a NumericUpDown control in a GUI that calculates interest rate. The calculations performed in this application are similar to those in Fig. 6.6. Text-Boxes are used to input the principal and interest rate amounts, and a NumericUpDown control is used to input the number of years for which we want to calculate interest.

Fig. 14.36. Demonstrating the NumericUpDown control.

image

image

For the NumericUpDown control named yearUpDown, we set the Minimum property to 1 and the Maximum property to 10. We left the Increment property set to 1, its default value. These settings specify that users can enter a number of years in the range 1 to 10 in increments of 1. If we had set the Increment to 0.5, we could also input values such as 1.5 or 2.5. If you don’t modify the DecimalPlaces property (0 by default), 1.5 and 2.5 display as 2 and 3, respectively. We set the NumericUpDown’s ReadOnly property to true to indicate that the user cannot type a number into the control to make a selection. Thus, the user must click the up and down arrows to modify the value in the control. By default, the ReadOnly property is set to false, but the IDE changes this to true when you drag a NumericUpDown onto the Form. The output for this application is displayed in a multiline read-only TextBox with a vertical scrollbar, so the user can scroll through the entire output.

14.11 Mouse-Event Handling

This section explains how to handle mouse events, such as clicks and moves, which are generated when the user interacts with a control via the mouse. Mouse events can be handled for any control that derives from class System.Windows.Forms.Control. For most mouse events, information about the event is passed to the event-handling method through an object of class MouseEventArgs, and the delegate used to create the mouse-event handlers is MouseEventHandler. Each mouse-event-handling method for these events requires an object and a MouseEventArgs object as arguments.

Class MouseEventArgs contains information related to the mouse event, such as the mouse pointer’s x- and y-coordinates, the mouse button pressed (Right, Left or Middle) and the number of times the mouse was clicked. The x- and y-coordinates of the Mouse-EventArgs object are relative to the control that generated the event—i.e., point (0,0) rep-resents the upper-left corner of the control where the mouse event occurred. Several common mouse events and event arguments are described in Fig. 14.37.

Fig. 14.37. Mouse events and event arguments.

image

Figure 14.38 uses mouse events to draw on a Form. Whenever the user drags the mouse (i.e., moves the mouse while a mouse button is pressed), small circles appear on the Form at the position where each mouse event occurs during the drag operation.

Fig. 14.38. Using the mouse to draw on a Form.

image

image

In line 12, the program declares variable shouldPaint, which determines whether to draw on the Form. We want the program to draw only while the mouse button is pressed (i.e., held down). Thus, when the user clicks or holds down a mouse button, the system generates a MouseDown event, and the event handler (lines 21–26) sets shouldPaint to true. When the user releases the mouse button, the system generates a MouseUp event, shouldPaint is set to false in the PainterForm_MouseUp event handler (lines 29–33) and the program stops drawing. Unlike MouseMove events, which occur continuously as the user moves the mouse, the system generates a MouseDown event only when a mouse button is first pressed and generates a MouseUp event only when a mouse button is released.

Whenever the mouse moves over a control, the MouseMove event for that control occurs. Inside the PainterForm_MouseMove event handler (lines 36–48), the program draws only if shouldPaint is true (i.e., a mouse button is pressed). In the using statement, line 42 calls inherited Form method CreateGraphics to create a Graphics object that allows the program to draw on the Form. Class Graphics provides methods that draw various shapes. For example, lines 44–45 use method FillEllipse to draw a circle. The first parameter to method FillEllipse in this case is an object of class SolidBrush, which specifies the solid color that will fill the shape. The color is provided as an argument to class SolidBrush’s constructor. Type Color contains numerous predefined color constants—we selected Color.BlueViolet. FillEllipse draws an oval in a bounding rectangle that is specified by the x- and y-coordinates of its upper-left corner, its width and its height—the final four arguments to the method. The x- and y-coordinates represent the location of the mouse event and can be taken from the mouse-event arguments (e.X and e.Y). To draw a circle, we set the width and height of the bounding rectangle so that they’re equal—in this example, both are 4 pixels. Graphics, SolidBrush and Color are all part of the namespace System.Drawing. Recall from Chapter 13 that the using statement automatically calls Dispose on the object that was created in the parentheses following keyword using. This is important because Graphics objects are a limited resource. Calling Dispose on a Graphics object ensures that its resources are returned to the system for reuse.

14.12 Keyboard-Event Handling

Key events occur when keyboard keys are pressed and released. Such events can be handled for any control that inherits from System.Windows.Forms.Control. There are three key events—KeyPress, KeyUp and KeyDown. The KeyPress event occurs when the user presses a key that represents an ASCII character. The specific key can be determined with property KeyChar of the event handler’s KeyPressEventArgs argument. ASCII is a 128-character set of alphanumeric symbols, a full listing of which can be found in Appendix C.

The KeyPress event does not indicate whether modifier keys (e.g., Shift, Alt and Ctrl) were pressed when a key event occurred. If this information is important, the KeyUp or Key-Down events can be used. The KeyEventArgs argument for each of these events contains information about modifier keys. Figure 14.39 lists important key event information. Several properties return values from the Keys enumeration, which provides constants that specify the various keys on a keyboard. Like the FontStyle enumeration (Section 14.7), the Keys enumeration is represented with a set of bits, so the enumeration’s constants can be combined to indicate multiple keys pressed at the same time.

Fig. 14.39. Keyboard events and event arguments.

image

Figure 14.40 demonstrates the use of the key-event handlers to display a key pressed by a user. The program is a Form with two Labels that displays the pressed key on one Label and modifier key information on the other.

Fig. 14.40. Demonstrating keyboard events.

image

image

Control charLabel displays the character value of the key pressed, whereas keyInfo-Label displays information relating to the pressed key. Because the KeyDown and KeyPress events convey different information, the Form (KeyDemo) handles both.

The KeyPress event handler (lines 18–22) accesses the KeyChar property of the KeyPressEventArgs object. This returns the pressed key as a char, which we then display in charLabel (line 21). If the pressed key is not an ASCII character, then the KeyPress event will not occur, and charLabel will not display any text. ASCII is a common encoding format for letters, numbers, punctuation marks and other characters. It does not support keys such as the function keys (like F1) or the modifier keys (Alt, Ctrl and Shift).

The KeyDown event handler (lines 25–34) displays information from its KeyEventArgs object. The event handler tests for the Alt, Shift and Ctrl keys by using the Alt, Shift and Control properties, each of which returns a bool value—true if the corresponding key is pressed and false otherwise. The event handler then displays the KeyCode, KeyData and KeyValue properties.

The KeyCode property returns a Keys enumeration value (line 31). The KeyCode property returns the pressed key, but does not provide any information about modifier keys. Thus, both a capital and a lowercase “a” are represented as the A key.

The KeyData property (line 32) also returns a Keys enumeration value, but this property includes data about modifier keys. Thus, if “A” is input, the KeyData shows that both the A key and the Shift key were pressed. Lastly, KeyValue (line 33) returns an int representing a pressed key. This int is the key code. The key code is useful when testing for non-ASCII keys like F12.

The KeyUp event handler (lines 37–41) clears both Labels when the key is released. As we can see from the output, non-ASCII keys are not displayed in charLabel, because the KeyPress event is not generated. For example, charLabel does not display any text when you press the F7 or Tab keys, as shown in Fig. 14.40(b) and (d). However, the KeyDown event still is generated, and keyInfoLabel displays information about the key that is pressed. The Keys enumeration can be used to test for specific keys by comparing the key pressed to a specific KeyCode.

Software Engineering Observation 14.2

image

To cause a control to react when a particular key is pressed (such as Enter), handle a key event and test for the pressed key. To cause a Button to be clicked when the Enter key is pressed on a Form, set the Form’s AcceptButton property.

By default, a keyboard event is handled by the control that currently has the focus. Sometimes it’s appropriate to have the Form handle these events. This can be accomplished by setting the Form’s KeyPreview property to true, which makes the Form receive keyboard events before they’re passed to another control. For example, a key press would raise the Form’s KeyPress, even if a control within the Form has the focus instead of the Form itself.

14.13 Wrap-Up

This chapter introduced several common GUI controls. We discussed event handling in detail, and showed how to create event handlers. We also discussed how delegates are used to connect event handlers to the events of specific controls. You learned how to use a control’s properties and Visual Studio to specify the layout of your GUI. We then demonstrated several controls, beginning with Labels, Buttons and TextBoxes. You learned how to use GroupBoxes and Panels to organize other controls. We then demonstrated CheckBoxes and RadioButtons, which are state buttons that allow users to select among several options. We displayed images in PictureBox controls, displayed helpful text on a GUI with ToolTip components and specified a range of numeric input values for users with a NumericUpDown control. We then demonstrated how to handle mouse and keyboard events. The next chapter introduces additional GUI controls. You’ll learn how to add menus to your GUIs and create Windows applications that display multiple Forms.

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

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