• No results found

A typical Windows Forms application has at least one form. Without the form, it’s just an

“application,” which is pretty boring. A form is simply a window, the unit of the Microsoft user interface we’ve seen since Windows 1.0.

One form in a Windows Forms application is typically the main form, meaning that it is either the parent or the owner of all other forms that may be shown during the lifetime of the application.1 It’s where the main menu is shown, along with the tool strip, status strip, and so on. Typically, the main form also governs the lifetime of a Windows Forms applica-tion; generally when an application starts, the main form is opened, and when the main form is closed, the application exits.

The main form of an application can be a simple message box, a dialog, a Single Docu-ment Interface (SDI) window, a Multiple DocuDocu-ment Interface (MDI) window, or something

1

1 The distinction between a form’s “parent” and its “owner” is covered in detail in Chapter 2: Forms.

more complicated (such as the forms you’re used to seeing in applications like VS05). These latter forms may include multiple child windows, tool windows, and floating tool strips.

If your application is simple, you can implement it using the staple of any windowing system, the lowly message box:

class MyFirstApp { static void Main() {

System.Windows.Forms.MessageBox.Show("Hello, Windows Forms!");

} }

If you’re new to C#, Main is the entry point for any C# application.2 The Main method must be a member of a class, and hence the need for MyFirstApp. However, the .NET run-time doesn’t create an instance of the MyFirstApp class when our code is loaded and exe-cuted, so our Main method must be marked static. In this way, you mark a method as available without requiring the instantiation of the type that exposes it.

The single line of real code in our first Windows Forms application calls the static Show method of the MessageBox class contained within the System.Windows.Forms namespace. Namespaces are used extensively in the .NET Framework Class Libraries (.NET Framework) to separate types such as classes, structures, and enumerations into logical groupings. This separation is necessary when you have thousands of Microsoft employees working on the .NET Framework, hundreds of third parties extending it, and millions of programmers trying to learn it. Without namespaces, you would need all kinds of wacky conventions to keep things uniquely named (as demonstrated by the existing Win32 API).

However, as necessary as namespaces are, they require a little too much typing for me, so I recommend the C# using statement:

using System.Windows.Forms; // For MessageBox

class MyFirstApp { static void Main() {

MessageBox.Show("Hello, Windows Forms!");

} }

When the compiler sees that the MessageBox class is being used, it first looks in the global namespace, which is where all types end up that aren’t contained by a namespace (for

2 The entry point is the method that the Common Language Runtime (CLR) calls when an application is launched.

For details, refer to Essential .NET (Addison-Wesley, 2003), by Don Box, with Chris Sells.

example, the MyFirstApp class is in the global namespace). If the compiler can’t find the type in the global namespace, it looks at all the namespaces currently being used—in this case, System.Windows.Forms. If the compiler finds a type name being used that exists in two or more namespaces, it produces an error and we’re forced to go back to the long notation. In practice, this is rare enough to make the short notation predominant when you type code by hand.

However, even though the MessageBox class is enormously handy for showing your users simple string information it’s hard to build a real application with MessageBox. For most things, you need an instance of the Form class, located in System.Windows.Forms:

using System.Windows.Forms; // For Form

class MyFirstApp { static void Main() {

Form form = new Form();

form.Show(); // Not what you want to do }

}

Although this code shows the form, you’ll have to be quick to see it because the Show method displays the form modelessly. If you’re not steeped in user interface lore, a mode-less form is one that’s displayed while allowing other activities (called modes) to take place.

So, control is returned to the Main method immediately after Show puts our new form on the screen, which promptly returns and exits the process, taking our nascent form with it.

To show a form modally—that is, to not return control to the Main function until the form has closed—you could call the ShowDialog method:

using System.Windows.Forms;

class MyFirstApp { static void Main() {

Form form = new Form();

form.ShowDialog(); // Still not what you want to do }

}

This code would show a blank form and wait for the user to close it before returning control to the Main method, but it’s not the code you generally write. Instead, to make it accessible from other parts of your application, you designate one form as the main form.

To do this, pass the main form as an argument to the Application object’s static Run method, which also resides in the System.Windows.Forms namespace:

using System.Windows.Forms; // For Form, Application

class MyFirstApp { static void Main() {

Form form = new Form();

Application.Run(form); // This is what you want to do }

}

The Run method shows the main form. When the form is closed, Run returns, letting our Main method exit and close the process. To see this in action, you can compile your first Windows Forms application using the following command:3

C:\> csc.exe /t:winexe /r:System.Windows.Forms.dll MyFirstApp.cs

The csc.exe command invokes the compiler on our source file, asking it to produce a Windows application via the /t flag (where the “t” stands for “target”), pulling in the Sys-tem.Windows.Forms.dll library using the /r flag (where the “r” stands for “reference”).4 The job of the compiler is to pull together the various source code files into a .NET assembly. An assembly is a collection of .NET types, code, or resources (or all three). An assembly can be either an application, in which case it has an .exe extension, or a library, in which case it has a .dll extension. The only real difference between assembly types is whether the assembly has an entry point that can be called by Windows when the assembly is launched (.exe files do, and .dll files do not).

Now that the compiler has produced MyFirstApp.exe, you can execute it and see an application so boring, it’s not even worth a screen shot. When you close the form, MyFirstApp.exe exits, ending your first Windows Forms experience.

To spice things up a bit, we can set a property on our new form before showing it:

class MyFirstApp { static void Main() {

Form form = new Form();

form.Text = "Hello, Windows Forms!";

Application.Run(form);

} }

3 To get a command prompt with the proper PATH environment variable set to access the .NET command line tools, click on Start | Programs | Microsoft Visual Studio 2005 | Visual Studio Tools, and then Visual Studio 2005 Command Prompt. If you don’t have VS05 installed, you can set up the PATH using the corvars.bat batch file in your FrameworkSDK\Bin directory.

4 csc.exe is the command line compiler for C#, and it is located in your c:\Windows\Microsoft.NET

\Framework\v2.0.50727 folder.

Like most classes in the .NET Framework, Form has several properties to access, meth-ods to call, and events to handle. In this case, we’ve set the Text property, which sets a form’s caption bar text. We could do the same thing to set other properties on the form, showing it when we were finished, but that’s not the way we generally do things in Windows Forms.

Instead, each custom form is a class that derives from Form and initializes its own properties:

class MyFirstForm : Form { public MyFirstForm() {

this.Text = "Hello, Windows Forms!";

} }

class MyFirstApp { static void Main() {

Form form = new MyFirstForm();

Application.Run(form);

} }

Notice that the MyFirstForm class derives from Form and then initializes its own prop-erties in the constructor. This gives us a simpler usage model, as shown in the new Main method, which creates an instance of the MyFirstForm class. You also gain the potential for reuse should MyFirstForm be needed in other parts of your application.

Still, our form is pretty boring. It doesn’t even include a way to interact with it except for the system-provided adornments. We can add some interactivity by adding a button:

class MyFirstForm : Form { public MyFirstForm() {

this.Text = "Hello, Windows Forms!";

Button button = new Button();

button.Text = "Click Me!";

this.Controls.Add(button);

} }

Adding a button to the form is a matter of creating a new Button object, setting the properties that we like, and adding the Button object to the list of controls that the form manages. This code produces a button on the form that does that nifty 3-D depress thing that buttons do when you press them, but nothing else interesting happens. That’s because

we’re still not handling the button’s Click event, which is fired when the user presses the button:

using System; // For EventArgs ...

class MyFirstForm : Form { public MyFirstForm() {

this.Text = "Hello, Windows Forms!";

Button button = new Button();

button.Text = "Click Me!";

button.Click += new EventHandler(button_Click);

this.Controls.Add(button);

}

void button_Click(object sender, EventArgs e) {

MessageBox.Show("That's a strong, confident click you've got...");

} }

Handling the button’s Click event involves two things. The first is creating a handler function with the appropriate signature; we’ve used the standard naming convention for events (VariableName_EventName) to name this method button_Click. The type signature of the vast majority of .NET events is a method that returns nothing and takes two param-eters: an object that represents the sender of the event (our button, in this case) and an instance of either the EventArgs class or a class that derives from the EventArgs class.

The second thing that’s needed to subscribe to an event in C# is shown by the use of the

“+=” operator in the MyFirstForm constructor. This notation means that we’d like to add a function to the list of all the other functions that care about a particular event on a par-ticular object, and that requires an instance of an EventHandler delegate object. A delegate is a class that translates invocations on an event into calls on the methods that have sub-scribed to the event.5

For this particular event, we have the following delegate and event defined for us in the .NET Framework:

namespace System {

delegate void EventHandler(object sender, EventArgs e);

}

namespace System.Windows.Forms { class Button {

public event EventHandler Click;

} }

5 Delegates and events are covered in depth in Appendix C: Delegates and Events.

Notice that the Click event on the Button class stores a reference to an EventHandler delegate. Consequently, to add our own method to the list of subscribers to the button’s Click event, we create an instance of the EventHandler delegate. To achieve the same effect with less typing, C# offers a syntactic shortcut that allows you to simply provide the name of the subscribing method:

public MyFirstForm() { ...

button.Click += button_Click;

...

}

Shortcut or not, it can quickly become tedious to add property settings and event han-dlers by hand for any nontrivial UI. Luckily, it’s also unnecessary, thanks to the Windows Forms Application Wizard and the Windows Forms Designer provided by VS05.

Windows Forms in Visual Studio .NET

Most Windows Forms projects start in the New Project dialog, available via File | New | Project (Ctrl+Shift+N) and shown in Figure 1.1.

Figure 1.1 Windows Forms Projects

To develop an application, you want the Windows Application project template. To develop a library of custom controls or forms for reuse, you want the Windows Control Library project template. When you run the Windows Application Wizard, choosing whatever you like for the project name, location, and solution name, click OK and you’ll get a blank form in the Windows Forms Designer, as shown in Figure 1.2.

Before we start the control drag-and-drop extravaganza that the Windows Forms Designer enables, let’s look at a slightly abbreviated version of the code generated by the Windows Forms Application Wizard (available by right-clicking on the design surface and choosing View Code or by pressing F7):6

// Program.cs

using System.Windows.Forms;

namespace MySecondApp { static class Program {

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main() {

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);

Application.Run(new Form1());

} } }

// Form1.cs

using System.Windows.Forms;

namespace MySecondApp {

Figure 1.2 A Windows Forms Application Wizard-Generated Blank Form in the Windows Forms Designer

6 The Windows Forms Designer offers two form views: Code and Designer. F7 toggles between them (although this keyboard shortcut is merely the VS05 default and, like any keystroke, depends on your specific settings).

partial class Form1 : Form {

#region Windows Form Designer generated code

/// <summary>

/// Required method for Designer support - do not modify /// the contents of this method with the code editor.

/// </summary>

void InitializeComponent() {

this.components = new System.ComponentModel.Container();

this.AutoScaleMode = AutoScaleMode.Font;

Most of this code should be familiar, including the using statements at the top, the form class that derives from the Form base class, the static Main function that provides the entry point to the application, and the Application.Run method. However, four things differ from what we did ourselves.

First, the Windows Forms Designer has dynamic theme support because of the call to Application.EnableVisualStyles, which keeps a UI’s appearance consistent with the current Windows theme.

Second, the Windows Forms Designer has also set the default form’s AutoScaleMode property to a value of AutoScaleMode.Font, which ensures that the form will automatically retain the correct visual proportions (as discussed in Chapter 4: Layout).

Third, the static Main method is implemented from a static class, Program, which exists in a file, program.cs, that’s separate from any of the UI elements in the application. Main is augmented with the STAThread attribute, which enables appropriate communication between Windows Forms and Component Object Model (COM) technology. This is required for several types of Windows Forms functionality, including using the Clipboard, the file dialogs, and drag and drop (shown in Appendix E: Drag and Drop). Because any serious Windows Forms application likely uses some form of COM, the Windows Forms Designer tacks this on to protect you from nasty exceptions that would otherwise arise.

Finally, a call to InitializeComponent is added to the form’s constructor to set the form’s properties instead of doing it in the constructor itself. InitializeComponent gives the Windows Forms Designer a place to put the code to initialize the form and its controls and components as we visually design the form. For example, dragging a button from the Toolbox onto the form’s design surface changes the InitializeComponent implementation to the following, in its entirety:

void InitializeComponent() {

this.button1 = new System.Windows.Forms.Button();

this.SuspendLayout();

//

// button1 //

this.button1.Location = new System.Drawing.Point(205, 75);

this.button1.Name = "button1";

this.button1.Size = new System.Drawing.Size(75, 23);

this.button1.TabIndex = 0;

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);

this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

this.ClientSize = new System.Drawing.Size(292, 266);

this.Controls.Add(this.button1);

this.Name = "Form1";

this.Text = "Form1";

this.ResumeLayout(false);

}

Notice again that this code is similar to what we built ourselves, although this time created for us by the Windows Forms Designer. Unfortunately, for this process to work reliably, the Windows Forms Designer must have complete control over the InitializeCom-ponent method. In fact, you can notice from the previous sample that the Wizard-generated InitializeComponent code is wrapped in a region that is collapsed to hide the code by default, and is marked with a telling comment:

#region Windows Form Designer generated code /// <summary>

/// Required method for Designer support - do not modify /// the contents of this method with the code editor.

/// </summary>

...

#endregion

To emphasize the need for control, the Windows Forms Designer splits the Form1 class across two files—Form1.cs and Form1.Designer.cs—using partial class support in C#.

The code in InitializeComponent may look like your favorite programming language, but it’s actually the serialized form of the object model that the Windows Forms Designer uses to manage the design surface. Although you can make minor changes to this code, such as changing the Text property on the new button, major changes are likely to be ignored—or, worse, thrown away. Feel free to experiment to find out how far you can go by modifying this serialization format by hand, but don’t be surprised if your work is lost. I recommend putting custom form initialization into the form’s constructor, after the call to Initialize-Component, giving you confidence that your code will be safe from the Windows Forms Designer.

However, we put up with the transgression of the Windows Forms Designer because of the benefits it provides. For example, instead of writing lines of code to set properties on the form or the controls contained therein, all you have to do is to right-click on the object of interest and choose Properties (or press F4) to bring up the Properties window for the selected object, as shown in Figure 1.3.7

7 Instead of F4, you can press Alt+Enter.

Figure 1.3 Browsing and Editing Properties in the Properties Window

Any properties with nondefault values, as indicated by values in boldface in the browser, are written to the InitializeComponent method for you. Similarly, to choose an event to handle for the form, or a control or component hosted on the form, you can press the Events lightning bolt button at the top of the Properties window to open the corre-sponding list of events (shown in Figure 1.4).

You have a few ways to handle an event from the Properties window. One way is to find the event you’d like to handle on the object selected (say, Click), type the name of the func-tion you’d like to call when this event is fired (say, button_Click), and press Enter. VS05 takes you to the body of an event handler with that name and the correct signature, all ready for you to implement:

void button_Click(object sender, System.EventArgs e) { }

After you’ve added a handler to a form, that handler will show up in a drop-down list for other events having the same signature. This technique is handy if you’d like the same event for multiple objects to be handled by the same method, such as multiple buttons with

Figure 1.4 Creating Events with the Properties Window

the same handler. You can use the sender argument to determine which object fired the event:

void button_Click(object sender, System.EventArgs e) { Button button = sender as Button;

MessageBox.Show(button.Text + "was clicked");

}

If you’d like each event that you handle for each object to be unique or if you just don’t care what the name of the handler is, as is often the case, you can simply double-click on the name of the event in the Properties window; an event handler name is generated for you, based on the name of the control and the name of the event. For example, if you

If you’d like each event that you handle for each object to be unique or if you just don’t care what the name of the handler is, as is often the case, you can simply double-click on the name of the event in the Properties window; an event handler name is generated for you, based on the name of the control and the name of the event. For example, if you