In this chapter you’ll learn:
• To create menus, tabbed windows and multiple document interface (MDI) programs.
• To use the ListView
and TreeView
controls for displaying information.
• To create hyperlinks using the LinkLabel
control.
• To display lists of information in ListBox
and ComboBox
controls.
• To input date and time data with the DateTimePicker
.
• To create custom controls.
I claim not to have controlled events, but confess plainly that events have controlled me.
—Abraham Lincoln
Capture its reality in paint!
—Paul Cézanne
An actor entering through the door, you’ve got nothing. But if he enters through the window, you’ve got a situation.
—Billy Wilder
But, soft! what light through yonder window breaks?
It is the east, and Juliet is the sun!
—William Shakespeare
15.1 Introduction
15.2 Menus
15.3 MonthCalendar
Control
15.4 DateTimePicker
Control
15.5 LinkLabel
Control
15.6 ListBox
Control
15.7 CheckedListBox
Control
15.8 ComboBox
Control
15.9 TreeView
Control
15.10 ListView
Control
15.11 TabControl
Control
15.12 Multiple Document Interface (MDI) Windows
15.13 Visual Inheritance
15.14 User-Defined Controls
15.15 Wrap-Up
This chapter continues our study of GUIs. We start with menus, which present users with logically organized commands (or options). We show how to develop menus with the tools provided by Visual Studio. Next, we discuss how to input and display dates and times using the MonthCalendar
and DateTimePicker
controls. We also introduce LinkLabel
s—powerful GUI components that enable the user to access one of several destinations, such as a file on the current machine or a web page, by simply clicking the mouse.
We demonstrate how to manipulate a list of values via a ListBox
and how to combine several checkboxes in a CheckedListBox
. We also create drop-down lists using ComboBox
es and display data hierarchically with a TreeView
control. You’ll learn two other important GUI elements—tab controls and multiple document interface (MDI) windows. These components enable you to create real-world programs with sophisticated GUIs.
Visual Studio provides many GUI components, several of which are discussed in this (and the previous) chapter. You can also design custom controls and add them to the ToolBox, as we demonstrate in this chapter’s last example. The techniques presented here form the groundwork for creating more substantial GUIs and custom controls.
Menus provide groups of related commands for Windows applications. Although these commands depend on the program, some—such as Open and Save—are common to many applications. Menus are an integral part of GUIs, because they organize commands without “cluttering” the GUI.
In Fig. 15.1, an expanded menu from the Visual C# IDE lists various commands (called menu items), plus submenus (menus within a menu). The top-level menus appear in the left portion of the figure, whereas any submenus or menu items are displayed to the right. The menu that contains a menu item is called that menu item’s parent menu. A menu item that contains a submenu is considered to be the parent of that submenu.
Fig. 15.1. Menus, submenus and menu items.
Menus can have Alt key shortcuts (also called access shortcuts, keyboard shortcuts or hotkeys), which are accessed by pressing Alt and the underlined letter—for example, Alt F typically expands the File menu. Menu items can have shortcut keys as well (combinations of Ctrl, Shift, Alt, F1, F2, letter keys, and so on). Some menu items display checkmarks, usually indicating that multiple options on the menu can be selected at once.
To create a menu, open the Toolbox and drag a MenuStrip control onto the Form
. This creates a menu bar across the top of the Form
(below the title bar) and places a MenuStrip
icon in the component tray. To select the MenuStrip
, click this icon. You can now use Design mode to create and edit menus for your application. Menus, like other controls, have properties and events, which can be accessed through the Properties window.
To add menu items to the menu, click the Type Here TextBox
(Fig. 15.2) and type the menu item’s name. This action adds an entry to the menu of type ToolStripMenuItem
.
Fig. 15.2. Editing menus in Visual Studio.
After you press the Enter key, the menu item name is added to the menu. Then more Type Here TextBox
es appear, allowing you to add items underneath or to the side of the original menu item (Fig. 15.3).
Fig. 15.3. Adding ToolStripMenuItems
to a MenuStrip
.
To create an access shortcut, type an ampersand (&
) before the character to be underlined. For example, to create the File menu item with the letter F underlined, type &File
. To display an ampersand, type &&
. To add other shortcut keys (e.g., <Ctrl> F9) for menu items, set the ShortcutKeys
property of the appropriate ToolStripMenuItem
s. To do this, select the down arrow to the right of this property in the Properties window. In the window that appears (Fig. 15.4), use the CheckBox
es and drop-down list to select the shortcut keys. When you are finished, click elsewhere on the screen. You can hide the shortcut keys by setting property ShowShortcutKeys
to false
, and you can modify how the shortcut keys are displayed in the menu item by modifying property ShortcutKeyDisplayString
.
Fig. 15.4. Setting a menu item’s shortcut keys.
Buttons can have access shortcuts. Place the & symbol immediately before the desired character in the Button'
s text. To press the button by using its access key in the running application, the user presses Alt and the underlined character. If the underline is not visible when the application runs, press the Alt key to display the underlines.
You can remove a menu item by selecting it with the mouse and pressing the Delete key. Menu items can be grouped logically by separator bars, which are inserted by right clicking the menu and selecting Insert > Separator or by typing “-
” for the text of a menu item.
In addition to text, Visual Studio allows you to easily add TextBox
es and ComboBox
es (drop-down lists) as menu items. When adding an item in Design mode, you may have noticed that before you enter text for a new item, you are provided with a drop-down list. Clicking the down arrow (Fig. 15.5) allows you to select the type of item to add—Menu-Item (of type ToolStripMenuItem
, the default), ComboBox (of type ToolStripComboBox
) and TextBox (of type ToolStripTextBox
). We focus on ToolStripMenuItem
s. [Note: If you view this drop-down list for menu items that are not on the top level, a fourth option appears, allowing you to insert a separator bar.]
Fig. 15.5. Menu-item options.
ToolStripMenuItem
s generate a Click
event when selected. To create an empty Click
event handler, double click the menu item in Design mode. Common actions in response to these events include displaying dialogs and setting properties. Common menu properties and a common event are summarized in Fig. 15.6.
Fig. 15.6. MenuStrip
and ToolStripMenuItem
properties and an event.
It is a convention to place an ellipsis (...) after the name of a menu item (e.g., Save As...) that requires the user to provide more information—typically through a dialog. A menu item that produces an immediate action without prompting the user for more information (e.g., Save) should not have an ellipsis following its name.
Class MenuTestForm
(Fig. 15.7) creates a simple menu on a Form
. The Form
has a top-level File menu with menu items About (which displays a MessageBox
) and Exit (which terminates the program). The program also includes a Format menu, which contains menu items that change the format of the text on a Label
. The Format menu has submenus Color and Font, which change the color and font of the text on a Label
.
Fig. 15.7. Menus for changing text font and color.
To create this GUI, begin by dragging the MenuStrip
from the ToolBox onto the Form
. Then use Design mode to create the menu structure shown in the sample outputs. The File menu (fileToolStripMenuItem
) has menu items About (aboutToolStripMenuItem
) and Exit (exitToolStripMenuItem
); the Format menu (formatToolStripMenuItem
) has two submenus. The first submenu, Color (colorToolStripMenuItem
), contains menu items Black (blackToolStripMenuItem
), Blue (blueToolStripMenuItem
), Red (redToolStripMenuItem
) and Green (greenToolStripMenuItem
). The second submenu, Font (fontToolStripMenuItem
), contains menu items Times New Roman (timesToolStripMenuItem
), Courier (courierToolStripMenuItem
), Comic Sans (comicToolStripMenuItem
), a separator bar (dashToolStripMenuItem
), Bold (boldToolStripMenuItem
) and Italic (italic-ToolStripMenuItem
).
Click
Events for the About and Exit Menu ItemsThe About menu item in the File menu displays a MessageBox
when clicked (lines 20–25). The Exit menu item closes the application through static
method Exit
of class Application
(line 31). Class Application
’s static
methods control program execution. Method Exit
causes our application to terminate.
We made the items in the Color submenu (Black, Blue, Red and Green) mutually exclusive—the user can select only one at a time (we explain how we did this shortly). To indicate that a menu item is selected, we will set each Color menu item’s Checked
property to true
. This causes a check to appear to the left of a menu item.
Each Color menu item has its own Click
event handler. The method handler for color Black is blackToolStripMenuItem_Click
(lines 45–54). Similarly, the event handlers for colors Blue, Red and Green are blueToolStripMenuItem_Click
(lines 57–66), redToolStripMenuItem_Click
(lines 69–78) and greenToolStripMenuItem_Click
(lines 81–90), respectively. Each Color menu item must be mutually exclusive, so each event handler calls method ClearColor
(lines 35–42) before setting its corresponding Checked
property to true
. Method ClearColor
sets the Checked
property of each color ToolStripMenuItem
to false
, effectively preventing more than one menu item from being selected at a time. In the designer, we initially set the Black menu item’s Checked
property to true
, because at the start of the program, the text on the Form
is black.
The mutual exclusion of menu items is not enforced by the MenuStrip, even when the Checked property is true. You must program this behavior.
The Font menu contains three menu items for fonts (Courier, Times New Roman and Comic Sans) and two menu items for font styles (Bold and Italic). We added a separator bar between the font and font-style menu items to indicate that these are separate options. A Font
object can specify only one font at a time but can set multiple styles at once (e.g., a font can be both bold and italic). We set the font menu items to display checks. As with the Color menu, we must enforce mutual exclusion of these items in our event handlers.
Event handlers for font menu items Times New Roman, Courier and Comic Sans are timesToolStripMenuItem_Click
(lines 102–112), courierToolStripMenuItem_Click
(lines 115–125) and comicToolStripMenuItem_Click
(lines 128–138), respectively. These event handlers behave in a manner similar to that of the event handlers for the Color menu items. Each event handler clears the Checked
properties for all font menu items by calling method ClearFont
(lines 93–99), then sets the Checked
property of the menu item that raised the event to true
. This enforces the mutual exclusion of the font menu items. In the designer, we initially set the Times New Roman menu item’s Checked
property to true
, because this is the original font for the text on the Form
. The event handlers for the Bold and Italic menu items (lines 141–163) use the bitwise logical exclusive OR (^
) operator to combine font styles, as we discussed in Chapter 14.
MonthCalendar
ControlMany applications must perform date and time calculations. The .NET Framework provides two controls that allow an application to retrieve date and time information—the MonthCalendar
and DateTimePicker
(Section 15.4) controls.
The MonthCalendar
(Fig. 15.8) control displays a monthly calendar on the Form
. The user can select a date from the currently displayed month or can use the provided arrows to navigate to another month. When a date is selected, it is highlighted. Multiple dates can be selected by clicking dates on the calendar while holding down the Shift key. The default event for this control is the DateChanged
event, which is generated when a new date is selected. Properties are provided that allow you to modify the appearance of the calendar, how many dates can be selected at once, and the minimum date and maximum date that may be selected. MonthCalendar
properties and a common MonthCalendar
event are summarized in Fig. 15.9.
Fig. 15.8. MonthCalendar
control.
Fig. 15.9. MonthCalendar
properties and an event.
DateTimePicker
ControlThe DateTimePicker
control (see output of Fig. 15.11) is similar to the MonthCalendar
control but displays the calendar when a down arrow is selected. The DateTimePicker
can be used to retrieve date and time information from the user. A DateTimePicker
’s Value
property stores a DateTime
object, which always contains both date and time information. You can retrieve the date information from the DateTime
object by using property Date
, and you can retrieve only the time information by using the TimeOfDay
property.
The DateTimePicker
is also more customizable than a MonthCalendar
control—more properties are provided to edit the look and feel of the drop-down calendar. Property Format
specifies the user’s selection options using the DateTimePickerFormat
enumeration. The values in this enumeration are Long
(displays the date in long format, as in Thursday, July 10, 2010), Short
(displays the date in short format, as in 7/10/2010), Time
(displays a time value, as in 5:31:02 PM) and Custom
(indicates that a custom format will be used). If value Custom
is used, the display in the DateTimePicker
is specified using property CustomFormat
. The default event for this control is ValueChanged
, which occurs when the selected value (whether a date or a time) is changed. DateTimePicker
properties and a common event are summarized in Fig. 15.10.
Fig. 15.10. DateTimePicker
properties and an event.
Figure 15.11 demonstrates using a DateTimePicker
to select an item’s drop-off time. Many companies use such functionality—several online DVD rental companies specify the day a movie is sent out and the estimated time that it will arrive at your home. The user selects a drop-off day, then an estimated arrival date is displayed. The date is always two days after drop-off, three days if a Sunday is reached (mail is not delivered on Sunday).
Fig. 15.11. Demonstrating DateTimePicker
.
The DateTimePicker
(dropOffDateTimePicker
) has its Format
property set to Long
, so the user can select a date and not a time in this application. When the user selects a date, the ValueChanged
event occurs. The event handler for this event (lines 18–35) first retrieves the selected date from the DateTimePicker
’s Value
property (line 21). Lines 24–26 use the DateTime
structure’s DayOfWeek
property to determine the day of the week on which the selected date falls. The day values are represented using the DayOfWeek
enumeration. Lines 29–30 and 33–34 use DateTime
’s AddDays
method to increase the date by three days or two days, respectively. The resulting date is then displayed in Long
format using method ToLongDateString
.
In this application, we do not want the user to be able to select a drop-off day before the current day, or one that is more than a year into the future. To enforce this, we set the DateTimePicker
’s MinDate
and MaxDate
properties when the Form
is loaded (lines 40 and 43). Property Today
returns the current day, and method AddYears
(with an argument of 1) is used to specify a date one year in the future.
Let’s take a closer look at the output. This application begins by displaying the current date (Fig. 15.11(a)). In Fig. 15.11(b), we selected the 30th of July. In Fig. 15.11(c), the estimated arrival date is displayed as the 2nd of August. Figure 15.11(d) shows that the 30th, after it is selected, is highlighted in the calendar.
LinkLabel
ControlThe LinkLabel
control displays links to other resources, such as files or web pages (Fig. 15.12). A LinkLabel
appears as underlined text (colored blue by default). When the mouse moves over the link, the pointer changes to a hand; this is similar to the behavior of a hyperlink in a web page. The link can change color to indicate whether it is not yet visited, previously visited or active. When clicked, the LinkLabel
generates a LinkClicked
event (see Fig. 15.13). Class LinkLabel
is derived from class Label
and therefore inherits all of class Label
’s functionality.
Fig. 15.12. LinkLabel
control in running program.
A LinkLabel is the preferred control for indicating that the user can click a link to jump to a resource such as a web page, though other controls can perform similar tasks.
Fig. 15.13. LinkLabel
properties and an event.
Class LinkLabelTestForm
(Fig. 15.14) uses three LinkLabel
s to link to the C:
drive, the Deitel website (www.deitel.com) and the Notepad application, respectively. The Text
properties of the LinkLabel
’s cDriveLinkLabel
, deitelLinkLabel
and notepadLink-Label
describe each link’s purpose.
Fig. 15.14. LinkLabel
s used to link to a drive, a web page and an application.
The event handlers for the LinkLabel
s call method Start
of class Process
(namespace System.Diagnostics
), which allows you to execute other programs, or load documents or web sites from an application. Method Start
can take one argument, the file to open, or two arguments, the application to run and its command-line arguments. Method Start
’s arguments can be in the same form as if they were provided for input to the Windows Run command (Start > Run...). For applications that are known to Windows, full path names are not needed, and the file extension often can be omitted. To open a file of a type that Windows recognizes (and knows how to handle), simply use the file’s full path name. For example, if you a pass the method a .doc
file, Windows will open it in Microsoft Word (or whatever program is registered to open .doc files, if any). The Windows operating system must be able to use the application associated with the given file’s extension to open the file.
The event handler for cDriveLinkLabel
’s LinkClicked
event browses the C:
drive (lines 19–26). Line 23 sets the LinkVisited
property to true
, which changes the link’s color from blue to purple (the LinkVisited
colors can be configured through the Properties window in Visual Studio). The event handler then passes @"C:"
to method Start
(line 25), which opens a Windows Explorer window. The @
symbol that we placed before "C:"
indicates that all characters in the string
should be interpreted literally—this is known as a verbatim string
. Thus, the backslash within the string
is not considered to be the first character of an escape sequence. This simplifies string
s that represent directory paths, since you do not need to use \
for each character in the path.
The event handler for deitelLinkLabel
’s LinkClicked
event (lines 29–36) opens the web page www.deitel.com in the user’s default web browser. We achieve this by passing the web-page address as a string
(line 35), which opens the web page in a new web browser window or tab. Line 33 sets the LinkVisited
property to true
.
The event handler for notepadLinkLabel'
s LinkClicked
event (lines 39–48) opens the Notepad application. Line 43 sets the LinkVisited
property to true
so that the link appears as a visited link. Line 47 passes the argument "notepad"
to method Start
, which runs notepad.exe
. In line 47, neither the full path nor the .exe
extension is required—Windows automatically recognizes the argument given to method Start
as an executable file.
ListBox
ControlThe ListBox
control allows the user to view and select from multiple items in a list. List-Box
es are static GUI entities, which means that users cannot directly edit the list of items. The user can be provided with TextBox
es and Button
s with which to specify items to be added to the list, but the actual additions must be performed in code. The CheckedListBox
control (Section 15.7) extends a ListBox
by including CheckBox
es next to each item in the list. This allows users to place checks on multiple items at once, as is possible with CheckBox
controls. (Users also can select multiple items from a ListBox
by setting the ListBox
’s SelectionMode
property, which is discussed shortly.) Figure 15.15 displays a ListBox
and a CheckedListBox
. In both controls, scrollbars appear if the number of items exceeds the ListBox
’s viewable area.
Fig. 15.15. ListBox
and CheckedListBox
on a Form
.
Figure 15.16 lists common ListBox
properties and methods and a common event. The SelectionMode
property determines the number of items that can be selected. This property has the possible values None
, One
, MultiSimple
and MultiExtended
(from the SelectionMode
enumeration)—the differences among these settings are explained in Fig. 15.16. The SelectedIndexChanged
event occurs when the user selects a new item.
Fig. 15.16. ListBox
properties, methods and an event.
Both the ListBox
and CheckedListBox
have properties Items
, SelectedItem
and SelectedIndex
. Property Items
returns a collection of the list items. Collections are a common way to manage lists of object
s in the .NET framework. Many .NET GUI components (e.g., ListBox
es) use collections to expose lists of internal objects (e.g., items in a ListBox
). We discuss collections further in Chapter 23. The collection returned by property Items
is represented as an object of type ListBox.ObjectCollection
. Property Selected-Item
returns the ListBox
’s currently selected item. If the user can select multiple items, use collection SelectedItems
to return all the selected items as a ListBox.SelectedObjectColection
. Property SelectedIndex
returns the index of the selected item—if there could be more than one, use property SelectedIndices
, which returns a ListBox.SelectedIndexColection
. If no items are selected, property SelectedIndex
returns -1
. Method GetSelected
takes an index and returns true
if the corresponding item is selected.
ListBox
es and CheckedListBox
esTo add items to a ListBox
or to a CheckedListBox
, we must add objects to its Items
collection. This can be accomplished by calling method Add
to add a string
to the ListBox
’s or CheckedListBox
’s Items
collection. For example, we could write
myListBox.Items
.Add( myListItem );
to add string
myListItem to ListBox
myListBox. To add multiple objects, you can either call method Add
multiple times or call method AddRange
to add an array of objects. Classes ListBox
and CheckedListBox
each call the submitted object’s ToString
method to determine the Label
for the corresponding object’s entry in the list. This allows you to add different objects to a ListBox
or a CheckedListBox
that later can be returned through properties SelectedItem
and SelectedItems
.
Alternatively, you can add items to ListBox
es and CheckedListBox
es visually by examining the Items
property in the Properties window. Clicking the ellipsis button opens the String Collection Editor, which contains a text area for adding items; each item appears on a separate line (Fig. 15.17). Visual Studio then writes code to add these string
s to the Items
collection inside method InitializeComponent
.
Fig. 15.17. String Collection Editor.
Figure 15.18 uses class ListBoxTestForm
to add, remove and clear items from ListBox displayListBox
. Class ListBoxTestForm
uses TextBox inputTextBox
to allow the user to type in a new item. When the user clicks the Add Button
, the new item appears in displayListBox
. Similarly, if the user selects an item and clicks Remove, the item is deleted. When clicked, Clear deletes all entries in displayListBox
. The user terminates the application by clicking Exit.
Fig. 15.18. Program that adds, removes and clears ListBox
items.
The addButton_Click
event handler (lines 20–24) calls method Add
of the Items
collection in the ListBox
. This method takes a string
as the item to add to displayListBox
. In this case, the string
used is the user input from the inputTextBox
(line 22). After the item is added, inputTextBox.Text
is cleared (line 23).
The removeButton_Click
event handler (lines 27–33) uses method RemoveAt
to remove an item from the ListBox
. Event handler removeButton_Click
first uses property SelectedIndex
to determine which index is selected. If SelectedIndex
is not –1
(i.e., an item is selected), lines 31–32 remove the item that corresponds to the selected index.
The clearButton_Click
event handler (lines 36–39) calls method Clear
of the Items
collection (line 38). This removes all the entries in displayListBox
. Finally, event handler exitButton_Click
(lines 42–45) terminates the application by calling method Application.Exit
(line 44).
CheckedListBox
ControlThe CheckedListBox
control derives from ListBox
and displays a CheckBox
with each item. Items can be added via methods Add
and AddRange
or through the String Collection Editor. CheckedListBox
es allow multiple items to be checked, but item selection is more restrictive. The only values for the SelectionMode
property are None
and One
. One
allows a single selection, whereas None
allows no selections. Because an item must be selected to be checked, you must set the SelectionMode
to be One
if you wish to allow users to check items. Thus, toggling property SelectionMode
between One
and None
effectively switches between enabling and disabling the user’s ability to check list items. Common properties, a method and an event of CheckedListBox
es appear in Fig. 15.19.
Fig. 15.19. CheckedListBox
properties, a method and an event.
The IDE displays an error message if you attempt to set the SelectionMode
property to MultiSimple
or MultiExtended
in the Properties window of a CheckedListBox
. If this value is set programmatically, a runtime error occurs.
Event ItemCheck
occurs whenever a user checks or unchecks a CheckedListBox
item. Event-argument properties CurrentValue
and NewValue
return CheckState
values for the current and new state of the item, respectively. A comparison of these values allows you to determine whether the CheckedListBox
item was checked or unchecked. The CheckedListBox
control retains the SelectedItems
and SelectedIndices
properties (it inherits them from class ListBox
). However, it also includes properties CheckedItems
and CheckedIndices
, which return information about the checked items and indices.
In Fig. 15.20, class CheckedListBoxTestForm
uses a CheckedListBox
and a ListBox
to display a user’s selection of books. The CheckedListBox
allows the user to select multiple titles. In the String Collection Editor, items were added for some Deitel books: C, C++, Java™, Internet & WWW, VB 2008, Visual C++ and Visual C# 2008 (the acronym HTP stands for “How to Program”). The ListBox
(named displayListBox
) displays the user’s selection. In the screenshots accompanying this example, the CheckedListBox
appears to the left, the ListBox
on the right.
Fig. 15.20. CheckedListBox
and ListBox
used in a program to display a user selection.
When the user checks or unchecks an item in itemCheckedListBox_ItemCheck
, an ItemCheck
event occurs and event handler itemCheckedListBox_ItemCheck
(lines 19–31) executes. An if
...else
statement (lines 27–30) determines whether the user checked or unchecked an item in the CheckedListBox
. Line 27 uses the NewValue
property to determine whether the item is being checked (CheckState.Checked
). If the user checks an item, line 28 adds the checked entry to the ListBox displayListBox
. If the user unchecks an item, line 30 removes the corresponding item from displayListBox
. This event handler was created by selecting the CheckedListBox
in Design mode, viewing the control’s events in the Properties window and double clicking the ItemCheck
event. The default event for a CheckedListBox
is a SelectedIndexChanged
event.
ComboBox
ControlThe ComboBox
control combines TextBox
features with a drop-down list—a GUI component that contains a list from which a value can be selected. A ComboBox
usually appears as a TextBox
with a down arrow to its right. By default, the user can enter text into the Text-Box
or click the down arrow to display a list of predefined items. If a user chooses an element from this list, that element is displayed in the TextBox
. If the list contains more elements than can be displayed in the drop-down list, a scrollbar appears. The maximum number of items that a drop-down list can display at one time is set by property MaxDropDownItems
. Figure 15.21 shows a sample ComboBox
in three different states.
Fig. 15.21. ComboBox
demonstration.
As with the ListBox
control, you can add objects to collection Items
programmatically, using methods Add
and AddRange
, or visually, with the String Collection Editor. Figure 15.22 lists common properties and a common event of class ComboBox
.
Use a ComboBox to save space on a GUI. A disadvantage is that, unlike with a ListBox, the user cannot see available items without expanding the drop-down list.
Fig. 15.22. ComboBox
properties and an event.
Property DropDownStyle
determines the type of ComboBox
and is represented as a value of the ComboBoxStyle
enumeration, which contains values Simple
, DropDown
and DropDownList
. Option Simple
does not display a drop-down arrow. Instead, a scrollbar appears next to the control, allowing the user to select a choice from the list. The user also can type in a selection. Style DropDown
(the default) displays a drop-down list when the down arrow is clicked (or the down arrow key is pressed). The user can type a new item in the ComboBox
. The last style is DropDownList
, which displays a drop-down list but does not allow the user to type in the TextBox
.
The ComboBox
control has properties Items
(a collection), SelectedItem
and SelectedIndex
, which are similar to the corresponding properties in ListBox
. There can be at most one selected item in a ComboBox
. If no items are selected, then SelectedIndex
is -1
. When the selected item changes, a SelectedIndexChanged
event occurs.
Class ComboBoxTestForm
(Fig. 15.23) allows users to select a shape to draw—circle, ellipse, square or pie (in both filled and unfilled versions)—by using a ComboBox
. The ComboBox
in this example is uneditable, so the user cannot type in the TextBox
.
Make lists (such as ComboBoxes) editable only if the program is designed to accept user-submitted elements. Otherwise, the user might try to enter a custom item that is improper for the purposes of your application.
Fig. 15.23. ComboBox
used to draw a selected shape.
After creating ComboBox imageComboBox
, make it uneditable by setting its DropDown-Style
to DropDownList
in the Properties window. Next, add items Circle
, Square
, Ellipse
, Pie
, Filled Circle
, Filled Square
, Filled Ellipse
and Filled Pie
to the Items
collection using the String Collection Editor. Whenever the user selects an item from imageComboBox
, a SelectedIndexChanged
event occurs and event handler imageComboBox_SelectedIndexChanged
(lines 19–66) executes. Lines 23–29 create a Graphics
object, a Pen
and a SolidBrush
, which are used to draw on the Form
. The Graphics
object (line 23) allows a pen or brush to draw on a component, using one of several Graphics
methods. The Pen
object (line 26) is used by methods DrawEllipse
, DrawRectangle
and DrawPie
(lines 38, 41, 44 and 47) to draw the outlines of their corresponding shapes. The SolidBrush
object (line 29) is used by methods FillEllipse
, FillRectangle
and FillPie
(lines 50, 53–54, 57 and 60–61) to fill their corresponding solid shapes. Line 32 colors the entire Form White
, using Graphics
method Clear
.
The application draws a shape based on the selected item’s index. The switch
statement (lines 35–63) uses imageComboBox.SelectedIndex
to determine which item the user selected. Graphics
method DrawEllipse
(line 38) takes a Pen
, and the x- and y-coordinates of the upper-left corner, the width and height of the bounding box in which the ellipse will be displayed. The origin of the coordinate system is in the upper-left corner of the Form
; the x-coordinate increases to the right, and the y-coordinate increases downward. A circle is a special case of an ellipse (with the width and height equal). Line 38 draws a circle. Line 44 draws an ellipse that has different values for width and height.
Class Graphics
method DrawRectangle
(line 41) takes a Pen
, the x- and y-coordinates of the upper-left corner and the width and height of the rectangle to draw. Method DrawPie
(line 47) draws a pie as a portion of an ellipse. The ellipse is bounded by a rectangle. Method DrawPie
takes a Pen
, the x- and y-coordinates of the upper-left corner of the rectangle, its width and height, the start angle (in degrees) and the sweep angle (in degrees) of the pie. Angles increase clockwise. The FillEllipse
(lines 50 and 57), Fill-Rectangle
(line 53–54) and FillPie
(line 60–61) methods are similar to their unfilled counterparts, except that they take a Brush
(e.g., SolidBrush
) instead of a Pen
. Some of the drawn shapes are illustrated in the screenshots of Fig. 15.23.
TreeView
ControlThe TreeView
control displays nodes hierarchically in a tree. Traditionally, nodes are objects that contain values and can refer to other nodes. A parent node contains child nodes, and the child nodes can be parents to other nodes. Two child nodes that have the same parent node are considered sibling nodes. A tree is a collection of nodes, usually organized in a hierarchical manner. The first parent node of a tree is the root node (a TreeView
can have multiple roots). For example, the file system of a computer can be represented as a tree. The top-level directory (perhaps C:
) would be the root, each subfolder of C:
would be a child node and each child folder could have its own children. TreeView
controls are useful for displaying hierarchical information, such as the file structure that we just mentioned. We cover nodes and trees in greater detail in Chapter 21, Data Structures. Figure 15.24 displays a sample TreeView
control on a Form
.
Fig. 15.24. TreeView
displaying a sample tree.
A parent node can be expanded or collapsed by clicking the plus box or minus box to its left. Nodes without children do not have these boxes.
The nodes in a TreeView
are instances of class TreeNode
. Each TreeNode
has a Nodes collection
(type TreeNodeCollection
), which contains a list of other TreeNode
s—known as its children. The Parent
property returns a reference to the parent node (or null
if the node is a root node). Figure 15.25 and Fig. 15.26 list the common properties of TreeView
s and TreeNode
s, common TreeNode
methods and a common TreeView
event.
Fig. 15.25. TreeView
properties and an event.
Fig. 15.26. TreeNode
properties and methods.
To add nodes to the TreeView
visually, click the ellipsis next to the Nodes
property in the Properties window. This opens the TreeNode Editor (Fig. 15.27), which displays an empty tree representing the TreeView
. There are Button
s to create a root and to add or delete a node. To the right are the properties of current node. Here you can rename the node.
Fig. 15.27. TreeNode Editor.
To add nodes programmatically, first create a root node. Create a new TreeNode
object and pass it a string
to display. Then call method Add
to add this new TreeNode
to the TreeView
’s Nodes
collection. Thus, to add a root node to TreeView
myTreeView, write
myTreeView.Nodes.Add( new TreeNode( rootLabel ) );
where myTreeView is the TreeView
to which we are adding nodes, and rootLabel is the text to display in myTreeView. To add children to a root node, add new TreeNode
s to its Nodes
collection. We select the appropriate root node from the TreeView
by writing
myTreeView.Nodes[ myIndex ]
where myIndex is the root node’s index in myTreeView’s Nodes
collection. We add nodes to child nodes through the same process by which we added root nodes to myTreeView. To add a child to the root node at index myIndex, write
myTreeView.Nodes[ myIndex ].Nodes.Add( new TreeNode( ChildLabel ) );
Class TreeViewDirectoryStructureForm
(Fig. 15.28) uses a TreeView
to display the contents of a directory chosen by the user. A TextBox
and a Button
are used to specify the directory. First, enter the full path of the directory you want to display. Then click the Button
to set the specified directory as the root node in the TreeView
. Each subdirectory of this directory becomes a child node. This layout is similar to that used in Windows Explorer. Folders can be expanded or collapsed by clicking the plus or minus boxes that appear to their left.
Fig. 15.28. TreeView
used to display directories.
When the user clicks the enterButton
, all the nodes in directoryTreeView
are cleared (line 68). Then, if the directory exists (line 73), the path entered in inputTextBox
is used to create the root node. Line 76 adds the directory to directoryTreeView
as the root node, and lines 79–80 call method PopulateTreeView
(lines 21–62), which takes a directory (a string
) and a parent node. Method PopulateTreeView
then creates child nodes corresponding to the subdirectories of the directory it receives as an argument.
Method PopulateTreeView
(lines 21–62) obtains a list of subdirectories, using method GetDirectories
of class Directory
(namespace System.IO
) in lines 25–26. Method GetDirectories
takes a string
(the current directory) and returns an array of string
s (the subdirectories). If a directory is not accessible for security reasons, an UnauthorizedAccessException
is thrown. Lines 58–61 catch this exception and add a node containing “Access denied
” instead of displaying the subdirectories.
If there are accessible subdirectories, lines 42–43 use method GetFileNameWithout-Extension
of class Path
to increase readability by shortening the full path name to just the directory name. The Path
class provides functionality for working with string
s that are file or directory paths. Next, each string
in the directoryArray
is used to create a new child node (line 46). We use method Add
(line 49) to add each child node to the parent. Then method PopulateTreeView
is called recursively on every subdirectory (line 52), which eventually populates the TreeView
with the entire directory structure. Our recursive algorithm may cause a delay when the program loads large directories. However, once the folder names are added to the appropriate Nodes
collection, they can be expanded and collapsed without delay. In the next section, we present an alternate algorithm to solve this problem.
ListView
ControlThe ListView
control is similar to a ListBox
in that both display lists from which the user can select one or more items (an example of a ListView
can be found in Fig. 15.31). List-View
is more versatile and can display items in different formats. For example, a ListView
can display icons next to the list items (controlled by its SmallImageList
, LargeImageList
or StateImageList
properties) and show the details of items in columns. Property Multi-Select
(a bool
) determines whether multiple items can be selected. CheckBox
es can be included by setting property CheckBoxes
(a bool
) to true
, making the ListView
’s appearance similar to that of a CheckedListBox
. The View
property specifies the layout of the ListBox
. Property Activation
determines the method by which the user selects a list item. The details of these properties and the ItemActivate
event are explained in Fig. 15.29.
Fig. 15.29. ListView
properties and events.
ListView
allows you to define the images used as icons for ListView
items. To display images, an ImageList
component is required. Create one by dragging it to a Form
from the ToolBox. Then, select the Images
property in the Properties window to display the Image Collection Editor (Fig. 15.30). Here you can browse for images that you wish to add to the ImageList
, which contains an array of Image
s. Adding images this way embeds them into the application (like resources), so they do not need to be included separately with the published application. They’re not however part of the project. In this example, we added images to the ImageList
programmatically rather than using the Image Collection Editor so that we could use image resources. After creating an empty ImageList
, add the file and folder icon images to the project as resources. Next, set property SmallImage-List
of the ListView
to the new ImageList
object. Property SmallImageList
specifies the image list for the small icons. Property LargeImageList
sets the ImageList
for large icons. The items in a ListView
are each of type ListViewItem
. Icons for the ListView
items are selected by setting the item’s ImageIndex
property to the appropriate index.
Fig. 15.30. Image Collection Editor window for an ImageList
component.
Class ListViewTestForm
(Fig. 15.31) displays files and folders in a ListView
, along with small icons representing each file or folder. If a file or folder is inaccessible because of permission settings, a MessageBox
appears. The program scans the contents of the directory as it browses, rather than indexing the entire drive at once.
Fig. 15.31. ListView
displaying files and folders.
ListViewTestForm_Load
Method ListViewTestForm_Load
(lines 114–123) handles the Form
’s Load
event. When the application loads, the folder and file icon images are added to the Images
collection of fileFolderImageList
(lines 117–118). Since the ListView
’s SmallImageList
property is set to this ImageList
, the ListView
can display these images as icons for each item. Because the folder icon was added first, it has array index 0
, and the file icon has array index 1. The application also loads its home directory (obtained at line 14) into the ListView
when it first loads (line 121) and displays the directory path (line 122).
LoadFilesInDirectory
The LoadFilesInDirectory
method (lines 64–111) populates browserListView
with the directory passed to it (currentDirectoryValue
). It clears browserListView
and adds the element "Go Up One Level"
. When the user clicks this element, the program attempts to move up one level (we see how shortly). The method then creates a DirectoryInfo
object initialized with the string currentDirectory
(lines 75–76). If permission is not given to browse the directory, an exception is thrown (and caught in line 105). Method Load-FilesInDirectory
works differently from method PopulateTreeView
in the previous program (Fig. 15.28). Instead of loading all the folders on the hard drive, method Load-FilesInDirectory
loads only the folders in the current directory.
Class DirectoryInfo
(namespace System.IO
) enables us to browse or manipulate the directory structure easily. Method GetDirectories
(line 80) returns an array of DirectoryInfo
objects containing the subdirectories of the current directory. Similarly, method GetFiles
(line 81) returns an array of class FileInfo
objects containing the files in the current directory. Property Name
(of both class DirectoryInfo
and class FileInfo
) contains only the directory or file name, such as temp
instead of C:myfolder emp
. To access the full name, use property FullName
.
Lines 84–91 and lines 94–101 iterate through the subdirectories and files of the current directory and add them to browserListView
. Lines 90 and 100 set the ImageIndex
properties of the newly created items. If an item is a directory, we set its icon to a directory icon (index 0); if an item is a file, we set its icon to a file icon (index 1).
browserListView_Click
Method browserListView_Click
(lines 23–61) responds when the user clicks control browserListView
. Line 26 checks whether anything is selected. If a selection has been made, line 29 determines whether the user chose the first item in browserListView
. The first item in browserListView
is always Go Up One Level; if it is selected, the program attempts to go up a level. Lines 32–33 create a DirectoryInfo
object for the current directory. Line 36 tests property Parent
to ensure that the user is not at the root of the directory tree. Property Parent
indicates the parent directory as a DirectoryInfo
object; if no parent directory exists, Parent
returns the value null
. If a parent directory does exist, lines 38–39 pass the parent directory’s full name to LoadFilesInDirectory
.
If the user did not select the first item in browserListView
, lines 44–56 allow the user to continue navigating through the directory structure. Line 47 creates string chosen
and assigns it the text of the selected item (the first item in collection SelectedItems
). Lines 50–51 determine whether the user selected a valid directory (rather than a file). Using the Combine
method of class Path
, the program combines string
s currentDirectory
and chosen
to form the new directory path. The Combine
method automatically adds a backslash (), if necessary, between the two pieces. This value is passed to the
Exists
method of class Directory
. Method Exists
returns true
if its string
parameter is a valid directory. If so, the program passes the string
to method LoadFilesInDirectory
(lines 53–54). Finally, displayLabel
is updated with the new directory (line 59).
This program loads quickly, because it indexes only the files in the current directory. A small delay may occur when a new directory is loaded. In addition, changes in the directory structure can be shown by reloading a directory. The previous program (Fig. 15.28) may have a large initial delay, as it loads an entire directory structure. This type of tradeoff is typical in the software world.
When designing applications that run for long periods of time, you might choose a large initial delay to improve performance throughout the rest of the program. However, in applications that run for only short periods, developers often prefer fast initial loading times and small delays after each action.
TabControl
ControlThe TabControl
creates tabbed windows, such as those in Visual Studio (Fig. 15.32). This enables you to specify more information in the same space on a Form
and group displayed data logically. TabControl
s contain TabPage
objects, which are similar to Panel
s and GroupBox
es in that TabPage
s also can contain controls. You first add controls to the Tab-Page
objects, then add the TabPage
s to the TabControl
. Only one TabPage
is displayed at a time. To add objects to the TabPage
and the TabControl
, write
myTabPage.Controls.Add( myControl );
myTabControl.TabPages.Add( myTabPage );
Fig. 15.32. Tabbed windows in Visual Studio.
The preceding statements call method Add
of the Controls
collection and method Add
of the TabPages
collection. The example adds TabControl
myControl to TabPage
myTab-Page, then adds myTabPage to myTabControl. Alternatively, we can use method AddRange
to add an array of TabPage
s or controls to a TabControl
or TabPage
, respectively. Figure 15.33 depicts a sample TabControl
.
Fig. 15.33. TabControl
with TabPages
example.
You can add TabControl
s visually by dragging and dropping them onto a Form
in Design mode. To add TabPage
s in Design mode, right click the TabControl
and select Add Tab (Fig. 15.34). Alternatively, click the TabPages
property in the Properties window and add tabs in the dialog that appears. To change a tab label, set the Text
property of the TabPage
. Clicking the tabs selects the TabControl
—to select the TabPage
, click the control area underneath the tabs. You can add controls to the TabPage
by dragging and dropping items from the ToolBox. To view different TabPage
s, click the appropriate tab (in either design or run mode). Common properties and a common event of TabControl
s are described in Fig. 15.35.
Fig. 15.34. TabPage
s added to a TabControl
.
Fig. 15.35. TabControl
properties and an event.
Each TabPage
generates a Click
event when its tab is clicked. Event handlers for this event can be created by double clicking the body of the TabPage
.
Class UsingTabsForm
(Fig. 15.36) uses a TabControl
to display various options relating to the text on a label (Color, Size and Message). The last TabPage
displays an About message, which describes the use of TabControl
s.
Fig. 15.36. TabControl
used to display various font settings.
The textOptionsTabControl
and the colorTabPage
, sizeTabPage
, messageTabPage
and aboutTabPage
are created in the designer (as described previously). The colorTabPage
contains three RadioButton
s for the colors black (blackRadioButton
), red (redRadioButton
) and green (greenRadioButton
). This TabPage
is displayed in Fig. 15.36(a). The CheckedChanged
event handler for each RadioButton
updates the color of the text in displayLabel
(lines 22, 29 and 36). The sizeTabPage
(Fig. 15.36(b)) has three RadioButton
s, corresponding to font sizes 12 (size12RadioButton
), 16 (size16RadioButton
) and 20 (size20RadioButton
), which change the font size of displayLabel
—lines 44, 52 and 60, respectively. The messageTabPage
(Fig. 15.36(c)) contains two RadioButton
s for the messages Hello! (helloRadioButton
) and Goodbye! (goodbyeRadioButton
). The two RadioButton
s determine the text on displayLabel
(lines 67 and 74, respectively). The aboutTabPage
(Fig. 15.36(d)) contains a Label
(messageLabel
) describing the purpose of TabControl
s.
A TabPage can act as a container for a single logical group of RadioButtons, enforcing their mutual exclusivity. To place multiple RadioButton groups inside a single TabPage, you should group RadioButtons within Panels or GroupBoxes contained within the TabPage.
In previous chapters, we have built only single document interface (SDI) applications. Such programs (including Microsoft’s Notepad and Paint) can support only one open window or document at a time. SDI applications usually have limited abilities—Paint and Notepad, for example, have limited image- and text-editing features. To edit multiple documents, the user must execute another instance of the SDI application.
Many complex applications are multiple document interface (MDI) programs, which allow users to edit multiple documents at once (e.g., Microsoft Office products). MDI programs also tend to be more complex—Paint Shop Pro and Photoshop have a greater number of image-editing features than does Paint.
An MDI program’s main window is called the parent window, and each window inside the application is referred to as a child window. Although an MDI application can have many child windows, each has only one parent window. Furthermore, a maximum of one child window can be active at once. Child windows cannot be parents themselves and cannot be moved outside their parent. Otherwise, a child window behaves like any other window (with regard to closing, minimizing, resizing, and so on). A child window’s functionality can differ from that of other child windows of the parent. For example, one child window might allow the user to edit images, another might allow the user to edit text and a third might display network traffic graphically, but all could belong to the same MDI parent. Figure 15.37 depicts a sample MDI application with two child windows.
Fig. 15.37. MDI parent window and MDI child windows.
To create an MDI Form
, create a new Form
and set its IsMdiContainer
property to true
. The Form
changes appearance, as in Fig. 15.38. Next, create a child Form
class to be added to the Form
. To do this, right click the project in the Solution Explorer, select Project > Add Windows Form... and name the file. Edit the Form
as you like. To add the child Form
to the parent, we must create a new child Form
object, set its MdiParent
property to the parent Form
and call the child Form
’s Show
method. In general, to add a child Form
to a parent, write
Fig. 15.38. SDI and MDI forms.
ChildFormClass childForm = New ChildFormClass();
childForm.MdiParent = parentForm;
childForm.Show();
In most cases, the parent Form
creates the child, so the parentForm reference is this
. The code to create a child usually lies inside an event handler, which creates a new window in response to a user action. Menu selections (such as File, followed by a submenu option of New, followed by a submenu option of Window) are common techniques for creating new child windows.
Class Form
property MdiChildren
returns an array of child Form
references. This is useful if the parent window wants to check the status of all its children (for example, ensuring that all are saved before the parent closes). Property ActiveMdiChild
returns a reference to the active child window; it returns null
if there are no active child windows. Other features of MDI windows are described in Fig. 15.39.
Fig. 15.39. MDI parent and MDI child properties, a method and an event.
Child windows can be minimized, maximized and closed independently of the parent window. Figure 15.40 shows two images: one containing two minimized child windows and a second containing a maximized child window. When the parent is minimized or closed, the child windows are minimized or closed as well. Notice that the title bar in Fig. 15.40(b) is Form1 - [Child1]. When a child window is maximized, its title-bar text is inserted into the parent window’s title bar. When a child window is minimized or maximized, its title bar displays a restore icon, which can be used to return the child window to its previous size (its size before it was minimized or maximized).
Fig. 15.40. Minimized and maximized child windows.
C# provides a property that helps track which child windows are open in an MDI container. Property MdiWindowListItem
of class MenuStrip
specifies which menu, if any, displays a list of open child windows that the user can select to bring the corresponding window to the foreground. When a new child window is opened, an entry is added to the end of the list (Fig. 15.41). If ten or more child windows are open, the list includes the option More Windows..., which allows the user to select a window from a list in a dialog.
When creating MDI applications, include a menu that displays a list of the open child windows. This helps the user select a child window quickly, rather than having to search for it in the parent window.
Fig. 15.41. MenuStrip
property MdiWindowListItem
example.
MDI containers allow you to organize the placement of its child windows. The child windows in an MDI application can be arranged by calling method LayoutMdi
of the parent Form
. Method LayoutMdi
takes an MdiLayout
enumeration, which can have values Arrange-Icons
, Cascade
, TileHorizontal
and TileVertical
. Tiled windows completely fill the parent and do not overlap; such windows can be arranged horizontally (value TileHorizontal
) or vertically (value TileVertical
). Cascaded windows (value Cascade
) overlap—each is the same size and displays a visible title bar, if possible. Value ArrangeIcons
arranges the icons for any minimized child windows. If minimized windows are scattered around the parent window, value ArrangeIcons
orders them neatly at the bottom-left corner of the parent window. Figure 15.42 illustrates the values of the MdiLayout
enumeration.
Fig. 15.42. MdiLayout
enumeration values.
Class UsingMDIForm
(Fig. 15.43) demonstrates MDI windows. Class UsingMDIForm
uses three instances of child Form ChildForm
(Fig. 15.44), each containing a PictureBox
that displays an image. The parent MDI Form
contains a menu enabling users to create and arrange child Form
s.
Form
Figure 15.43 presents class UsingMDIForm
—the application’s MDI parent Form
. This Form
, which is created first, contains two top-level menus. The first of these menus, File (fileToolStripMenuItem
), contains both an Exit item (exitToolStripMenuItem
) and a New submenu (newToolStripMenuItem
) consisting of items for each child window. The second menu, Window (windowToolStripMenuItem
), provides options for laying out the MDI children, plus a list of the active MDI children.
In the Properties window, we set the Form
’s IsMdiContainer
property to true
, making the Form
an MDI parent. In addition, we set the MenuStrip
’s MdiWindowListItem
property to windowToolStripMenuItem
. This enables the Window menu to contain the list of child MDI windows.
Fig. 15.43. MDI parent-window class.
The Cascade menu item (cascadeToolStripMenuItem
) has an event handler (cascadeToolStripMenuItem_Click
, lines 58–62) that arranges the child windows in a cascading manner. The event handler calls method LayoutMdi
with the argument Cascade
from the MdiLayout
enumeration (line 61).
The Tile Horizontal menu item (tileHorizontalToolStripMenuItem
) has an event handler (tileHorizontalToolStripMenuItem_Click
, lines 65–69) that arranges the child windows in a horizontal manner. The event handler calls method LayoutMdi
with the argument TileHorizontal
from the MdiLayout
enumeration (line 68).
Finally, the Tile Vertical menu item (tileVerticalToolStripMenuItem
) has an event handler (tileVerticalToolStripMenuItem_Click
, lines 72–76) that arranges the child windows in a vertical manner. The event handler calls method LayoutMdi
with the argument TileVertical
from the MdiLayout
enumeration (line 75).
Form
At this point, the application is still incomplete—we must define the MDI child class. To do this, right click the project in the Solution Explorer and select Add > Windows Form.... Then name the new class in the dialog as ChildForm
(Fig. 15.44). Next, we add a PictureBox
(displayPictureBox
) to ChildForm
. In ChildForm
’s constructor, line 16 sets the title-bar text. Lines 19–21 retrieve the appropriate image resource, cast it to an Image
and set displayPictureBox
’s Image
property. The images that are used can be found in the Images
subfolder of this chapter’s examples directory.
Fig. 15.44. MDI child ChildForm
.
After the MDI child class is defined, the parent MDI Form
(Fig. 15.43) can create new child windows. The event handlers in lines 18–48 create a new child Form
corresponding to the menu item clicked. Lines 22–23, 33–34 and 44–45 create new instances of ChildForm
. Lines 24, 35 and 46 set each Child
’s MdiParent
property to the parent Form
. Lines 25, 36 and 47 call method Show
to display each child Form
.
Chapter 11 discussed how to create classes by inheriting from other classes. We have also used inheritance to create Form
s that display a GUI, by deriving our new Form
classes from class System.Windows.Forms.Form
. This is an example of visual inheritance. The derived Form
class contains the functionality of its Form
base class, including any base-class properties, methods, variables and controls. The derived class also inherits all visual aspects—such as sizing, component layout, spacing between GUI components, colors and fonts—from its base class.
Visual inheritance enables you to achieve visual consistency across applications. For example, you could define a base Form
that contains a product’s logo, a specific background color, a predefined menu bar and other elements. You then could use the base Form
throughout an application for uniformity and branding. You can also create controls that inherit from other controls. For example, you might create a custom UserControl
(discussed in Section 15.14) that is derived from an existing control.
Form
Class VisualInheritanceBaseForm
(Fig. 15.45) derives from Form
. The output depicts the workings of the program. The GUI contains two Label
s with text Bugs, Bugs, Bugs and Copyright 2010, by Deitel & Associates, Inc., as well as one Button
displaying the text Learn More. When a user presses the Learn More Button
, method learnMoreButton_Click
(lines 18–24) is invoked. This method displays a MessageBox
that provides some informative text.
Fig. 15.45. Class VisualInheritanceBaseForm
, which inherits from class Form
, contains a Button
(Learn More).
Before a Form
(or any class) can be used in multiple applications, it must be placed in a class library to make it reusable. The steps for creating a reusable class are:
public
class. If the class is not public
, it can be used only by other classes in the same assembly—that is, compiled into the same DLL or EXE file.namespace
declaration to the source-code file for the reusable class declaration.Let’s take a look at these steps in the context of this example
public
ClassFor Step 1 in this discussion, we use the public
class VisualInheritanceBaseForm
declared in Fig. 15.45. By default, every new Form
class you create is declares as a public class.
namespace
DeclarationFor Step 2, we use the namespace
declaration that was created for us by the IDE. By default, every new class you define is placed in a namespace
with the same name as the project. In almost every example in the text, we’ve seen that classes from preexisting libraries, such as the .NET Framework Class Library, can be imported into a C# application. Each class belongs to a namespace that contains a group of related classes. As applications become more complex, namespaces help you manage the complexity of application components. Class libraries and namespaces also facilitate software reuse by enabling applications to add classes from other namespaces (as we’ve done in most examples). We removed the namespace declarations in earlier chapters because they were not necessary.
Placing a class inside a namespace
declaration indicates that the class is part of the specified namespace. The namespace
name is part of the fully qualified class name, so the name of class VisualInheritanceTestForm
is actually VisualInheritanceBase.Visual-InheritanceBaseForm
. You can use this fully qualified name in your applications, or you can write a using
directive and use the class’s simple name (the unqualified class name—VisualInheritanceBaseForm
) in the application. 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).
To allow other Form
s to inherit from VisualInheritanceForm
, we must package Visual-InheritanceForm
as a class library and compile it into a .dll
file. Such as file is known as a dynamically linked library—a way to package classes that you can reference from other applications. Right click the project name in the Solution Explorer and select Properties, then choose the Application tab. In the Output type drop-down list, change Windows Application to Class Library. Building the project produces the .dll
. You can configure a project to be a class library when you first create it by selecting the Class Library template in the New Project dialog. [Note: A class library cannot execute as a stand-alone application. The screen captures in Fig. 15.45 were taken before changing the project to a class library.]
Once the class is compiled and stored in the class library file, the library can be referenced from any application by indicating to the Visual C# Express IDE where to find the class library file. To visually inherit from VisualInheritanceBaseForm
, first create a new Windows application. Right-click the project name in the Solution Explorer window and select Add Reference... from the pop-up menu that appears. The dialog box that appears will contain a list of class libraries from the .NET Framework. Some class libraries, like the one containing the System
namespace, are so common that they’re added to your application by the IDE. The ones in this list are not.
In the Add Reference... dialog box, click the Browse tab. When you build a class library, Visual C# places the .dll
file in the project’s binRelease
folder. In the Browse tab, you can navigate to the directory containing the class library file you created in Step 3, as shown in Fig. 15.46. Select the .dll
file and click OK.
Fig. 15.46. Adding a reference.
Form
Open the file that defines the new application’s GUI and modify the line that defines the class to indicate that the application’s Form
should inherit from class VisualInheritanceBaseForm
. The class-declaration line should now appear as follows:
public partial class VisualInhertianceTestForm :
VisualInheritanceBase.VisualInheritanceBaseForm
Unless you specify namespace VisualInheritanceBase
in a using
directive, you must use the fully qualified name VisualInheritanceBase.VisualInheritanceBaseForm
. In Design view, the new application’s Form
should now display the controls inherited from the base Form
(Fig. 15.47). We can now add more components to the Form
.
Fig. 15.47. Form
demonstrating visual inheritance.
VisualInheritanceTestForm
Class VisualInheritanceTestForm
(Fig. 15.48) is a derived class of VisualInheritanceBaseForm
. The output illustrates the functionality of the program. The components, their layouts and the functionality of base class VisualInheritanceBaseForm
(Fig. 15.45) are inherited by VisualInheritanceTestForm
. We added an additional Button
with text About this Program. When a user presses this Button
, method aboutButton_Click
(lines 19–25) is invoked. This method displays another MessageBox
providing different informative text (lines 21–24).
Fig. 15.48. Class VisualInheritanceTestForm
, which inherits from class VisualInheritanceBaseForm
, contains an additional Button
.
If a user clicks the Learn More button, the event is handled by the base-class event handler learnMoreButton_Click
. Because VisualInheritanceBaseForm
uses a private
access modifier to declare its controls, VisualInheritanceTestForm
cannot modify the controls inherited from class VisualInheritanceBaseForm
visually or programmatically. You can, however, add event handlers for the inherited controls. The IDE displays a small icon at the top left of the visually inherited controls to indicate that they’re inherited and cannot be altered.
The .NET Framework allows you to create custom controls. These custom controls appear in the user’s Toolbox and can be added to Form
s, Panel
s or GroupBox
es in the same way that we add Button
s, Label
s and other predefined controls. The simplest way to create a custom control is to derive a class from an existing control, such as a Label
. This is useful if you want to add functionality to an existing control, rather than replacing it with one that provides the desired functionality. For example, you can create a new type of Label
that behaves like a normal Label
but has a different appearance. You accomplish this by inheriting from class Label
and overriding method OnPaint
.
OnPaint
All controls have an OnPaint
method, which the system calls when a component must be redrawn (such as when the component is resized). The method receives a PaintEventArgs
object, which contains graphics information—property Graphics
is the graphics object used to draw, and property ClipRectangle
defines the rectangular boundary of the control. Whenever the system raises a Paint
event to draw the control on the screen, the control catches the event and calls its OnPaint
method. The base class’s OnPaint
should be called explicitly from an overridden OnPaint
implementation before executing custom-paint code. In most cases, you want to do this to ensure that the original painting code executes in addition to the code you define in the custom control’s class. Alternately, if we do not wish to let the base-class OnPaint
method execute, we do not call it.
To create a new control composed of existing controls, use class UserControl
. Controls added to a custom control are called constituent controls. For example, a programmer could create a UserControl
composed of a Button
, a Label
and a TextBox
, each associated with some functionality (for example, the Button
setting the Label
’s text to that contained in the TextBox
). The UserControl
acts as a container for the controls added to it. The UserControl
contains constituent controls, but it does not determine how these constituent controls are displayed. To control the appearance of each constituent control, you can handle each control’s Paint
event or override OnPaint
. Both the Paint
event handler and OnPaint
are passed a PaintEventArgs
object, which can be used to draw graphics (lines, rectangles, and so on) on the constituent controls.
Using another technique, a programmer can create a brand-new control by inheriting from class Control
. This class does not define any specific behavior; that’s left to you. Instead, class Control
handles the items associated with all controls, such as events and sizing handles. Method OnPaint
should contain a call to the base class’s OnPaint
method, which calls the Paint
event handlers. You add code that draws custom graphics inside the overridden OnPaint
method. This technique allows for the greatest flexibility but also requires the most planning. All three approaches are summarized in Fig. 15.49.
Fig. 15.49. Custom-control creation.
We create a “clock” control in Fig. 15.50. This is a UserControl
composed of a Label
and a Timer
—whenever the Timer
raises an event (once per second in this example), the Label
is updated to reflect the current time.
Fig. 15.50. UserControl
-defined clock.
Timer
sTimer
s (System.Windows.Forms
namespace) are non-visual components that generate Tick
events at a set interval. This interval is set by the Timer
’s Interval
property, which defines the number of milliseconds (thousandths of a second) between events. By default, timers are disabled and do not generate events.
This application contains a user control (ClockUserControl
) and a Form
that displays the user control. Create a Windows application, then create a UserControl
class by selecting Project > Add User Control.... This displays a dialog from which we can select the type of control to add—user controls are already selected. We then name the file (and the class) ClockUserControl
. Our empty ClockUserControl
is displayed as a grey rectangle.
You can treat this control like a Windows Form, meaning that you can add controls using the ToolBox and set properties using the Properties window. However, instead of creating an application, you are simply creating a new control composed of other controls. Add a Label
(displayLabel
) and a Timer
(clockTimer
) to the UserControl
. Set the Timer
interval to 1000 milliseconds and set displayLabel
’s text with each Tick
event (lines 18–22). To generate events, clockTimer
must be enabled by setting property Enabled
to true
in the Properties window.
Structure DateTime
(namespace System
) contains property Now
, which returns the current time. Method ToLongTimeString
converts Now
to a string
containing the current hour, minute and second (along with AM or PM, depending on your locale). We use this to set the time in displayLabel
in line 21.
Once created, our clock control appears as an item on the ToolBox in the section titled ProjectName Components, where ProjectName is your project’s name. You may need to switch to the application’s Form before the item appears in the ToolBox. To use the control, simply drag it to the Form
and run the Windows application. We gave the ClockUserControl
object a white background to make it stand out in the Form
. Figure 15.50 shows the output of Clock
, which contains our ClockUserControl
. There are no event handlers in Clock
, so we show only the code for ClockUserControl
.
Visual Studio allows you to share custom controls with other developers. To create a User-Control
that can be exported to other solutions, do the following:
Class1.cs
, initially provided with the application.UserControl
(Fig. 15.51).
Fig. 15.51. Custom-control creation.
.dll
file for the UserControl
in the output directory (bin/Release
). The file is not executable; class libraries are used to define classes that are reused in other executable applications..dll
file from the class library created in Steps 1–5. The item will then appear in the Choose Toolbox Items dialog (Fig. 15.52). If it is not already checked, check this item. Click OK to add the item to the Toolbox. This control can now be added to the Form
as if it were any other control.
Fig. 15.52. Custom control added to the ToolBox.
Many of today’s commercial applications provide GUIs that are easy to use and manipulate. Because of this demand for user-friendly GUIs, the ability to design sophisticated GUIs is an essential programming skill. Visual Studio’s IDE makes GUI development quick and easy. In Chapters 14 and 15, we presented basic Windows Forms GUI development techniques. In Chapter 15, we demonstrated how to create menus, which provide users easy access to an application’s functionality. You learned the DateTimePicker
and MonthCalendar
controls, which allow users to input date and time values. We demonstrated LinkLabel
s, which are used to link the user to an application or a web page. You used several controls that provide lists of data to the user—ListBox
es, CheckedListBox
es and ListView
s. We used the ComboBox
control to create drop-down lists, and the TreeView
control to display data in hierarchical form. We then introduced complex GUIs that use tabbed windows and multiple document interfaces. The chapter concluded with demonstrations of visual inheritance and creating custom controls. In Chapter 16, we introduce string
and character processing.