952 CHAPTER 26 ■ INTRODUCING WINDOWS WORKFLOW FOUNDATION
If you are interested in learning more about building custom activities, the .NET Framework 3.5 SDK documentation provides a number of interesting examples, including the construction of a “Send E-mail Activity.” For more details, simply browse the Custom Activities samples found under the WF Samples node of the provided documentation (see Figure 26-29).
Windows Workflow Foundation (WF) is an API that was released with .NET 3.0. In essence, WF allows you to model an application’s internal business processes directly within the application itself. Beyond simply modeling the overall workflow, however, WF provides a complete runtime engine and several services that round out this API’s overall functionality (transaction services, persistence and tracking services, etc.). While this introductory chapter did not examine these services in any great detail, do remember that a production-level WF application will most certainly make use of these facilities.
When building a workflow-enabled application, Visual Studio 2008 provides several designer tools, including a workflow designer, configuration using the Properties window, and (most important) the Windows Workflow Toolbox. Here, you will find numerous built-in activities that constitute the overall composition of a particular workflow. Once you have modeled your workflow, you are then able to execute the workflow instance using the WorkflowRuntime type, using your host of choice.
P A R T 6
Desktop User Interfaces
C H A P T E R 2 7
Programming with Windows Forms
Since the release of the .NET platform (circa 2001), the base class libraries have included a particular API named Windows Forms (represented by the System.Windows.Forms.dll assembly). As you may know, the Windows Forms toolkit provides the types necessary to build desktop graphical user interfaces (GUIs), create custom controls, manage resources (string tables, icons, etc.), and perform other GUI-centric programming tasks. In addition, a separate API named GDI+ (bundled within the System.Drawing.dll assembly) provides additional types that allow programmers to generate 2D graphics, interact with networked printers, and manipulate image data.
The Windows Forms (and GDI+) APIs are still alive and well with the release of .NET 3.5, and will exist within the base class library for quite some time (arguably forever, in fact). However, since the release of .NET 3.0, Microsoft shipped a brand new GUI toolkit called Windows Presentation Foundation (WPF). As you will see beginning in the next chapter, WPF provides a massive amount of horsepower that can be used to build bleeding-edge user interfaces.
The point of this chapter, however, is to provide a tour of the traditional Windows Forms API for one simple reason: many GUI applications simply might not require the horsepower offered by WPF. In fact, for many UI applications, WPF can be overkill. Furthermore, there are many existing Windows Forms applications scattered throughout the .NET universe that will need to be maintained.
Given these points, in this chapter you will come to understand the Windows Forms programming model, work with the integrated designers of Visual Studio 2008, experiment with numerous Windows Forms controls, and receive an overview of graphics programming using GDI+. To pull this information together in a cohesive whole, we wrap things up by creating a (semicapable) painting application.
■Note Earlier editions of this text included three (fairly lengthy) chapters dedicated to the Windows Forms API. Given that WPF is poised to become the preferred toolkit for .NET GUI development, this edition has consolidated Windows Forms/GDI+ coverage to this single chapter. However, those who have purchased this book can download the previous Windows Forms/GDI+ chapters in PDF format from the Apress website for free.
The Windows Forms Namespaces
The Windows Forms API consists of hundreds of types (classes, interfaces, structures, enums, and delegates) that are organized within various namespaces of the System.Windows.Forms.dll assembly. Figure 27-1 shows these namespaces displayed through the Visual Studio 2008 object browser.
955
956 CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
Figure 27-1.The Windows Forms namespaces of System.Windows.Forms.dll
By far and away, the most important namespace is System.Windows.Forms. From a high level, the types within the System.Windows.Forms namespace can be grouped into the following broad categories:
•Core infrastructure: These are types that represent the core operations of a Windows Forms program (Form, Application, etc.) and various types to facilitate interoperability with legacy ActiveX controls.
•Controls: These are types used to create rich UIs (Button, MenuStrip, ProgressBar, DataGridView, etc.), all of which derive from the Control base class. Controls are configurable at design time and are visible (by default) at runtime.
•Components: These are types that do not derive from the Control base class but still provide visual features to a Windows Forms program (ToolTip, ErrorProvider, etc.). Many components (such as the Timer and BackgroundWorker) are not visible at runtime, but can be configured visually at design time.
•Common dialog boxes: Windows Forms provides a number of canned dialog boxes for common operations (OpenFileDialog, PrintDialog, ColorDialog, etc.). As you would hope, you can certainly build your own custom dialog boxes if the standard dialog boxes do not suit your needs.
Given that the total number of types within System.Windows.Forms is well over 100 strong, it would be redundant (not to mention a terrible waste of paper) to list every member of the Windows Forms family. As you work through this chapter, you will gain a firm foundation upon which to build. However, be sure to check out the .NET Framework 3.5 SDK documentation for further details.
Building a Simple Windows Forms Application (IDE-Free)
As you would expect, modern .NET IDEs (such as Visual Studio 2008, C# 2008 Express, or SharpDevelop) provide numerous form designers, visual editors, and integrated code generation tools (aka wizards) to facilitate the construction of a Windows Forms application. While these tools are extremely useful, they can also hinder the process of learning Windows Forms, as these same tools tend to generate a good deal of boilerplate code that can obscure the core object model.
CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
957
Given this, our first Windows Forms example will be created using a no-frills text editor and the C# command-line compiler (see Chapter 2 for the details of working with csc.exe).
To begin, create a folder named SimpleWinFormsApp (I’d suggest creating this directly off your C drive), open a Visual Studio 2008 command prompt, and using your text editor of choice, create a file named SimpleWFApp.cs. Author the following code within your new file, and save it in the SimpleWinFormsApp folder.
// The minimum required namespaces. using System;
using System.Windows.Forms;
namespace SimpleWFApp
{
//This is our application object. class Program
{
static void Main()
{
Application.Run(new MainWindow());
}
}
//This is our main window.
class MainWindow : Form {}
}
This code represents the absolute simplest Windows Forms application. At bare minimum, we need a class type that extends the Form base class and a Main() method to call the static Application.Run() method (more details on Form and Application later in this chapter). You can compile this application using the following command set (recall from Chapter 2 that the default response file [csc.rsp] automatically references numerous .NET assemblies, including System. Windows.Forms.dll and System.Drawing.dll):
csc /target:winexe *.cs
■Note Technically speaking, you can build a Windows application at the command line using the /target:exe option; however, if you do, you will find that a command window will be looming in the background (and it will stay there until you shut down the main window). When you specify /target:winexe, your executable runs as a native Windows Forms application (without the looming command window).
If you were to run your application, you would find you have a resizable, minimizable, maximizable, and closable topmost window (see Figure 27-2).
Figure 27-2.A very simple Windows Forms application
958 CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
Granted, our current application is not terribly exciting, but it does illustrate how simple a Windows Forms application can be. To spruce things up a bit, let’s add a custom constructor to our MainWindow type, which allows the caller to set various properties on the window to be displayed. For example:
// This is our main window. class MainWindow : Form
{
public MainWindow(string title, int height, int width)
{
//Set various properties from our parent classes.
Text = title; Width = width; Height = height;
//Inherited method to center the form on the screen.
CenterToScreen();
}
}
We can now update the call to Application.Run() as follows:
While this is a step in the right direction, any window worth its salt will require various user interface elements (menu systems, status bars, buttons, etc.) to allow for input. To understand how a Form-derived type can contain such elements, you must understand the role of the Controls property and the underlying controls collection.
Populating the Controls Collection
The System.Windows.Forms.Control base class (which is the inheritance chain of the Form type) defines a property named Controls. This property wraps a custom collection nested in the Control class named ControlsCollection. This collection (as the name suggests) references each UI element maintained by the derived type. Like other containers, this type supports a number of methods to insert, remove, and find a given UI widget (see Table 27-1).
Table 27-1.ControlCollection Members
Member
Meaning in Life
Add()
Used to insert a new Control-derived type (or array of types) in the collection
AddRange()
Clear()
Removes all entries in the collection
Count
Returns the number of items in the collection
GetEnumerator()
Returns the IEnumerator interface for this collection
Remove()
Used to remove a control from the collection
RemoveAt()
When you wish to populate the UI of a Form-derived type, you will typically follow a very predictable series of steps:
■Note
CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
959
•Define a member variable of a given UI element within the Form derived class.
•Configure the look and feel of the UI element.
•Add the UI element to the form’s ControlsCollection container via a call to Controls.Add().
Assume you wish to update your MainWindow class to support a File Exit menu system. Here are the relevant updates, with code analysis to follow:
class MainWindow : Form
{
//Members for a simple menu system.
private MenuStrip mnuMainMenu = new MenuStrip();
private ToolStripMenuItem mnuFile = new ToolStripMenuItem(); private ToolStripMenuItem mnuFileExit = new ToolStripMenuItem();
public MainWindow(string title, int height, int width)
{
...
// Method to create our menu system.
BuildMenuSystem();
}
private void BuildMenuSystem()
{
//Add the File menu item to the main menu. mnuFile.Text = "&File"; mnuMainMenu.Items.Add(mnuFile);
//Now add the Exit menu to the File menu. mnuFileExit.Text = "E&xit"; mnuFile.DropDownItems.Add(mnuFileExit);
mnuFileExit.Click += new System.EventHandler(this.mnuFileExit_Click);
First off, notice that the MainWindow type now maintains three new member variables. The MenuStrip type represents the entirety of the menu system, where a given ToolStripMenuItem represents any given topmost menu item (e.g., File) or submenu item (e.g., Exit) supported by the host.
If you have programmed with earlier versions of Windows Forms (1.0 or 1.1), you may recall that the MainMenu type was used to hold any number of MenuItem objects. The MenuStrip control (introduced with
.NET 2.0) is similar to MainMenu; however, MenuStrip is able to contain controls beyond “normal menu items” (combo boxes, text boxes, etc.).
960 CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
The menu system is configured within our BuildMenuSystem() helper function. Notice that the text of each ToolStripMenuItem is controlled via the Text property, each of which has been assigned a string literal containing an embedded ampersand symbol. As you may already know, this syntax sets the Alt key shortcut, thus selecting Alt+F will activate the File menu, while selecting Alt+X will activate the Exit menu. Also notice that the File ToolStripMenuItem object (mnuFile) adds subitems via the DropDownItems property. The MenuStrip object itself adds a topmost menu item via the Items property.
Once the menu system has been established, it is then added to the controls collection (via the Controls property), after which we assign our MenuStrip object to the inherited MainMenuStrip property. While this step may seem redundant, having a specific property such as MainMenuStrip makes it possible to dynamically establish which menu system to show a user, perhaps due to user preferences or security settings.
Figure 27-3.A simple window, with a simple menu system
The only other point of interest is the fact that we are handling the Click event of the File Exit menu, in order to capture when the user selects this submenu. The Click event works in conjunction with a standard delegate type named System.EventHandler. This event can only call methods that take a System.Object as the first parameter and a System.EventArgs as the second. Here, our delegate target (mnuFileExit_Click) has been implemented to terminate the entire Windows application using the static Application.Exit() method.
Once this application has been recompiled and executed, you will now find your simple window sports a custom menu system (see Figure 27-3).
The Role of System.EventArgs and System.EventHandler
System.EventHandler is one of many delegate types used within the Windows Forms (and ASP.NET) APIs during the event-handling process. As you have seen, this delegate can only point to methods where the first argument is of type System.Object, which is a reference to the type that sent the event. For example, if we were to update the implementation of the mnuFileExit_Click() method as follows:
MessageBox.Show(string.Format("{0} sent this event", sender.ToString())); Application.Exit();
}
we would be able to verify that the mnuFileExit type sent the event, as the string
"E&xit sent this event"
is displayed within the message box. You may be wondering what purpose the second argument, System.EventArgs, serves. In reality, the System.EventArgs type brings little to the table, as it simply extends Object and provides practically nothing by way of addition functionality:
public class EventArgs
{
public static readonly EventArgs Empty; static EventArgs();
public EventArgs();
}
CHAPTER 27 ■ PROGRAMMING WITH WINDOWS FORMS
961
This type is, however, very useful in the overall scheme of .NET event handling, in that it is the parent to many (very useful) derived types. For example, the MouseEventArgs type extends EventArgs to provide details regarding the current state of the mouse. KeyEventArgs also extends EventArgs to provide details of the state of the keyboard (such as which key was pressed), PaintEventArgs extends EventArgs to yield graphically relevant data, and so forth. You will see numerous EventArgs descendents (and the delegates that make use of them) not when working with Windows Forms, but with the WPF and ASP.NET APIs as well.
In any case, while we could most certainly continue to build more and more functionality into our MainWindow (status bars, dialog boxes, etc.) using a simple text editor, we will eventually end up with hand cramps, as we have to manually author all the grungy control configuration logic. Thankfully, Visual Studio 2008 provides numerous integrated designers that take care of these details on our behalf. As we use these tools during the remainder of this chapter, always remember that they are authoring everyday C# code. There is nothing “magical” about them whatsoever.
■Source Code The SimpleWinFormsApp project can be found under the Chapter 27 subdirectory.
The Visual Studio Windows Forms Project Template
When you wish to leverage the Windows Forms designer tools of Visual Studio 2008, your first step is to select the Windows Application project template via the File New Project menu option. To get comfortable with the core Windows Forms designer tools, create a new application named SimpleVSWinFormsApp (see Figure 27-4).
Figure 27-4.The Visual Studio Windows Forms project template