Programming Microsoft Windows Ce Net 3Rd [Electronic resources]

نسخه متنی -صفحه : 169/ 137
نمايش فراداده

Windows Forms Applications

Almost every managed application created for the Compact Framework will be a Windows Forms application. Because of this, this section of the Framework deserves some special attention. Windows Forms applications relate to simple managed applications as Windows applications relate to console applications. Windows Forms applications create one or more windows on the Windows desktop. This top-level window is referred to as a form. The form almost always contains many child windows, typically predefined controls provided by the Framework but also controls created by the application.

Visual Studio makes creating Windows Forms applications almost trivial with its powerful application designer. While all programmers, including me, use the designer to create Windows Forms applications, understanding the underlying code is important. For this reason, I'm going to briefly dive into the nuts and bolts of Windows Forms applications, not from a designer perspective, but from a code perspective. This short introduction can provide all the details inquisitive programmers would want. The best source of how Windows Forms applications work is Charles Petzold's two books Programming Microsoft Windows with C# and Programming Microsoft Windows with Visual Basic .NET, both from Microsoft Press.

The Windows Forms library can be considered analogous to the GWE functionality of Windows CE. It provides extensive classes for creating windows and controls as well as drawing elements such as brushes, pens, and bitmaps. The limitations in the Windows Forms classes center around the reduction in the exposed methods and properties that are available on the desktop version of the FCL.

One significant difference in the Compact Framework implementation of the Windows Forms classes is the lack of a WndProc method in the Control class. On the desktop, this method can be overridden so that the managed application can intercept any unmanaged window message, such as WM_SIZE, and deal with the message as the managed code sees fit. On the Compact Framework, the Control class does not expose this method, effectively isolating the managed code from the underlying operating system. Even using techniques to discover the true window handle of a managed control can be dangerous. The Compact Framework pools window handles and can reuse them unexpectedly. To throw a bone to those programmers who need access to window messages, a special Compact Framework class, the MessageWindow class, is supported that does expose the WndProc method. That class is discussed later in this chapter.

Just as Windows applications have a message queue and a corresponding message loop to handle the messages, Windows Forms applications also manage window messages, although in the case of managed applications the Framework hides the grisly details of the message loop.

A Basic Windows Forms Application

A Windows Forms application is signified by the declaration and creation of one or more form classes along with a call to the Run method of the Application class to start the window message–processing infrastructure under the covers. A trivial Windows Forms application is shown here:

using System;
using System.Windows.Forms;
namespace FirstWindowsForms
{
class WindowsForms1 : Form
{
static void Main()
{
WindowsForms1 f = new WindowsForms1();
f.Text = "Form Title";
Application.Run (f);
}
}
}

The structure of the application looks somewhat strange because a method in the class is actually creating an instance of the class; however, this is the traditional structure of a Windows Forms application. The Run method of the Application class is a statically defined method that starts the message loop processing under the covers of the application. Like a message loop, the Run method doesn't return until the form passed as its single parameter is destroyed. When this happens, the Run method returns, Main exits, and the application terminates.

The Form class is derived from the Control class, which is the basis for windows in a Windows Forms application. The methods and properties of the Control and Form class are too numerous to list in this short discussion, but they provide the typical information that would be expected for a window, such as size, position, client area, window text, and such. Figure 23-1 shows this first Windows Forms application as it appears on an embedded Windows CE platform.

Figure 23-1: A simple Windows Forms application

Painting

The Control class, and by inheritance the Form class, contains a series of methods that can be overridden to customize the look and feel of a window. Most of these methods correspond to window messages. For example, the OnPaint method is called when the form needs to paint some region of its window. In the following code, the OnPaint method is overridden to draw an ellipse in the window.

using System;
using System.Drawing;
using System.Windows.Forms;
namespace FirstWindowsForms
{
class WindowsForms1 : Form
{
static void Main()
{
WindowsForms1 f = new WindowsForms1();
f.Text = "Form Title";
Application.Run (f);
}
private Color c = Color.Blue;
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle (0, 0,
ClientRectangle.Width - 1,
ClientRectangle.Height - 1);
Brush br = new SolidBrush (c);
e.Graphics.FillEllipse (br, rc);
br.Dispose();
base.OnPaint (e);
}
}
}

Figure 23-2 shows the results of the code.

Figure 23-2: A Windows Forms application that draws an ellipse in its form

The ellipse is drawn in the OnPaint method. The single PaintEventArgs parameter provides two properties: the self-explanatory ClipRectangle and an instance of a Graphics class. The Graphics class wraps the device context (DC) for the window. The Graphics class contains a large number of properties and methods for integrating the state of the device context and drawing in the DC. The preceding simple example uses the DrawEllipse method to draw the ellipse on the form.

Adding Controls

Many Windows Forms applications never override the OnPaint method. Instead, they create forms with a number of controls that provide the entire user interface needed for the application. Providing a control on a form is a multistep process. First a member variable of the specific control class type is defined in the form class. An instance of the control is then created, and the necessary properties of that form are initialized. The control is then added to the control collection of the form. Finally the form handles any relevant events that the control fires.

In the following example, a button control is added to the form so that when it is pressed, the color of the ellipse changes from red to green to blue. First a member variable is defined of class Button, as shown here:

protected Button btnChangeColor;

The member variable is declared as protected because although there is no need for outside classes to manipulate the control, if the program were rewritten to derive a class from the form, the derived form might want to access the button.

Next an instance of the button must be created. This is best done when the form class is created. Once the form class is created, the size and location of the button are set along with its text. The following code shows the form constructor routine that creates and initializes the instance of the button:

public WindowsForms1 ()
{
btnChangeColor = new Button();
btnChangeColor.Location = new Point (5, 5);
btnChangeColor.Size = new Size (70, 25);
btnChangeColor.Text = "Click Me!";
}

The location and size properties of the button are initialized with Point and Size structures, respectively. These structures need to be created—hence the new keyword that creates and, in combination with their constructor routines, initializes the structures.

A common mistake of programmers who hand-generate Windows Forms code is to forget the next step: the control that has been created must be added to the collection of controls that the form class owns. This task is accomplished with the following line of code, which must be placed after the child control has been created:

this.Controls.Add (btnChangeColor);

All classes based on Control, including Form, have a Controls property, which is a ControlCollection class that maintains a list of the controls in the window. The ControlCollections class has an Add method that is used to add a new control to the list. At this point, the application can be recompiled and the button will appear in the window. Figure 23-3 shows the window with the button in the upper left corner.

Figure 23-3: The Windows Forms application with a button in the upper left corner of the form

Adding an Event Handler

The control has been added, but clicking on it has no effect. To have the application be notified when the user clicks the button, an event handler needs to be added to the code. While the designer makes adding an event handler a point-and-click affair, programmers should understand the underlying code.

Adding an event handler is done in two parts: first the event handler routine has to be added to the application, and second the routine has to hook the event chain for the control. The delegate of the event handler depends on the event being handled, but traditionally, Windows Forms controls throw events with a delegate that looks similar to the following:

void EventHandler (object o, EventArgs e);

The object is the source of the event, and EventArgs is a dummy class that, although providing no additional information, is the base class for argument classes that do provide information. For example, a mouse event passes a MouseEventArgs class derived from EventArgs that provides information such as the mouse coordinates. To add a button click handler, the default function prototype is used since the button click event doesn't return any additional data. The handler for the click event is shown here:

protected void MyClickEventHandler (object o, EventArgs e)
{
if (c == Color.Blue)
c = Color.Red;
else if (c == Color.Red)
c = Color.Green;
else if (c == Color.Green)
c = Color.Blue;
this.Invalidate();
}

For the second part, adding the event handler to the list of handlers, the following single line is used:

btnChangeColor.Click += new EventHandler(MyClickEventHandler);

This code creates a new event handler that is then added to the button's Click event. When the example is now recompiled and run, clicking on the button will cause the ellipse to be redrawn in a different color.

Where Is All This Code?

So, you might ask, where is all this code when I use the designer to generate a Windows Forms application? If you check out the code of any designer-generated Windows Forms application in Visual Studio, you'll find the following lines of code:

protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
Application.Run(new Form1());
}

Notice the line #region Windows Form Designer generated code. This line hides a large amount of autogenerated code that the designer inserts in the application as controls are dragged and dropped into the form by the developer.

Opening up that region of hidden code by clicking on the line shows that the designer-generated code simply sets the location, size, and other properties of the controls, as was done in the preceding examples. One of the interesting features of the .NET languages is that all information on the layout of the forms is contained in the source code, not in a separate resource file as is done in unmanaged Windows code. Resources are available in managed applications, but they're used for storing language-specific strings, cursors, bitmaps, and such; they're not used for dialog box templates.

Configuring a Top-Level Form

The Compact Framework supports a handful of different methods for configuring the look and feel of the top-level form. Forms can be made to cover the full screen, have an OK button instead of a smart Close button, and, on non–Pocket PC systems, change the default size and location of the window.

On a Pocket PC, the standard smart Minimize button, which looks like a Close box but actually minimizes the application, can be converted to an OK button that closes the window by setting the MinimizeBox property to false. The button can be removed completely by setting the ControlBox property to false.

To hide the navigation bar, set the form's FormWindowState property to Maximized. The only other setting supported is the default state, Normal. You can remove the menu bar by deleting the MenuBar class, which the designer adds automatically. In addition, the size and location of the top-level form can't be changed on a Pocket PC unless the FormBorderStyle property is set to null. In this case, the form can be placed anywhere on the desktop, but it won't have a border.

On embedded systems, the properties have a somewhat different action. Setting the ControlBox property to false removes the caption bar. Setting the MinimizeBox and MaximizeBox parameters to false removes the Minimize and Maximize buttons from the caption bar. Setting the WindowState parameter to Maximized maximizes the window. Adding a MenuBar class to the form causes a command bar control to be placed at the top of the client area of the form. Finally, the Size and Location parameters are used, even if the ControlBox parameter is true.