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 ToolTip
s 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
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 Label
s, TextBox
es and Button
s
14.6 GroupBox
es and Panel
s
14.7 CheckBox
es and RadioButton
s
14.8 PictureBox
es
14.9 ToolTip
s
14.10 NumericUpDown
Control
14.11 Mouse-Event Handling
14.12 Keyboard-Event Handling
14.13 Wrap-Up
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.
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.
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.
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.
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.
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.
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.
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.
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"
.
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.
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.
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.
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.
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.
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.
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
).
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.
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.
A single method can handle multiple events from multiple controls. For example, the Click
events of three Button
s 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.
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.
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.
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.
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 TextBox
es 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
.
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.
Fig. 14.13. Anchoring demonstration.
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. Form
s 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
.
Fig. 14.15. Control
layout properties.
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.
For resizable Form
s, ensure that the GUI layout appears consistent across various Form
sizes.
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.
Label
s, TextBox
es and Button
sLabel
s 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.
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 TextBox
es, 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 TextBox
es.
Fig. 14.18. TextBox
properties and an event.
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.
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 TextBox
es 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.
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
.
GroupBox
es and Panel
sGroupBox
es and Panel
s arrange controls on a GUI. GroupBox
es and Panel
s 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 GroupBox
es and Panel
s 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 GroupBox
es can display a caption (i.e., text) and do not include scrollbars, whereas Panel
s can include scrollbars and do not include a caption. GroupBox
es have thin borders by default; Panel
s can be set so that they also have borders by changing their BorderStyle
property. Figures 14.21–14.22 list the common properties of GroupBox
es and Panel
s, respectively.
Panel
s and GroupBox
es can contain other Panel
s and GroupBox
es for more complex layouts.
Fig. 14.21. GroupBox
properties.
Fig. 14.22. Panel
properties.
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.
Use Panel
s 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 Button
s. When these Button
s are clicked, their event handlers change the text on a Label
.
Fig. 14.24. Using GroupBox
es and Panel
s to arrange Button
s.
The mainGroupBox
has two Button
s—hiButton
(which displays the text Hi) and bye-Button
(which displays the text Bye). The Panel
(named mainPanel
) also has two Button
s, 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 Button
s are located in lines 18–39. Lines 20, 26, 32 and 38 change the text of messageLabel
to indicate which Button
the user pressed.
CheckBox
es and RadioButton
sC# has two types of state buttons that can be in the on/off or true/false states—CheckBox
es and RadioButton
s. Like class Button
, classes CheckBox
and RadioButton
are derived from class ButtonBase
.
CheckBox
esA 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 CheckBox
es 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.
The program in Fig. 14.26 allows the user to select CheckBox
es 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 CheckBox
es are selected, the font style is set to bold and italic. Initially, neither CheckBox
is checked.
Fig. 14.26. Using CheckBox
es to change font styles.
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 CheckBox
es 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.
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.
RadioButton
sRadio buttons (defined with class RadioButton
) are similar to CheckBox
es in that they also have two states—selected and not selected (also called deselected). However, RadioButton
s 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, RadioButton
s 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).
Use RadioButton
s when the user should choose only one option in a group.
Use CheckBox
es when the user should be able to choose multiple options in a group.
All RadioButton
s added to a container become part of the same group. To divide RadioButton
s into several groups, they must be added to separate containers, such as GroupBox
es or Panel
s. The common properties and a common event of class RadioButton
are listed in Fig. 14.27.
Fig. 14.27. RadioButton
properties and an event.
Form
s, GroupBox
es, and Panel
s can act as logical groups for RadioButton
s. The RadioButton
s within each group are mutually exclusive to each other, but not to RadioButton
s in different logical groups.
The program in Fig. 14.28 uses RadioButton
s 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 RadioButton
s to set message-window options.
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 MessageBox
es for all of the MessageBoxButtons
enumeration values.
We created two GroupBox
es, one for each set of enumeration values. The GroupBox
captions are Button Type and Icon. The GroupBox
es contain RadioButton
s for the corresponding enumeration options, and the RadioButton
s’ Text
properties are set appropriately. Because the RadioButton
s 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 RadioButton
s 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 RadioButton
s. Similarly, when the user checks the RadioButton
s 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.
PictureBox
esA 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.
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.
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:
Images
folder with this chapter’s examples.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.
ToolTip
sIn 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.
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 Label
s, 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.
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.
Fig. 14.34. Setting a control’s tool tip text.
NumericUpDown
ControlAt 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 Button
s 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.
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-Box
es 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.
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.
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.
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
.
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.
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.
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 Label
s that displays the pressed key on one Label
and modifier key information on the other.
Fig. 14.40. Demonstrating keyboard events.
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 Label
s 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
.
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.
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 Label
s, Button
s and TextBox
es. You learned how to use GroupBox
es and Panel
s to organize other controls. We then demonstrated CheckBox
es and RadioButton
s, 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 Form
s.