Chapter 6. Building Windows Forms Applications

Topics in This Chapter

  • IntroductionWith 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 ControlsAll 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 ClassThe Form class includes custom properties that affect its appearance, and enable it to work with menus and manage child forms.

  • Message and Dialog BoxesA pop-up window to provide user interaction or information can be created as a message or dialog box.

  • MDI FormsA 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 FormHelp buttons, ToolTips, and HTML Help are options for adding help to a form.

  • Form InheritanceVisual 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.FormsWindows Forms (WinForms).

  • System.Web.UIWebControlsWeb Forms.

  • System.Web.UIMobileControlsMobile 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.”

Programming a Windows Form

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.

Building a Windows Forms Application by Hand

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.

Introductory Windows application

Figure 6-1. Introductory Windows application

The code breaks logically into three sections:

  1. 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.

  2. 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.

  3. Handle Button Click Event.

    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.

Core Note

Core Note

.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.

Windows.Forms Control Classes

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).

Windows Forms class hierarchy

Figure 6-2. Windows Forms class hierarchy

The Control Class

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.

Control Properties

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

Size

A Size object that exposes the width and height.

Location

Location is a Point object that specifies the x and y coordinates of the top left of the control.

Width, Height, Top, Left, Right

These int values are derived from the size and location of the object. For example, Right is equal to Left + Width; Bottom is equal to Top + Height.

Bounds

Bounds is a rectangle that defines a control's position and size:

Button.Bounds =
   new Rectangle (10,10,50,60)

ClientRectangle

ClientRectangle is a rectangle that represents the client area of the control—that is, the area excluding scroll bars, titles, and so on.

Anchor

Specifies which edges of the container the control is anchored to. This is useful when the container is resized.

Dock

Controls which edge of the container the control docks to.

Color and appearance

BackColor, ForeColor

Specifies the background and foreground color for the control. Color is specified as a static property of the Color structure.

BackGroundImage

BackGroundImage specifies an image to be used in the background of the control.

Text

Text

Text associated with the control.

Font

Font describes the characteristics of the text font: typeface, size, bold, and so on.

Focus

TabIndex

int value that indicates the tab order of this control within its container.

TabStop

Boolean value that indicates whether this control can receive focus from the Tab key.

Focused

Boolean value indicating whether a control has input focus.

Visible

Boolean value indicating whether the control is displayed.

Keyboard and mouse

MouseButtons

Returns the current state of the mouse buttons (left, right, and middle).

MousePosition

Returns a Point type that specifies the cursor position.

ModifierKeys

Indicates which of the modifier keys (Shift, Ctrl, Alt) is pressed.

Cursor

Specifies the shape of the mouse pointer when it is over the control. Assigned value is a static property of the Cursors class. These include:

.Hand    .Cross   UpArrow      Default
.Beam    .Arrow   WaitCursor

Runtime status

Handle

int value representing the handle of Windows control.

Focused

bool value indicating whether the control has focus.

Working with Controls

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.

Size and Position

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.

How to Anchor and Dock a Control

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.

Control resizing and positioning using the Dock property

Figure 6-3. Control resizing and positioning using the Dock property

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.

Core Note

Core Note

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.

How anchoring affects the resizing and positioning of controls

Figure 6-4. How anchoring affects the resizing and positioning of controls

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 and Focus

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.

Tab order for controls on a form

Figure 6-5. Tab order for controls on a form

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(); }

Iterating Through Controls on a Form

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.

Control Events

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.

KeyPress

KeyPressEventHandler (
   object sender, 
   KeyPressEventArgs e)

Event triggered by pressing any key.

Handling Mouse Events

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;
}

Core Note

Core Note

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

Button

Indicates which mouse button was pressed. The attribute value is defined by the MouseButtons enumeration: Left, Middle, None, Right

Clicks

Number of clicks since last event.

Delta

Number of detents the mouse wheel rotates. A positive number means it's moving forward; a negative number shows backward motion.

X, Y

Mouse coordinates relative to the container's upper-left corner. This is equivalent to the control's MousePosition property.

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.

Handling Keyboard Events

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.

Handled

Boolean value that indicates whether an event was handled.

KeyCode

Returns the key code for the event. This code is of the Keys enumeration type.

KeyData

Returns the key data for the event. This is also of the Keys enumeration type, but differs from the KeyCode in that it recognizes multiple keys.

Modifiers

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 Class

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

FormBorderStyle

Gets or sets the border style of the form. It is defined by the FormBorderStyle enumeration:

Fixed3D        None
FixedSingle    Sizable
FixedDialog

ControlBox

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.

Opacity

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.

TransparencyKey

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

AutoScale

Indicates whether the form adjusts its size to accommodate the size of the font used.

ClientSize

Size of the form excluding borders and the title bar.

DesktopLocation

A Point type that indicates where the form is located on the desktop window.

StartPosition

Specifies the initial position of a form. It takes a FormStartPosition enum value:

  • CenterParent—. Centered within bounds of parent form.

  • CenterScreen—. Centered within the display.

  • Manual—. Use the DeskTopLocation value.

  • Windows DefaultLocation—. Windows sets value.

MinimumSize
MaximumSize

A Size object that designates the maximum and minimum size for the form. A value of (0,0) indicates no minimum or maximum.

ShowInTaskBar

Boolean value specifying whether application is represented in Windows task bar. Default is true.

TopLevel
TopMost

Indicates whether to display the form as a TopLevel window or TopMost window. A top-level window has no parent; top-most form is always displayed on top of all other non-TopMost forms.

WindowState

Indicates how the form is displayed on startup. It takes a value from the FormWindowState enumeration: Maximized, Minimized, or Normal.

Owner forms

Owner

The form designated as the owner of the form.

OwnedForms

A Form array containing the forms owned by a form.

Setting a Form's Appearance

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.

Properties to control what appears on the title bar

Figure 6-6. Properties to control what appears on the title bar

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.

Form Opacity

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;   }

Form Transparency

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.

Form using transparency to create irregular appearance

Figure 6-7. Form using transparency to create irregular appearance

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.

Setting Form Location and Size

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 InitializeComponent method is called to initialize the form.

Form displayed:

Form.Show()
Form.Load
Form.Activated

The Load event is called first, followed by the Activated event.

Form activated

Form.Activated

This occurs when the user selects the form. This becomes an “active” form.

Form deactivated

Form.Deactivate

Form is deactivated when it loses focus.

Form closed

Form.Deactivate
Form.Closing
Form.Closed

Form is closed by executing Form.Close or clicking on the form's close button.

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.

Core Note

Core Note

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.

Displaying Forms

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.

The Life Cycle of a Modeless Form

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.

Creating and Displaying a Form

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
}

Form Activation and Deactivation

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;   }

Closing a Form

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;
   }
}

Forms Interaction—A Sample Application

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.

Text search application using multiple forms

Figure 6-8. Text search application using multiple forms

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.

Code for the Main Form

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;   }

Code for the Search Form

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;
      }
   }
}

Owner and Owned Forms

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.

Message and Dialog Boxes

.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.

MessageBox

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.

MessageBox

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 );
MessageBox.Show example

Figure 6-9. MessageBox.Show example

Clicking one of the three buttons returns a value of DialogResult.Yes, DialogResult.No, or DialogResult.Cancel, respectively.

ShowDialog

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; }
Creating a menu with VS.NET

Figure 6-10. Creating a menu with VS.NET

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"); }

Multiple Document Interface Forms

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.

MDI form

Figure 6-11. MDI form

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.

Creating a Menu and MDI Form

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.

MDI Form menu

Figure 6-12. MDI Form menu

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.

Creating an MDI Menu Using VisualStudio.NET

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.

Creating a menu with VS.NET

Figure 6-13. Creating a menu with VS.NET

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.

Working with Menus

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.

MenuItem Properties

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:

EnabledSetting this to false, grays out the button and makes it unavailable.

CheckedPlaces a checkmark beside the menu item text.

RadioCheckPlaces a radio button beside the menu item text; Checked must also be true.

BreakBar or BreakSetting this to true places the menu item in a new column.

ShortcutDefines 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.

Context Menus

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).

Context menu

Figure 6-14. Context menu

Constructing a Context Menu

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
Constructing a Context Menu.Azure;
   if(txt == "White Background")        this.contextMenu1.SourceControl.BackColor = Color
Constructing a Context Menu.White;
   if(txt == "Beige Background")        this.contextMenu1.SourceControl.BackColor = Color
Constructing a Context Menu.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.

Adding Help to a Form

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.

ToolTips

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.

ToolTip information is displayed when mouse passes over name

Figure 6-15. ToolTip information is displayed when mouse passes over name

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 ...
   }
}

Core Note

Core Note

To dynamically change a control's tool tip value, you must get an instance of the control's ToolTip, execute its RemoveAll method, and then use SetToolTip to reset the value of the tool tip string.

Responding to F1 and the Help Button

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

Figure 6-16. The Help button

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.

The HelpProvider Component

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:

  1. ShowHelpSet to true to make activate Help feature.

  2. HelpNavigatorTakes the HelpNavigator enumeration value.

  3. HelpKeywordCorresponds to the param or keyword parameter in ShowHelp.

  4. HelpStringThis 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.

Forms Inheritance

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.

Building and Using a Forms Library

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

Using the Inherited Form

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.

Overriding Events

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();
}

Creating Inherited Forms with Visual Studio.NET

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.

Summary

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.

Test Your Understanding

1:

From which class must a Windows Forms application inherit?

2:

Describe the difference between anchoring and docking.

3:

What MouseEventArgs properties are used to identify the mouse coordinates and button clicked?

4:

Which Form property is used to create an irregular shaped form?

5:

What is the primary difference between a modal and modeless form?

6:

How does creating an owner-owned form relationship affect the behavior of the related forms?

7:

What form properties must be set in order to display a Help button?

8:

Compare using a Help button and a ToolTip.

9:

Describe how a base form can structure its event handling code so that an inherited form can override an event.

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

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