Topics in This Chapter
Introduction: With just a few lines of code, you can build a Windows Forms (WinForms) application that demonstrates the basics of event handling and creating child forms.
Using Form Controls: All controls inherit from the base Control
class. The members of this class provide a uniform way of positioning, sizing, and modifying a control's appearance.
The Form Class: The Form
class includes custom properties that affect its appearance, and enable it to work with menus and manage child forms.
Message and Dialog Boxes: A pop-up window to provide user interaction or information can be created as a message or dialog box.
MDI Forms: A Multiple Document Interface (MDI) is a container that holds child forms. A main menu permits the forms to be organized, accessed, and manipulated.
Working with Menus: .NET supports two types of menus: a main form menu and a context menu that can be associated with individual controls.
Adding Help to a Form: Help buttons, ToolTips, and HTML Help are options for adding help to a form.
Form Inheritance: Visual inheritance enables a form to be created quickly, by inheriting the interface elements and functionality from another form.
This chapter is aimed at developers responsible for creating Graphical User Interface (GUI) applications for the desktop—as opposed to applications that run on a Web server or mobile device. The distinction is important because .NET provides separate class libraries for each type of application and groups them into distinct namespaces:
System.Windows.Forms
. Windows Forms (WinForms).
System.Web.UIWebControls
. Web Forms.
System.Web.UIMobileControls
. Mobile Forms for hand-held and pocket devices.
Although this chapter focuses on Windows Forms, it is important to recognize that modern applications increasingly have to support multiple environments. Acknowledging this, .NET strives to present a uniform “look and feel” for applications developed in each. The forms and controls in all three namespaces provide similar, but not identical, functionality. The knowledge you acquire in this chapter will shorten the learning curve required to develop .NET applications for the Web and mobile devices.
Developers usually rely on an Integrated Development Environment (IDE), such as Visual Studio, to develop GUI applications. This makes it easy to overlook the fact that a form is a class that inherits from other classes and has its own properties and methods. To provide a true understanding of forms, this chapter peers beneath the IDE surface at the class members and how their implementation defines and affects the behavior of the form. The discussion includes how to display forms, resize them, make them scrollable or transparent, create inherited forms, and have them react to mouse and keyboard actions.
This is not a chapter about the principles of GUI design, but it does demonstrate how adding amenities, such as Help files and a tab order among controls, improves form usability. Controls, by the way, are discussed only in a generic sense. A detailed look at specific controls is left to Chapter 7, “Windows Forms Controls.”
All Windows Forms programs begin execution at a designated main window. This window is actually a Form
object that inherits from the System.Windows. Forms.Form
class. The initial window is displayed by passing an instance of it to the static Application.Run
method.
The challenge for the developer is to craft an interface that meets the basic rule of design—form follows function. This means that a form's design should support its functionality to the greatest extent possible. To achieve this, a developer must understand the properties, methods, and events of the Form
class, as well as those of the individual controls that are placed on the form.
Let's create a simple Windows application using a text editor and the C# compiler from the command line. This application, shown in Listing 6-1, consists of a single window with a button that pops up a message when the button is clicked. The simple exercise demonstrates how to create a form, add a control to it, and set up an event handler to respond to an event fired by the control.
Example 6-1. Basic Windows Forms Application Built by Hand
using System; using System.Windows.Forms; using System.Drawing; class MyWinApp { static void Main() { // (1) Create form and invoke it Form mainForm = new SimpleForm(); Application.Run(mainForm); } } // User Form derived from base class Form class SimpleForm:Form { private Button button1; public SimpleForm() { this.Text = "Hand Made Form"; // (2) Create a button control and set some attributes button1 = new Button(); button1.Location = new Point(96,112); button1.Size = new Size(72,24); button1.Text= "Status"; this.Controls.Add(button1); // (3) Create delegate to call routine when click occurs button1.Click += new EventHandler(button1_Click); } void button1_Click(object sender, EventArgs e) { MessageBox.Show("Up and Running"); } }
Recall from Chapter 1, “Introduction to .NET and C#,” that command-line compilation requires providing a target output file and a reference to any required assemblies. In this case, we include the System.Windows.Forms
assembly that contains the necessary WinForms classes. To compile, save the preceding source code as winform.cs
and enter the following statement at the command prompt:
csc /t:winform.exe /r:System.Windows.Forms.dll winform.cs
After it compiles, run the program by typing winform;
the screen shown in Figure 6-1 should appear. The output consists of a parent form and a second form created by clicking the button. An important point to note is that the parent form cannot be accessed as long as the second window is open. This is an example of a modal form, where only the last form opened can be accessed. The alternative is a modeless form, in which a parent window spawns a child window and the user can access either the parent or child window(s). Both of these are discussed later.
The code breaks logically into three sections:
Form Creation.
The parent form is an instance of the class SimpleForm
, which inherits from Form
and defines the form's custom features. The form—and program—is invoked by passing the instance to the Application.Run
method.
Create Button Control.
A control is placed on a form by creating an instance of the control and adding it to the form. Each form has a Controls
property that returns a Control.Collection
type that represents the collection of controls contained on a form. In this example, the Controls.Add
method is used to add a button to the form. Note that a corresponding Remove
method is also available to dynamically remove a control from its containing form. An IDE uses this same Add
method when you drag a control onto a form at design time. However, if you want to add or delete controls at runtime, you will be responsible for the coding.
Controls have a number of properties that govern their appearance. The two most basic ones are Size
and Location
. They are implemented as:
button1.Size = new Size(72,24); // width, height button1.Location = new Point(96,112); //x,y
The struct Size
has a constructor that takes the width and height as parameters; the constructor for Point
accepts the x and y coordinates of the button within the container.
Event handling requires providing a method to respond to the event and creating a delegate that invokes the method when the event occurs (refer to Chapter 3, “Class Design in C#,” for details of event handling). In this example, button1_Click
is the method that processes the event. The delegate associated with the Click
event is created with the following statement:
button1.Click += new EventHandler(button1_Click);
This statement creates an instance of the built-in delegate EventHandler
and registers the method button1_Click
with it.
.NET 2.0 adds a feature known as Partial Types, which permits a class to be physically separated into different files. To create a partial class, place the keyword partial
in front of class
in each file containing a segment of the class. Note that only one class declaration should specify Forms
inheritance. The compilation process combines all the files into one class—identical to a single physical class. For Windows applications, partial classes seem something of a solution in search of a problem. However, for Web applications, they serve a genuine need, as is discussed in Chapter 16, “ASP.NET Web Forms and Controls.”
This exercise should emphasize the fact that working with forms is like working with any other classes in .NET. It requires gaining a familiarity with the class members and using standard C# programming techniques to access them.
The previous examples have demonstrated how a custom form is derived from the Windows.Forms.Form
class. Now, let's take a broader look at the class hierarchy from which the form derives and the functionality that each class offers (see Figure 6-2).
It may seem counterintuitive that the Form
class is below the Control
class on the hierarchical chain, because a form typically contains controls. But the hierarchy represents inheritance, not containment. A Form
is a container control, and the generic members of the Control
class can be applied to it just as they are to a simple Label
or TextBox
.
System.Windows.Forms.dll
contains more than fifty controls that are available for use on a Windows Form. A few that inherit directly from Control
are listed in Figure 6-2. All controls share a core set of inherited properties, methods, and events. These members allow you to size and position the control; adorn it with text, colors, and style features; and respond to keyboard and mouse events. This chapter examines the properties that are common to all inheriting controls; Chapter 7 offers a detailed look at the individual controls and how to use their distinct features in applications.
Table 6-1 defines some of the properties common to most types that inherit from Control
.
Table 6-1. Common Control Properties
Category | Property | Description |
---|---|---|
Size and position |
| A |
|
| |
These | ||
|
Button.Bounds = new Rectangle (10,10,50,60) | |
|
| |
| Specifies which edges of the container the control is anchored to. This is useful when the container is resized. | |
| Controls which edge of the container the control docks to. | |
Color and appearance |
| Specifies the background and foreground color for the control. Color is specified as a static property of the |
|
| |
Text |
|
|
|
| |
Focus |
|
|
| Boolean value that indicates whether this control can receive focus from the Tab key. | |
| Boolean value indicating whether a control has input focus. | |
| Boolean value indicating whether the control is displayed. | |
Keyboard and mouse |
| Returns the current state of the mouse buttons (left, right, and middle). |
Returns a | ||
| Indicates which of the modifier keys (Shift, Ctrl, Alt) is pressed. | |
| Specifies the shape of the mouse pointer when it is over the control. Assigned value is a static property of the .Hand .Cross UpArrow Default .Beam .Arrow WaitCursor | |
Runtime status |
|
|
|
|
When you drag a control onto a form, position it, and size it, VisualStudio.NET (VS.NET) automatically generates code that translates the visual design to the underlying property values. There are times, however, when a program needs to modify controls at runtime to hide them, move them, and even resize them. In fact, size and position are often based on the user's screen size, which can only be detected at runtime. An IDE cannot do this, so it is necessary that a programmer understand how these properties are used to design an effective control layout.
As we saw in the earlier example, the size of a control is determined by the Size
object, which is a member of the System.Drawing
namespace:
button1.Size = new Size(80,40); // (width, height) button2.Size = button1.Size; // Assign size to second button
A control can be resized during runtime by assigning a new Size
object to it. This code snippet demonstrates how the Click
event handler method can be used to change the size of the button when it is clicked:
private void button1_Click(object sender, System.EventArgs e) { MessageBox.Show("Up and Running"); Button button1 = (Button) sender; //Cast object to Button button1.Size = new Size(90,20); //Dynamically resize
The System.Drawing.Point
object can be used to assign a control's location. Its arguments set the x and y coordinates—in pixels—of the upper-left corner of a control. The x coordinate is the number of pixels from the left side of the container. The y coordinate is the number of pixels from the top of the container.
button1.Location = new Point(20,40); // (x,y) coordinates
It is important to recognize that this location is relative to the control's container. Thus, if a button is inside a GroupBox
, the button's location is relative to it and not the form itself. A control's Location
also can be changed at runtime by assigning a new Point
object.
Another approach is to set the size and location in one statement using the Bounds
property:
button1.Bounds = new Rectangle(20,40, 100,80);
A Rectangle
object is created with its upper-left corner at the x,y coordinates (20,40) and its lower-right coordinates at (100,80). This corresponds to a width of 80 pixels and a height of 40 pixels.
The Dock
property is used to attach a control to one of the edges of its container. By default, most controls have docking set to none
; some exceptions are the StatusStrip/StatusBar
that is set to Bottom
and the ToolStrip/ToolBar
that is set to Top
. The options, which are members of the DockStyle
enumeration, are Top
, Bottom
, Left
, Right
, and Fill
. The Fill
option attaches the control to all four corners and resizes it as the container is resized. To attach a TextBox
to the top of a form, use
TextBox1.Dock = DockStyle.Top;
Figure 6-3 illustrates how docking affects a control's size and position as the form is resized. This example shows four text boxes docked, as indicated.
Resizing the form does not affect the size of controls docked on the left or right. However, controls docked to the top and bottom are stretched or shrunk horizontally so that they take all the space available to them.
The Form
class and all other container controls have a DockPadding
property that can be set to control the amount of space (in pixels) between the container's edge and the docked control.
The Anchor
property allows a control to be placed in a fixed position relative to a combination of the top, left, right, or bottom edge of its container. Figure 6-4 illustrates the effects of anchoring.
The distance between the controls' anchored edges remains unchanged as the form is stretched. The PictureBox
(1) is stretched horizontally and vertically so that it remains the same distance from all edges; the Panel
(2) control maintains a constant distance from the left and bottom edge; and the Label
(3), which is anchored only to the top, retains its distance from the top, left, and right edges of the form.
The code to define a control's anchor position sets the Anchor
property to values of the AnchorStyles
enumeration (Bottom
, Left
, None
, Right
, Top
). Multiple values are combined using the OR ( |
) operator:
btnPanel.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
Tab order defines the sequence in which the input focus is given to controls when the Tab key is pressed. The default sequence is the order in which the controls are added to the container.
The tab order should anticipate the logical sequence in which users expect to input data and thus guide them through the process. The form in Figure 6-5 represents such a design: The user can tab from the first field down to subsequent fields and finally to the button that invokes the final action.
Observe two things in the figure: First, even though labels have a tab order, they are ignored and never gain focus; and second, controls in a container have a tab order that is relative to the container—not the form.
A control's tab order is determined by the value of its TabIndex
property:
TextBox1.TabIndex = 0; //First item in tab sequence
In VS.NET, you can set this property directly with the Property Manager, or by selecting ViewTabOrder and clicking the boxes over each control to set the value. If you do not want a control to be included in the tab order, set its TabStop
value to false
. This does not, however, prevent it from receiving focus from a mouse click.
When a form is loaded, the input focus is on the control (that accepts mouse or keyboard input) with the lowest TabIndex
value. During execution, the focus can be given to a selected control using the Focus
method:
if(textBox1.CanFocus) { textBox1.Focus(); }
All controls on a form are contained in a Controls
collection. By enumerating through this collection, it is possible to examine each control, identify it by name and type, and modify properties as needed. One common use is to clear the value of selected fields on a form in a refresh operation. This short example examines each control in Figure 6-5 and displays its name and type:
int ctrlCt = this.Controls.Count; // 8 foreach (Control ct in this.Controls) { object ob = ct.GetType(); MessageBox.Show(ob.ToString()); //Displays type as string MessageBox.Show(ct.Name); }
There are several things to be aware of when enumerating control objects:
The type of each control is returned as a fully qualified name. For example, a TextBox
is referred to as System.Forms.Form.TextBox
.
Only a container's top-level objects are listed. In this example, the Controls.Count
value is 8 rather than 10 because the GroupBox
is counted as one control and its child controls are excluded.
You can use a control's HasChildren
property to determine if it is a container. Listing 6-2 uses recursion to list all child controls.
Example 6-2. Enumerate All Controls on a Form Recursively
void IterateThroughControls(Control parent) { foreach (Control c in parent.Controls) { MessageBox.Show(c.ToString()); if(c.HasChildren) { IterateThroughControls(c); } } }
Applying this code to Figure 6-5 results in all controls on the main form being listed hierarchically. A control is listed followed by any child it may have.
When you push a key on the keyboard or click the mouse, the control that is the target of this action fires an event to indicate the specific action that has occurred. A registered event handling routine then responds to the event and formulates what action to take.
The first step in handling an event is to identify the delegate associated with the event. You must then register the event handling method with it, and make sure the method's signature matches the parameters specified by the delegate. Table 6-2 summarizes the information required to work with mouse and keyboard triggered events.
Table 6-2. Control Events
Event | Built-In Delegate/Parameters | Description |
---|---|---|
Click, DoubleClick, MouseEnter, MouseLeave, MouseHover, MouseWheel |
EventHandler ( object sender, EventArgs e) | Events triggered by clicking, double clicking, or moving the mouse. |
MouseDown, MouseUp, MouseMove |
MouseEventHandler ( object sender, MouseEventArgs) | Events triggered by mouse and mouse button motions. Note that this event is not triggered if the mouse action occurs within a control in the current container. |
KeyUp, KeyDown |
KeyEventHandler ( object sender, KeyEventArgs e) | Events triggered by key being raised or lowered. |
|
KeyPressEventHandler ( object sender, KeyPressEventArgs e) | Event triggered by pressing any key. |
In addition to the familiar Click
and DoubleClick
events, all Windows Forms controls inherit the MouseHover
, MouseEnter
, and MouseLeave
events. The latter two are fired when the mouse enters and leaves the confines of a control. They are useful for creating a MouseOver
effect that is so common to Web pages.
To illustrate this, let's consider an example that changes the background color on a text box when a mouse passes over it. The following code sets up delegates to call OnMouseEnter
and OnMouseLeave
to perform the background coloring:
TextBox userID = new TextBox(); userID.MouseEnter += new EventHandler(OnMouseEnter); userID.MouseLeave += new EventHandler(OnMouseLeave);
The event handler methods match the signature of the EventHandler
delegate and cast the sender
parameter to a Control
type to access its properties.
private void OnMouseEnter(object sender, System.EventArgs e){ Control ctrl = (Control) sender; ctrl.BackColor= Color.Bisque; } private void OnMouseLeave(object sender, System.EventArgs e){ Control ctrl = (Control) sender; ctrl.BackColor= Color.White; }
It is possible to handle events by overriding the default event handlers of the base Control
class. These methods are named using the pattern Oneventname
—for example, OnMouseDown
, OnMouseMove
, and so on. To be consistent with .NET, the delegate approach is recommended over this. It permits a control to specify multiple event handlers for an event, and it also permits a single event handler to process multiple events.
The delegates for the MouseDown
, MouseUp
, and MouseMove
events take a second argument of the MouseEventArgs
type rather than the generic EventArgs
type. This type reveals additional status information about a mouse via the properties shown in Table 6-3.
Table 6-3. Properties of MouseEventArgs
Property | Description |
---|---|
| Indicates which mouse button was pressed. The attribute value is defined by the |
| Number of clicks since last event. |
| Number of detents the mouse wheel rotates. A positive number means it's moving forward; a negative number shows backward motion. |
| Mouse coordinates relative to the container's upper-left corner. This is equivalent to the control's |
The properties in this table are particularly useful for applications that must track mouse movement across a form. Prime examples are graphics programs that rely on mouse location and button selection to control onscreen drawing. To illustrate, Listing 6-3 is a simple drawing program that draws a line on a form's surface, beginning at the point where the mouse key is pressed and ending where it is raised. To make it a bit more interesting, the application draws the line in black if the left button is dragged, and in red if the right button is dragged.
Example 6-3. Using Mouse Events to Draw on a Form
using System; using System.Windows.Forms; using System.Drawing; class MyWinApp { static void Main() { Form mainForm = new DrawingForm(); Application.Run(mainForm); } } // User Form derived from base class Form class DrawingForm:Form { Point lastPoint = Point.Empty; // Save coordinates public Pen myPen; //Defines color of line public DrawingForm() { this.Text = "Drawing Pad"; // reate delegates to call MouseUp and MouseDown this.MouseDown += new MouseEventHandler(OnMouseDown); this.MouseUp += new MouseEventHandler(OnMouseUp); } private void OnMouseDown(object sender, MouseEventArgs e) { myPen = (e.Button==MouseButtons.Right)? Pens.Red: Pens.Black; lastPoint.X = e.X; lastPoint.Y = e.Y; } private void OnMouseUp(object sender, MouseEventArgs e) { // Create graphics object Graphics g = this.CreateGraphics(); if (lastPoint != Point.Empty) g.DrawLine(myPen, lastPoint.X,lastPoint.Y,e.X,e.Y); lastPoint.X = e.X; lastPoint.Y = e.Y; g.Dispose(); } }
Even without an understanding of .NET graphics, the role of the graphics-related classes should be self-evident. A Graphics
object is created to do the actual drawing using its DrawLine
method. The parameters for this method are the Pen
object that defines the color and the coordinates of the line to be drawn. When a button is pressed, the program saves the coordinate and sets myPen
based on which button is pressed: a red pen for the right button and black for the left. When the mouse button is raised, the line is drawn from the previous coordinate to the current location. The MouseEventArgs
object provides all the information required to do this.
Keyboard events are also handled by defining a delegate to call a custom event handling method. Two arguments are passed to the event handler: the sender
argument identifies the object that raised the event and the second argument contains fields describing the event. For the KeyPress
event, this second argument is of the KeyPressEventArgs
type. This type contains a Handled
field that is set to true
by the event handling routine to indicate it has processed the event. Its other property is KeyChar
, which identifies the key that is pressed.
KeyChar
is useful for restricting the input that a field accepts. This code segment demonstrates how easy it is to limit a field's input to digits. When a non-digit is entered, Handled
is set to true
, which prevents the form engine from displaying the character. Otherwise, the event handling routine does nothing and the character is displayed.
private void OnKeyPress(object sender, KeyPressEventArgs e) { if (! char.IsDigit(e.KeyChar)) e.Handled = true; }
The KeyPress
event is only fired for printable character keys; it ignores non-character keys such as Alt or Shift. To recognize all keystrokes, it is necessary to turn to the KeyDown
and KeyUp
events. Their event handlers receive a KeyEventArgs
type parameter that identifies a single keystroke or combination of keystrokes. Table 6-4 lists the important properties provided by KeyEventArgs
.
Table 6-4. KeyEventArgs
Properties
Member | Description |
---|---|
Alt, Control, Shift | Boolean value that indicates whether the Alt, Control, or Shift key was pressed. |
| Boolean value that indicates whether an event was handled. |
| Returns the key code for the event. This code is of the |
| Returns the key data for the event. This is also of the |
| Indicates which combination of modifier keys (Alt, Ctrl, and Shift) was pressed. |
A few things to note:
The Alt
, Control
, and Shift
properties are simply shortcuts for comparing the KeyCode
value with the Alt
, Control
, or Shift
member of the Keys
enumeration.
KeyCode
represents a single key value; KeyData
contains a value for a single key or combination of keys pressed.
The Keys
enumeration is the secret to key recognition because its members represent all keys. If using Visual Studio.NET, Intellisense lists all of its members when the enum is used in a comparison; otherwise, you need to refer to online documentation for the exact member name because the names are not always what you expect. For example, digits are designated by D1
, D2
, and so on.
The preceding code segment showed how to use KeyPress
to ensure a user presses only number keys (0–9). However, it does not prevent one from pasting non-numeric data using the Ctrl-V key combination. A solution is to use the KeyDown
event to detect this key sequence and set a flag notifying the KeyPress
event handler to ignore the attempt to paste.
In this example, two event handlers are registered to be called when a user attempts to enter data into a text box using the keyboard. KeyDown
is invoked first, and it sets paste
to true
if the user is pushing the Ctrl-V key combination. The KeyPress
event handler uses this flag to determine whether to accept the key strokes.
private bool paste; //Register event handlers for TextBox t. //They should be registered in this order, //because the last registered is the //first executed t.KeyPress += new KeyPressEventHandler(OnKeyPress); t.KeyDown += new KeyEventHandler(OnKeyDown); private void OnKeyDown(object sender, KeyEventArgs e) { if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V) { paste=true; //Flag indicating paste attempt string msg = string.Format("Modifier: {0} KeyCode: {1} KeyData: {2}", e.Modifiers.ToString(), e.KeyCode.ToString(), e.KeyData.ToString()); MessageBox.Show(msg); //Display property values } private void OnKeyPress(object sender, KeyPressEventArgs e) { if (paste==true) e.Handled = true; }
This program displays the following values for the selected KeyEventArgs
properties when Ctrl-V is pressed:
Modifier: Control KeyCode: V KeyData: Control, V
The Form
object inherits all the members of the Control
class as well as the ScrollableControl
class, which provides properties that enable scrolling. To this it adds a large number of properties that enable it to control its appearance, work with child forms, create modal forms, display menus, and interact with the desktop via tool and status bars. Table 6-5 shows a selected list of these properties.
Table 6-5. Selected Form Properties
Category | Property | Description |
---|---|---|
Appearance |
| Gets or sets the border style of the form. It is defined by the Fixed3D None FixedSingle Sizable FixedDialog |
| Boolean value that determines whether the menu icon in the left corner of a form and the close button on the upper right are shown. | |
MaximizeBox MinimizeBox | Boolean value that indicates whether these buttons are displayed on the form. | |
| Gets or sets the opacity of the form and all of its controls. The maximum value (least transparent) is 1.0. Does not work with Windows 95/98. | |
| A color that represents transparent areas on the form. Any control or portion of the form that has a back color equal to this color is not displayed. Clicking this transparent area sends the event to any form below it. | |
Size and position |
| Indicates whether the form adjusts its size to accommodate the size of the font used. |
| Size of the form excluding borders and the title bar. | |
| A | |
Specifies the initial position of a form. It takes a
| ||
MinimumSize MaximumSize | A | |
| Boolean value specifying whether application is represented in Windows task bar. Default is | |
TopLevel TopMost | Indicates whether to display the form as a | |
| Indicates how the form is displayed on startup. It takes a value from the | |
Owner forms |
| The form designated as the owner of the form. |
| A |
The four properties shown in Figure 6-6 control which buttons and icon are present on the top border of a form. The Icon
property specifies the .ico
file to be used as the icon in the left corner; the ControlBox
value determines whether the icon and close button are displayed (true
) or not displayed (false
); similarly, the MaximizeBox
and MinimizeBox
determine whether their associated buttons appear.
The purpose of these properties is to govern functionality more than appearance. For example, it often makes sense to suppress the minimize and maximize buttons on modal forms in order to prevent a user from maximizing the form and hiding the underlying parent form.
A form's opacity
property determines its level of transparency. Values ranges from 0 to 1.0, where anything less than 1.0 results in partial transparency that allows elements beneath the form to be viewed. Most forms work best with a value of 1.0, but adjusting opacity can be an effective way to display child or TopMost
forms that hide an underlying form. A common approach is to set the opacity of such a form to 1.0 when it has focus, and reduce the opacity when it loses focus. This technique is often used with search windows that float on top of the document they are searching.
Let's look at how to set up a form that sets its opacity to 1 when it is active and to .8 when it is not the active form. To do this, we take advantage of the Deactivate
and Activated
events that are triggered when a form loses or gains focus. We first set up the delegates to call the event handling routines:
this.Deactivate += new System.EventHandler(Form_Deactivate); this.Activated += new System.EventHandler(Form_Activate);
The code for the corresponding event handlers is trivial:
void Form_Deactivate(object sender, EventArgs e) { this.Opacity= .8; } void Form_Activate(object sender, EventArgs e) { this.Opacity= 1; }
Opacity
affects the transparency of an entire form. There is another property, TransparencyKey
, which can be used to make only selected areas of a form totally transparent. This property designates a pixel color that is rendered as transparent when the form is drawn. The effect is to create a hole in the form that makes any area below the form visible. In fact, if you click a transparent area, the event is recognized by the form below.
The most popular use of transparency is to create non-rectangular forms. When used in conjunction with a border style of FormBorderStyle.None
to remove the title bar, a form of just about any geometric shape can be created. The next example illustrates how to create and use the cross-shaped form in Figure 6-7.
The only requirement in creating the shape of the form is to lay out the transparent areas in the same color as the transparency key color. Be certain to select a color that will not be used elsewhere on the form. A standard approach is to set the back color of the form to the transparency key color, and draw an image in a different color that it will appear as the visible form.
To create the form in Figure 6-7, place Panel
controls in each corner of a standard form and set their BackColor
property to Color.Red
. The form is created and displayed using this code:
CustomForm myForm = new CustomForm(); myForm.TransparencyKey = Color.Red; myForm.FormBorderStyle= FormBorderStyle.None; myForm.Show();
This achieves the effect of making the panel areas transparent and removing the title bar. With the title bar gone, it is necessary to provide a way for the user to move the form. This is where the mouse event handling discussed earlier comes to the rescue.
At the center of the form is a multiple arrow image displayed in a PictureBox
that the user can click and use to drag the form. Listing 6-4 shows how the MouseDown
, MouseUp
, and MouseMove
events are used to implement form movement.
Example 6-4. Using Form Transparency to Create a Non-Rectangular Form
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; public class CustomForm : Form { private Point lastPoint = Point.Empty; //Save mousedown public CustomForm() { InitializeComponent(); // set up form //Associate mouse events with pictureBox pictureBox1.MouseDown += new MouseEventHandler( OnMouseDown); pictureBox1.MouseUp += new MouseEventHandler(OnMouseUp); pictureBox1.MouseMove += new MouseEventHandler( OnMouseMove); } private void OnMouseDown(object sender, MouseEventArgs e) { lastPoint.X = e.X; lastPoint.Y = e.Y; } private void OnMouseUp(object sender, MouseEventArgs e) { lastPoint = Point.Empty; } //Move the form in response to the mouse being dragged private void OnMouseMove(object sender, MouseEventArgs e) { if (lastPoint != Point.Empty) { //Move form in response to mouse movement int xInc = e.X - lastPoint.X; int yInc = e.Y - lastPoint.Y; this.Location = new Point(this.Left + xInc, this.Top+yInc); } } // Close Window private void button1_Click(object sender, System.EventArgs e) { this.Close(); } }
The logic is straightforward. When the user clicks the PictureBox
, the coordinates are recorded as lastPoint
. As the user moves the mouse, the Location
property of the form is adjusted to reflect the difference between the new coordinates and the original saved location. When the mouse button is raised, lastPoint
is cleared. Note that a complete implementation should also include code to handle form resizing.
The initial location of a form is determined directly or indirectly by its StartPosition
property. As described in Table 6-6, it takes its value from the FormStartPosition
enumeration. These values allow it to be placed in the center of the screen, in the center of a parent form, at a Windows default location, or at an arbitrarily selected location. Manual
offers the greatest flexibility because it allows the program to set the location.
Table 6-6. The Life Cycle of a Modeless Form
Action | Events Triggered | Description |
---|---|---|
Form object created | The form's constructor is called. In Visual Studio, the | |
Form displayed: Form.Show() |
Form.Load Form.Activated | The |
Form activated |
| This occurs when the user selects the form. This becomes an “active” form. |
Form deactivated |
| Form is deactivated when it loses focus. |
Form closed |
Form.Deactivate Form.Closing Form.Closed | Form is closed by executing |
The initial location is normally set in the Form.Load
event handler. This example loads the form 200 pixels to the right of the upper-left corner of the screen:
private void opaque_Load(object sender, System.EventArgs e) { this.DesktopLocation = new Point(200,0); }
The form's initial location can also be set by the form that creates and displays the form object:
opaque opForm = new opaque(); opForm.Opacity = 1; opForm.TopMost = true; //Always display form on top opForm.StartPosition = FormStartPosition.Manual; opForm.DesktopLocation = new Point(10,10); opForm.Show();
This code creates an instance of the form opaque
and sets its TopMost
property so that the form is always displayed on top of other forms in the same application. The DeskTopLocation
property sets the form's initial location. For it to work, however, the StartPostion
property must first be set to FormStartPosition.Manual
.
The DesktopLocation
property sets coordinates within a screen's working area, which is the area not occupied by a task bar. The Location
property of the Control
class sets the coordinates relative to the upper-left edge of the control's container.
A form's size can be set using either its Size
or ClientSize
property. The latter is usually preferred because it specifies the workable area of the form—the area that excludes the title bar, scrollbars, and any edges. This property is set to an instance of a Size
object:
this.ClientSize = new System.Drawing.Size(208, 133);
It is often desirable to position or size a form relative to the primary (.NET supports multiple screens for an application) screen size. The screen size is available through the Screen.PrimaryScreen.WorkingArea
property. This returns a rectangle that represents the size of the screen excluding task bars, docked toolbars, and docked windows. Here is an example that uses the screen size to set a form's width and height:
int w = Screen.PrimaryScreen.WorkingArea.Width; int h = Screen.PrimaryScreen.WorkingArea.Height; this.ClientSize = new Size(w/4,h/4);
After a form is active, you may want to control how it can be resized. The aptly named MinimumSize
and MaximumSize
properties take care of this. In the following example, the maximum form size is set to one-half the width and height of the working screen area:
//w and h are the screen's width and height this.MaximumSize = new Size(w/2,h/2); this.MinimumSize = new Size(200, 150);
Setting both width and height to zero removes any size restrictions.
After a main form is up and running, it can create instances of new forms and display them in two ways: using the Form.ShowDialog
method or the Form.Show
method inherited from the Control
class. Form.ShowDialog
displays a form as a modal dialog box. When activated, this type of form does not relinquish control to other forms in the application until it is closed. Dialog boxes are discussed at the end of this section.
Form.Show
displays a modeless form, which means that the form has no relationship with the creating form, and the user is free to select the new or original form. If the creating form is not the main form, it can be closed without affecting the new form; closing the main form automatically closes all forms in an application.
A form is subject to a finite number of activities during its lifetime: It is created, displayed, loses and gains focus, and finally is closed. Most of these activities are accompanied by one or more events that enable the program to perform necessary tasks associated with the event. Table 6-6 summarizes these actions and events.
Let's look at some of the code associated with these events.
When one form creates another form, there are coding requirements on both sides. The created form must set up code in its constructor to perform initialization and create controls. In addition, delegates should be set up to call event handling routines. If using Visual Studio.NET, any user initialization code should be placed after the call to InitializeComponent
.
For the class that creates the new form object, the most obvious task is the creation and display of the object. A less obvious task may be to ensure that only one instance of the class is created because you may not want a new object popping up each time a button on the original form is clicked. One way to manage this is to take advantage of the Closed
event that occurs when a created form is closed (another way, using OwnedForms
, is discussed shortly). If the form has not been closed, a new instance is not created. The code that follows illustrates this.
An EventHandler
delegate is set up to notify a method when the new form, opForm
, is closed. A flag controls what action occurs when the button to create or display the form is pushed. If an instance of the form does not exist, it is created and displayed; if it does exist, the Form.Activate
method is used to give it focus.
//Next statement is at beginning of form's code public opaque opForm; bool closed = true; //Flag to indicate if opForm exists //Create new form or give focus to existing one private void button1_Click(object sender, System.EventArgs e) { if (closed) { closed = false; opForm = new opaque(); //Call OnOpClose when new form closes opForm.Closed += new EventHandler(OnOpClose); opForm.Show(); //Display new form object } else { opForm.Activate(); //Give focus to form } } //Event handler called when child form is closed private void OnOpClose(object sender, System.EventArgs e) { closed = true; //Flag indicating form is closed }
A form becomes active when it is first shown or later, when the user clicks on it or moves to it using an Alt-Tab key to iterate through the task bar. This fires the form's Activated
event. Conversely, when the form loses focus—through closing or deselection—the Deactivate
event occurs. In the next code segment, the Deactivate
event handler changes the text on a button to Resume and disables the button; the Activated
event handler re-enables the button.
this.Deactivate += new System.EventHandler(Form_Deactivate); this.Activated += new System.EventHandler(Form_Activate); // void Form_Deactivate(object sender, EventArgs e) { button1.Enabled = false; button1.Text = "Resume"; } void Form_Activate(object sender, EventArgs e) { button1.Enabled = true; }
The Closing
event occurs as a form is being closed and provides the last opportunity to perform some cleanup duties or prevent the form from closing. This event uses the CancelEventHandler
delegate to invoke event handling methods. The delegate defines a CancelEventArgs
parameter that contains a Cancel
property, which is set to true
to cancel the closing. In this example, the user is given a final prompt before the form closes:
this.Closing += new CancelEventHandler(Form_Closing); void Form_Closing(object sender, CancelEventArgs e) { if(MessageBox.Show("Are you sure you want to Exit?", "", MessageBoxButtons.YesNo) == DialogResult.No) { //Cancel the closing of the form e.Cancel = true; } }
When multiple form objects have been created, there must be a way for one form to access the state and contents of controls on another form. It's primarily a matter of setting the proper access modifiers to expose fields and properties on each form. To illustrate, let's build an application that consists of two modeless forms (see Figure 6-8). The main form contains two controls: a Textbox
that holds the document being processed and a Search button that, when clicked, passes control to a search form. The search form has a Textbox
that accepts text to be searched for in the main form's document. By default, the search phrase is any highlighted text in the document; it can also be entered directly by the user.
When the Find Next button is pushed, the application searches for the next occurrence of the search string in the main document. If an occurrence is located, it is highlighted. To make it more interesting, the form includes options to search forward or backward and perform a case-sensitive or case-insensitive search.
The main challenge in developing this application is to determine how each form makes the content of its controls available to the other form. DocForm
, the main form, must expose the contents of documentText
so that the search form can search the text in it and highlight an occurrence of matching text. The search form, SearchForm
, must expose the contents of txtSearch
, the TextBox
containing the search phrase, so that the main form can set it to the value of any highlighted text before passing control to the form.
DocForm
shares the contents of documentText
through a text box field myText
that is assigned the value of documentText
when the form loads. Setting myText
to public static
enables the search form to access the text box properties by simply qualifying them with DocForm.myText
.
public static TextBox myText; //Declare public variable private void docForm_Load(object sender, System.EventArgs e) { myText = documentText; }
SearchForm
exposes the contents of txtSearch
to other objects through a write-only string property.
public String SearchPhrase { set { txtSearch.Text = value;} //Write Only }
DocForm
, as well as any object creating an instance of SearchForm
, can set this property. Now let's look at the remaining code details of the two forms.
When the button on DocForm
is clicked, the application either creates a new instance of SearchForm
or passes control to an existing instance. In both cases, it first checks its text box and passes any highlighted text (SelectedText
) to the SearchForm
object via its SearchPhrase
property (see Listing 6-5). Techniques described in earlier examples are used to create the object and set up a delegate to notify the DocForm
object when the search form object closes.
Example 6-5. Method to Pass Control to Search Form Instance
private void btnSearch_Click(object sender, System.EventArgs e) { //Create instance of search form if it does not exist if (closed) { closed= false; searchForm = new SearchForm(); //Create instance searchForm.TopMost = true; searchForm.Closed += new EventHandler(onSearchClosed); searchForm.StartPosition = FormStartPosition.Manual; searchForm.DesktopLocation = new Point(this.Right-200, this.Top-20); searchForm.SearchPhrase = documentText.SelectedText; searchForm.Show(); } else { searchForm.SearchPhrase = documentText.SelectedText; searchForm.Activate(); } } private void onSearchClosed(object sender, System.EventArgs e) { closed= true; }
Listing 6-6 displays the code executed when the Find Next button is clicked. The search for the next occurrence of the search string can proceed up the document using the LastIndexOf
method or down the document using IndexOf
. Logic is also included to ignore or recognize case sensitivity.
Example 6-6. Search for Matching Text on Another Form
private void btnFind_Click(object sender, System.EventArgs e) { int ib; //Index to indicate position of match string myBuff = DocForm.myText.Text; //Text box contents string searchText= this.txtSearch.Text; //Search string int ln = searchText.Length; //Length of search string if (ln>0) { //Get current location of selected text int selStart = DocForm.myText.SelectionStart; if (selStart >= DocForm.myText.Text.Length) { ib = 0; } else { ib = selStart + ln; } if (!this.chkCase.Checked) //Case-insensitive search { searchText = searchText.ToUpper(); myBuff = myBuff.ToUpper(); } if (this.radDown.Checked)ib = myBuff.IndexOf(searchText,ib); if (this.radUp.Checked && ib>ln-1)ib = myBuff.LastIndexOf(searchText,ib-2,ib-1); if (ib >= 0) //Highlight text on main form { DocForm.myText.SelectionStart = ib; DocForm.myText.SelectionLength = txtSearch.Text.Length; } } }
When a form displays an instance of a modeless form, it does not by default create an explicit relationship between itself and the new form. The forms operate autonomously: They either can be closed (except for a main form, which causes all forms to be closed) or minimized without affecting the other; and the creator form has no easy way to distinguish among instances of the forms it has launched.
Often, however, one form does have a dependency on the other. In the preceding example, the floating search window exists only as a companion to a document that it searches. Its relationship to the form that created it is referred to as an owner-owned relationship. In .NET, this can be more than just a logical relationship. A form has an Owner
property that can be set to the instance of the form that “owns” it. After this relationship is formally established, the behavior of the owner and owned form(s) is linked. For example, the owned form is always visually on top of its owner form. This eliminates the need to make SearchForm
a TopMost
form in our preceding example.
An owner-owned relationship is easily established by setting the Owner
property of a newly created form to the form that is creating it.
opaque opForm = new opaque(); opForm.Owner = this; //Current form now owns new form opForm.Show();
This relationship affects the user's interaction with the form in three ways: The owned form is always on top of the owner form even if the owner is active; closing the owner form also closes the owned form; and minimizing the owner form minimizes all owned forms and results in only one icon in the task bar.
Another advantage of the owner-owned relationship is that an owner form has an OwnedForms
collection that contains all the owned forms it creates. The following example demonstrates how an owner form creates two owned forms, opForm
and opForm2,
and then enumerates the collection to set the caption of each form before displaying it:
opaque opForm = new opaque(); opForm.Owner = this; //Set current form to owner form opaque opForm2 = new opaque(); opForm2.Owner = this; //Set current form to owner form for (int ndx=0; ndx<this.OwnedForms.Length; ndx++) { myForms.Text = "Owner: Form1 - Form"+ndx.ToString(); myForms.Show(); }
Note that although modal forms exhibit the features of an owned form, the Owner
property must be set to establish an explicit relationship.
.NET provides a set of classes and enumerations that make it easy to create a message or dialog window to interact with a user. The simplest approach is to use the MessageBox
class and its versatile Show
method. The other approach is to create a custom form and invoke it with the form's ShowDialog
method. Both of these methods create modal forms.
The MessageBox
class uses its Show
method to display a message box that may contain text, buttons, and even an icon. The Show
method includes these overloads:
Syntax:
static DialogResult Show(string msg) static DialogResult Show(string msg, string caption) static DialogResult Show(string msg, string caption, MessageBoxButtons buttons) static DialogResult Show(string msg, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defBtn)
DialogResult
. The method returns one of the enum members Abort
, Cancel
, Ignore
, No
, None
, OK
, Retry
, and Yes
.
MessageBoxIcon
. This enumeration places an icon on the message box. Members include Asterisk
, Error
, Exclamation
, Hand
, Information
, None
, Question
, Stop
, and Warning
.
MessageBoxButtons
. This is an enumeration with values that determine which buttons are displayed in the message box. Its members are AbortRetryIgnore
, OK
, OKCancel
, RetryCancel
, YesNo
, and YesNoCancel
. The buttons correspond to the text in the member name. For example, YesNo
results in a form with a Yes and No button.
MessageBoxDefaultButton
. This an enumeration that defines the default button on the screen. Its members are Button1
, Button2
, and Button3
.
Figure 6-9, which is created with the following statement, provides a visual summary of these parameter options:
MessageBox.Show("OK to Close", "Game Status", MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question, MessageBoxDefaultButton.Button2 );
Clicking one of the three buttons returns a value of DialogResult.Yes
, DialogResult.No
, or DialogResult.Cancel
, respectively.
The ShowDialog
method permits you to create a custom form that is displayed in modal mode. It is useful when you need a dialog form to display a few custom fields of information. Like the MessageBox
, it uses buttons to communicate with the user.
The form used as the dialog box is a standard form containing any controls you want to place on it. Although not required, the form's buttons are usually implemented to return a DialogResult
enum value. The following code handles the Click
event for the two buttons shown on the form in Figure 6-10:
private void buttonOK_Click(object sender, System.EventArgs e) { this.DialogResult = DialogResult.OK; } private void buttonCancel_Click(object sender, System.EventArgs e) { this.DialogResult = DialogResult.Cancel; }
To complete the form, we also need to set a default button and provide a way for the form to be cancelled if the user presses the Esc key. This is done by setting the form's AcceptButton
and CancelButton
properties in the form's constructor.
AcceptButton = buttonOK; //Button to receive default focus CancelButton = buttonCancel; //Fires when Esc pushed
The code that creates and displays the form is similar to previous examples. The only difference is that the new form instance calls its ShowDialog
method and returns a DialogResult
type result.
customer cust = new customer(); cust.MinimizeBox = false; cust.MaximizeBox = false; if (cust.ShowDialog() == DialogResult.OK) { MessageBox.Show("Returns OK"); } else { MessageBox.Show("Returns Cancel"); }
A Multiple Document Interface (MDI) application is characterized by one application window and several document windows. Structurally, a single container is used to hold multiple documents. To manage the collection of documents, the MDI application includes a menu system with options to open, save, and close a document; switch between documents; and arrange the documents in various visual layouts.
No special classes are required to create an MDI application. The only requirement is that one form be designated the container by setting its IsMdiContainer
property to true
. Child forms are designated by setting their MdiParent
property to the container form.
The MDI form in Figure 6-11 shows the three elements that comprise an MDI form: the parent container; the child form(s); and a menu to manage the creation, selection, and arrangement of the child forms.
The container form is created by including this statement in its constructor:
this.IsMdiContainer = true;
By tradition, child forms are created by selecting an option on the File menu such as File-New or File-Open. The supporting code creates an instance of the child form and sets its MdiParent
property.
invForm myForm = new invForm(); myForm.MdiParent = this; mdiCt += mdiCt; //Count number of forms created myForm.Text= "Invoice" + mdiCt.ToString(); myForm.Show();
A variable that counts the number of forms created is appended to each form's Text
property to uniquely identify it.
A discussion of MDI forms is incomplete without considering the requirements for a menu to manage the windows within the container. Minimally, an MDI parent menu should contain a File section for creating and retrieving forms and a Windows section that lists all child forms and permits form selection.
A basic menu is constructed from two classes: the MainMenu
class that acts as a container for the whole menu structure and the MenuItem
class that represents the menu items in the menu. Both of these classes expose a MenuItems
collection property that is used to create the menu hierarchy by adding subitems to each class that represent the menu options. After the menu items are in place, the next step is to tie them to appropriate event handling routines using the familiar delegate approach. Let's step through an example that demonstrates how to create the menu system shown in Figure 6-12. Afterwards, we'll look at creating the menu in Visual Studio.NET. It is certainly much quicker and easier to use VS.NET, but it is less flexible if you need to create menus at runtime.
The first step is to declare the main menu object and the menu items as class variables. (To avoid repetition, code for all menu items is not shown.)
private MainMenu mainMenu1; private MenuItem menuItem1; //File private MenuItem menuItem2; //Edit private MenuItem menuItem3; //Window private MenuItem menuItem4; //File - New
The main menu and menu items are created inside the class constructor:
this.mainMenu1 = new System.Windows.Forms.MainMenu(); this.menuItem1 = new System.Windows.Forms.MenuItem("File"); this.menuItem2 = new System.Windows.Forms.MenuItem("Edit"); this.menuItem3 = new System.Windows.Forms.MenuItem("Window"); this.menuItem4 = new System.Windows.Forms.MenuItem("New");
Next, the menu hierarchy is established by adding menu items to the main menu object to create the menu bar. The menu bar items then have menu items added to their MenuItems
collection, which creates the drop-down menu.
//Add menu items to main menu object
this.mainMenu1.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {
this.menuItem1,
this.menuItem2,
this.menuItem3});
//Add menu item below File
this.menuItem1.MenuItems.Add(this.menuItem4);
//Add menu items to Window menu item
this.menuItem3.MdiList = true; //Causes child forms to display
this.menuItem3.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {this.menuItem5,
this.menuItem6, this.menuItem7, this.menuItem8});
//Set menu on form
this.Menu = this.mainMenu1;
The main points to observe in this code are:
The Add
and AddRange
methods add a single or multiple menu items to the MenuItems
collection.
Setting a menu item's MdiList
property to true
causes a list of child forms to appear in the menu below that menu item (Invoice1 and Invoice2 are listed in Figure 6-12).
To place a menu on a form, set the form's Menu
property to the MainMenu
object.
The final step is to set up event handling code that provides logic to support the menu operations. Here is the code to define a delegate and method to support an event fired by clicking the File–New menu item. The code creates a new instance of invForm
each time this menu item is clicked.
//Following is defined in constructor MenuItem4.Click += new System.EventHandler(menuItem4_Click); private void menuItem4_Click(object sender, System.EventArgs e) { invForm myForm = new invForm(); myForm.MdiParent = this; mdiCt += mdiCt; //Count number of forms created myForm.Text= "Invoice" + mdiCt.ToString(); myForm.Show(); }
The Window
option on the menu bar has submenu items that let you rearrange the child forms within the MDI container. The LayoutMdi
method of a form makes implementing this almost trivial. After setting up delegates in the usual manner, create the event handling routines:
private void menuItem6_Click(object sender, System.EventArgs e){ this.LayoutMdi(MdiLayout.ArrangeIcons); } private void menuItem6_Click(object sender, System.EventArgs e){ this.LayoutMdi(MdiLayout.Cascade); } private void menuItem7_Click(object sender, System.EventArgs e){ this.LayoutMdi(MdiLayout.TileHorizontal); } private void menuItem8_Click(object sender, System.EventArgs e){ this.LayoutMdi(MdiLayout.TileVertical); }
The methods reorder the window by passing the appropriate MdiLayout
enumeration value to the LayoutMdi
method.
With the Form Designer open, double click the MainMenu icon in the Toolbox window. Two things happen: An icon appears in the component tray at the bottom and a menu template appears docked to the top of the form. Type the menu item titles into the cells that appear (see Figure 6-13). The top horizontal row of cells represents the menu bar; by moving vertically, you create drop-down menu items below the top-level items. After typing in the item name, double click the cell to create a Click
event for the menu item. VS.NET creates the delegate and method stub automatically.
Use the Property Window (press F4), which displays properties of the active cell, to change the default names assigned to menu items and set any other values.
The previous section provided a solid introduction to menus. This section adds a checklist of MenuItem
properties that affect an item's appearance and describes how to use the ContextMenu
class.
The .NET menu system is designed with the utilitarian philosophy that the value of a thing depends on its utility. Its menu item is not a thing of beauty, but it works. Here are some of its more useful properties:
Enabled
. Setting this to false
, grays out the button and makes it unavailable.
Checked
. Places a checkmark beside the menu item text.
RadioCheck
. Places a radio button beside the menu item text; Checked
must also be true
.
BreakBar
or Break
. Setting this to true
places the menu item in a new column.
Shortcut
. Defines a shortcut key from one of the Shortcut
enum members. These members represent a key or key combination (such as Shortcut.AltF10)
that causes the menu item to be selected when the keys are pressed. On a related matter, note that you can also place an & in front of a letter in the menu item text to produce a hot key that causes the item to be selected by pressing Alt-letter.
In addition to the MainMenu
and MenuItem
classes that have been discussed, there is a ContextMenu
class that also inherits from the Menu
class. This ContextMenu
class is associated with individual controls and is used most often to provide a contextsensitive pop-up menu when the user right-clicks on a control.
The statements to construct a menu based on ContextMenu
are the same as with a MainMenu
. The only difference is that visually there is no top-level menu bar, and the menu is displayed near the control where it is invoked.
A menu can be associated with multiple controls, or each control can have its own menu. Typically, one menu is used for each control type. For example, you might have a context menu for all
TextBox
controls, and another for buttons. To illustrate, let's create a menu that colors the background of a TextBox
control (see Figure 6-14).
Creating a context menu is similar to creating a MainMenu
. If using VS.NET, you drag the ContextMenu
control to the form and visually add menu items. If coding by hand, you create an instance of the ContextMenu
class and add menu items using the MenuItems.Add
method. Following is a sampling of the code used to create the menu. Note that a single method handles the Click
event on each menu item.
private ContextMenu contextMenu1; //Context menu private TextBox txtSearch; //Text box that will use menu // Following is in constructor contextMenu1 = new ContextMenu(); // Add menu items and event handler using Add method contextMenu1.MenuItems.Add("Azure Background", new System.EventHandler(this.menuItem_Click)); contextMenu1.MenuItems.Add("White Background", new System.EventHandler(this.menuItem_Click)); contextMenu1.MenuItems.Add("Beige Background", new System.EventHandler(this.menuItem_Click));
The completed menu is attached to a control by setting the control's ContextMenu
property to the context menu:
//Associate text box with a context menu this.txtSearch.ContextMenu = this.contextMenu1;
A right-click on txtSearch
causes the menu to pop up. Click one of the menu items and this event handling routine is called:
private void menuItem_Click(object sender, System.EventArgs e) { //Sender identifies specific menu item selected MenuItem conMi = (MenuItem) sender; string txt = conMi.Text; //SourceControl is control associated with this event if(txt == "Azure Background") this.contextMenu1.SourceControl.BackColor = Color .Azure; if(txt == "White Background") this.contextMenu1.SourceControl.BackColor = Color .White; if(txt == "Beige Background") this.contextMenu1.SourceControl.BackColor = Color .Beige; }
The two most important things to note in this example are that the argument sender
identifies the selected menu item and that the context menu property SourceControl
identifies the control associated with the event. This capability to identify the control and the menu item enables one method to handle events from any control on the form or any menu item in the context menu.
The majority of software users do not read documentation, except as a last resort. Users expect a program to be intuitive and provide context-sensitive documentation where it is needed. In addition to the Help option on the main menu bar, a polished program should provide assistance down to the individual controls on the form.
.NET offers multiple ways to configure an integrated help system:
Easy-to-use ToolTips that are displayed when the mouse moves over a control. These are specified as a control property provided by the ToolTip
component.
The HelpProvider component is an “extender” that adds properties to existing controls. These properties enable the control to reference Microsoft HTML Help (.chm
) files.
A custom event handler can be written to implement code that explicitly handles the Control.HelpRequested
event that is fired by pressing the F1 key or using a Help button.
This component adds mouseover capabilities to controls. If using VS.NET, you simply select the ToolTip
control from the tool box and add it to your form. The effect of this is to add a string
property (ToolTip on toolTip1
) to each control, whose value is displayed when the mouse hovers over the control.
Of more interest is using ToolTips within a program to dynamically provide annotation for objects on a screen. Maps, which can be created dynamically in response to user requests, offer a prime example. They typically contain points of interest implemented as labels or picture boxes. As an example, consider the display in Figure 6-15, which shows a constellation and labels for its most important stars. When a user passes the cursor over the label, tool tip text describing the star is shown.
Listing 6-7 shows a portion of the code that creates the form displayed in the figure. The form's BackGroundImage
property is set to the image representing the constellation. Labels are placed on top of this that correspond to the position of three stars (code for only one star is shown). The Tag
property of each label is set to the description of the star, and a ToolTip
associates this information with the label using the SetToolTip
method.
Example 6-7. Using ToolTips to Annotate an Image
public class StarMap:Form
{
public StarMap()
{
this.Text = "Star Search";
this.Width=400;
this.Height=220;
// Place image of constellation on form
this.BackgroundImage= new Bitmap(@"c:dracoblack.gif");
// Add name of star on Label
Label star1 = new Label();
Star1.Location = new Point(285,115);
Star1.Size = new Size(60,16);
star1.Text = "Thuban";
star1.Tag = " Alpha Draconis
> Magnitude: 3.67
>"+
" 310 Light Years";
star1.Font = new Font(star.Font, star.Font.Style |
FontStyle.Bold);
this.Controls.Add(star1);
ToolTip toolTip1 = new ToolTip();
toolTip1.AutoPopDelay= 1500; // Tool tip displays
// for 1.5 secs.
// Tool tip text comes from Tag property of Label
toolTip1.SetToolTip(star1, star1.Tag.ToString());
// Add labels for other stars Etamin and Gianfar here ...
}
}
Many users regard the F1 key as a de facto way to invoke help. .NET provides built-in F1 support by causing a Control.HelpRequested
event to fire when the user presses the F1 key. This event also fires when a user clicks the context-sensitive Help button at the top of a form and then clicks on a control using the Help cursor. See Figure 6-16.
The Help button is displayed by setting these form properties:
Set MinimizeBox
and MaxmizeBox
to false
.
Set HelpButton
to true
.
A recommended approach is to create one event handler routine and have each control invoke it. As an example, the following code defines delegates for two text boxes that notify the ShowHelp
method when the HelpRequested
event occurs. This method uses either a Tag
property associated with each control or the control's name to specify help germane to that control.
this.date.HelpRequested += new HelpEventHandler(ShowHelp);
this.ssn.HelpRequested += new HelpEventHandler(ShowHelp);
this.ssn.Tag = "Enter as: nnn-nn-nnnn";
this.date.Tag = "Enter as: mm/dd/yyyy";
private void ShowHelp(object sender, HelpEventArgs e)
{
Control reqControl = (Control)sender;
// Technique 1: Use tag associated with control
MessageBox.Show(reqControl.Tag.ToString());
// Technique 2: Link to specific text within a CHM file
string anchor = "#"+reqControl.Name;
// ShowHelp invokes a compiled Help file
Help.ShowHelp(reqControl,@"c:ctest.chm",HelpNavigator.Topic,
"customers.htm"+anchor);
e.Handled = true; // Always set this
}
The event handler receives two arguments: the familiar sender
that identifies the control that has focus when the event occurs and HelpEventArgs
, which has Handled
and MousePos
as its only two properties. Handled
is set to indicate the event has been handled. MousePos
is a Point
type that specifies the location of the cursor on the form.
The method provides context-sensitive help by identifying the active control and using this knowledge to select the Help text to be displayed. In this example, the first technique displays the tag
property of a control as the Help message. The second—and more interesting technique—uses Help.ShowHelp
to display a section of an HTML file that uses the control's name as an anchor tag. Specifically, it looks inside ctest.chm
for the customers.htm
page. Then, it searches that page for a named anchor tag such as <a name=ssn>
. If found, it displays the HTML at that location.
ShowHelp
is the most useful method of the Help
class. It includes several overloads to show compiled Help files (.chm
) or HTML files in an HTML Help format.
// URL may be .chm file or html file public static void ShowHelp(Control parent, string url); // HelpNavigator defines the type of .chm file to be displayed public static void ShowHelp(Control parent, string url, HelpNavigator navigator); // Displays contents of Help file for a specified keyword public static void ShowHelp(Control parent, string url, string keyword); // param is used with HelpNavigator.Topic to refine selection public static void ShowHelp(Control parent, string url, HelpNavigator navigator, object param);
The HelpNavigator
enumeration specifies which part of a .chm
file is displayed. It's values include TableofContents
, Find
, Index
, and Topic
. If you are unfamiliar with them, compiled Help files package multiple HTML files along with an optional table of contents and keyword indexes. The downloadable Microsoft HTML Help Workshop is the easiest way to learn how to use and create these files.
This component is a wrapper that is used primarily with Visual Studio.NET. Its main value is that it eliminates the need to explicitly handle the HelpRequested
event. It is an extender that adds several properties to each control. These properties essentially correspond to the parameters in the ShowHelp
method, which it calls underneath.
Add the HelpProvider
to a form by selecting it from the tool box. Then, set its HelpNameSpace
property to the name of the HTML or .chm
file that the underlying ShowHelp
method should reference (this corresponds to the URL parameter).
Each control on the form adds four extended properties:
ShowHelp
. Set to true
to make activate Help feature.
HelpNavigator
. Takes the HelpNavigator
enumeration value.
HelpKeyword
. Corresponds to the param
or keyword
parameter in ShowHelp
.
HelpString
. This contains a message that is displayed when the Help button is used to click a control.
Help is not enabled on a control that has ShowHelp
set to false
. If it is set to true
, but the other properties are not set, the file referenced in HelpNameSpace
is displayed. A popular Help configuration is to set only the HelpString
value so that the Help button brings up a short specific message and F1 brings up an HTML page.
Just as a class inherits from a base class, a GUI form—which is also a class—can inherit the settings, properties, and control layout of a preexisting form. This means that you can create forms with standard features to serve as templates for derived forms. Before looking at the details of inheritance, let's first examine how to store a set of base forms in a code library and organize them by namespace.
Each form consists of a physical .cs
file. A library of multiple forms is created by compiling each .cs
file into a common .dll
file. After this is done, the forms can be accessed by any compliant language—not just the one they are written in.
As an example, let's use the compiler from the command line to compile two forms into a single .dll
file:
csc /t:library product.cs customer.cs /out:ADCFormLib.dll
A base form must provide a namespace for the derived form to reference it. The following code defines a Products
namespace for our example:
namespace Products { public class ProductForm : System.Windows.Forms.Form {
To inherit this form, a class uses the standard inheritance syntax and designates the base class by its namespace and class name:
// User Form derived from base class Form class NewProductForm: Products.ProductForm {
As a final step, the compiler must be given a reference to the external assembly ADCFormLib
so that the base class can be located. If using VS.NET, you use the Project-AddReference menu option to specify the assembly; from the command line, the reference
flag is used.
csc /t:winexe /r:ADCFormLib.dll myApp.cs
If the derived form provides no additional code, it generates a form identical to its base form when executed. Of course, the derived form is free to add controls and supporting code. The only restriction is that menu items cannot be added to an existing menu; however, an entire menu can be added to the form and even replace an existing one on the base form.
The properties of inherited controls can be changed, but their default access modifier of private
must first be changed to protected
, and the base form then recompiled. The derived form is then free to make modifications: It may reposition the control or even set its Visible
property to false
to keep it from being displayed.
Suppose the base form contains a button that responds to a click by calling event handler code to close the form. However, in your derived form, you want to add some data verification checks before the form closes. One's instinct is to add a delegate and event handler method to respond to the button Click
event in the derived form. However, this does not override the original event handler in the base form, and both event handling routines get called. The solution is to restructure the original event handler to call a virtual method that can be overridden in the derived form. Here is sample code for the base form:
private void btn1_Clicked(object sender, System.EventArgs e) { ButtonClicks(); // Have virtual method do actual work } protected virtual void ButtonClicks() { this.Close(); }
The derived form simply overrides the virtual method and includes its own code to handle the event:
protected override void ButtonClicks() { // Code to perform any data validation this.Close(); }
To create an inherited form using Visual Studio, open a project containing the form to serve as the base and compile it. Then, select Project-Add Inherited Form from the menu. Give the new form a name and open it. Next, an Inheritance Picker dialog box appears that lists the eligible base forms. Use the Browse feature to display forms that are in external libraries.
Select a base form and the new inherited form appears in the designer window. All of the controls on the form are marked with a plus (+
), and you will find that only properties on the form itself are exposed. At this point, you can add controls or modify existing ones using the techniques just described.
Despite the migration of applications to the Internet, there is still a compelling need for Windows Forms programming. Windows Forms provide features and functionality superior to those of Web Forms. Moreover, the majority of real-world applications continue to run on local area networks. The .NET Framework Class Library provides a rich set of classes to support forms programming. The Control
class at the top of the hierarchy provides the basic properties, methods, and events that allow controls to be positioned and manipulated on a form. Keyboard and mouse events enable a program to recognize any keys or mouse buttons that are clicked, as well as cursor position.
The Form
class inherits all of the Control
class members and adds to it properties that specify form appearance, position, and relationship with other forms. A form created by another form may be modal, which means it does not relinquish focus until it is closed, or modeless, in which case focus can be given to either form. In a multiple document interface (MDI) application, one form serves as a container to hold child forms. The container usually provides a menu for selecting or rearranging its child forms.
.NET includes a HelpRequested
event that is fired when a user pushes the F1 key. This can be combined with the Help.ShowHelp
method, which supports compiled HTML (.chm
) files, to enable a developer to provide context-sensitive help on a form.