C. Programming Windows Forms Applications

The first place many programmers start when developing applications with .NET is with Windows Forms. Windows Forms represents an application framework within the .NET Framework for developing desktop user interface applications and their supporting components. Getting started with programming Windows Forms applications is very easy, especially when using Visual Studio 2005. If you have programmed with Visual Basic 6 or earlier, the process for designing Windows Forms in Visual Studio 2005 will feel very comfortable and familiar. If you come from a C++ background, you will find that designing with Windows Forms is familiar but easier than the process of programming views and dialogs in Microsoft Foundation Class (MFC), but that you sacrifice almost none of the power and control you had when programming Windows from C++. If you have been developing Windows Forms applications in .NET, then you are probably already familiar with most of what is covered in this appendix. The bottom line is that Windows Forms programming is intuitive and fun, and it is easier and faster to put together rich user interface applications in Windows Forms than with any other technology that I have encountered.

Despite the name, Windows Forms can be used to create any style of Windows application that you need; the UI doesn’t necessarily have to be oriented toward the presentation of actual forms. In earlier Windows UI development frameworks, everything was treated as a window. Each top-level window was a window, each dialog was a window, and even each control within a window was itself a window. In .NET, the common underlying construct for developing Windows Forms is the control. In Windows Forms, every UI element presented by your application will generally be derived from the Control base class. Even forms are derived from the Control base class and are therefore controls as well. But the term form in Windows Forms is used to describe any class that derives from the System.Windows.Forms.Form base class and which presents a window or dialog. Any other classes that derive from the Control base class and are intended to be contained within a window are called controls.

This appendix isn’t intended to be a comprehensive treatise on Windows Forms programming. I just want to give you enough background on the basics of Windows Forms programming so that if you haven’t done any Windows Forms programming, you don’t have to go read another book first. For a comprehensive treatment of Windows Forms programming, I recommend the forthcoming Windows Forms 2005 Programming in C# by Chris Sells and Mike Weinhardt (tentatively scheduled for publication by Addison-Wesley in early 2006).

This appendix covers the basic structure of a Windows Forms application, the architecture of the supporting Framework classes, some of the most common controls you will deal with in developing data-driven applications, and some of the new Windows Forms controls that are available in .NET 2.0. This appendix doesn’t go into any detail on the data-bound controls because they have their own dedicated chapters in the book. If you are an experienced .NET Windows Forms programmer, you might want to skip over most of this appendix, with the exception of those sections that are noted as “new in 2.0.” I’ve also marked the parts of other sections that use new features in .NET 2.0 or Visual Studio 2005 with a (New in 2.0) comment.

Your First Windows Forms Data Application

This first application will take you a small step beyond a “Hello World” application; you’ll create a very simple application that loads some data and displays it in a grid. I explain the steps to create the application from a pure code perspective, and in the next section you will repeat the creation of the application using Visual Studio. This way you can get a sense of both what is going on behind the scenes and how to do things the quick and easy way with the Visual Studio designer. Then we will dig into what is going on under the covers so that you understand where all the functionality in the Windows Forms Framework comes from.

Okay, let’s get started with the pure code approach.

1.   Open a text file and name it DataAppForm.cs.

2.   Declare a class that derives from System.Windows.Forms.Form :

using System;
using System.Windows.Forms;
public class DataAppForm : Form
{
}

      This code includes the using statement for the System.Windows.Forms namespace, which is where the Form and Application classes are defined, as well as the controls that you will be adding shortly.

3.   You also need a class that will contain the start-up code for the program. For this simple example, you can place this code in the same file:

static class Program
{
   [STAThread]
   static void Main()
   {

      Application.EnableVisualStyles();
      Application.Run(new DataAppForm());
   }
}

      The Main method is the entry point for any .NET application. In Visual Studio.NET 2003, this was always placed in the first Form class added to a project. In Visual Studio 2005, the Program class is added to the project as a separate code file so that the Main method doesn’t get lost if the first form is deleted from the project. We will mirror that approach here, but just put it in the same file for simplicity.

      The STAThread attribute is needed if the application will use any ActiveX controls, the Clipboard, or any single-threaded apartment COM objects, so it is added by default to any Visual Studio Windows application projects, and in general, you always need it there. You need to bring in the System namespace with a using statement to make that attribute type available.

4.   To compile this file using the C# command line compiler, open a Visual Studio command prompt (located in the Start menu under the Microsoft Visual Studio grouping, Visual Studio Tools submenu), switch to the folder where the file resides, and type the command:

csc DataAppForm.cs

      Note that you need to have the environment variables set so that Windows can find the command line compiler (csc.exe) if you don’t use the Visual Studio command prompt. You should now have an executable named DataAppForm.exe that you can run. If you do so, you will get a blank, square window.

5.   That’s a little boring, so let’s add a grid and a button to the form. Listing C.1 shows the changes (in bold) to the class that you need to add the button and grid and to position them reasonably.

LISTING C.1: Adding a Button and Grid to the Form


using System;
using System.Windows.Forms;
using System.Drawing;

public class DataAppForm : Form
{
   Button m_LoadButton;
   DataGridView m_CustomersGrid;

   public DataAppForm()
   {
        // Change the form size
        Size = new Size(500,400);

        // Set the button properties
        m_LoadButton = new Button();
        m_LoadButton.Location = new Point(10,10);

        m_LoadButton.Text = "Load";

        // Set the grid properties
        m_CustomersGrid = new DataGridView();
        m_CustomersGrid.Location = new Point(10,50);
        m_CustomersGrid.Size = new Size(480,250);

        // Add the controls to the child controls collection
        this.Controls.Add(m_LoadButton);
        this.Controls.Add(m_CustomersGrid);
   }

}


The code in Listing C.1 is a little more involved, so let’s walk through it. First you need an additional namespace, System.Drawing, for the Size and Point classes you use to specify the size and location of the controls. You then need to declare class members for each control that you are going to add to the form as a child control. In this case, you add a Button and a DataGridView member, which are defined as control classes in the System.Windows.Forms namespace. Then, in the constructor for the class, you create the instances of those controls, set the appropriate properties for them, and then add them to the child controls collection that form maintains.

For the form, you change its size by accessing the Size property of the base Form class and setting it to 500 × 400 pixels. Next you create an instance of the Button class and set its location and text. The location you specify is relative to the upper left portion of the form’s client area, with positive x-axis values increasing to the right and positive y-axis values increasing down toward the bottom of the screen. The client area starts just under the title bar and inside the border that is drawn around the window. The text is what will be presented on the button’s face when rendered on the form. The default size will be fine for this button because the text length is short. If you need to specify longer strings for the button text, you will need to adjust the size of the button to an appropriate width to fit the text, or better yet, set the AutoSize property to true.

For the grid, you create an instance of it and specify both the size and location to get it positioned underneath the button but within the bounds of the form. There are actually a lot of different options for laying out controls on a form (they will be covered later in this appendix), and the grid itself is a very complex type. But it is well designed so that for simple usage, you only need a few lines of code.

Once you have all the controls created and their properties set, you add them to the form’s Controls collection using the Add method from the collection class. This makes them child controls on the form. When the form is rendered, it will walk through the list of child controls, asking each to paint itself on the form using its properties and built-in painting behavior. Each control encapsulates its own state and behavior, both in the form in the way it paints itself, and in terms of what events it will fire based on user or system interaction.

When you compile and run this application again from the command line, you will now get a larger form with a button and empty grid on the form’s surface. For the final modification of this sample, you need to add an event handler for the button Click event and load some data in that handler and bind it to the grid.

In the constructor, add the following line of code to wire up a method named OnLoadData as an event handler for the Click event of the m_LoadButton button:

m_LoadButton.Click += new EventHandler(OnLoadData);

Then add the corresponding handler method to the class:


public void OnLoadData(object sender, EventArgs e)
{

   DataSet ds = new DataSet();
   ds.ReadXml("CustomersDataSet.xml");
   m_CustomersGrid.DataSource = ds.Tables[0];

}

Finally, you will need to add one more using statement at the top to bring in the System.Data namespace:

using System.Data;

The OnLoadData handler method has a signature (parameter and return types) defined by the EventHandler delegate that is used by many Windows Forms control events. In this example you can ignore the arguments that are passed to the method, but the method still must declare those arguments to match the event’s delegate type definition.

You can use whatever method name you like for the event handler, but I suggest you have a consistent, easy-to-read convention for what you name your event handlers. I usually name them with an On<action> convention as in this example. You will often create these handlers through the designer, and it applies a slightly different naming convention, as shown in the next section, so I usually set my event handler names using the Properties window to get the method names I want.

The code in the event handler creates a data set object and loads some data into it using the ReadXml method as shown in Appendix D. The source XML can be any valid XML that is suitable for loading into a data set with at least one table of data in it. This case uses the same simplified version of the Customers data set XML that is shown in the Loading Data Sets from a File section in Appendix D.

Once the data set has been populated, you bind the first table (index zero) in the data set to the grid using the grid’s DataSource property. The chapters in this book go into more detail on the mechanics of the data-binding process, but for the purposes of this appendix, suffice it to say that when you set the grid’s DataSource property to a data table, the grid will extract the schema information and create a column for each column in the table, and then will iterate through each row in the table and add a corresponding row to the grid containing the data.

To get the sample running, place the XML file in the same directory as the DataAppForm.cs file and compile at the command line as described before. Then run the resulting DataAppForm.exe file and click the Load button, and presto, you have your first running data-bound Windows Forms application.

There are a couple of key things to notice about the code you have written so far. The first is that it is just code. There is no magic being done on your behalf by an integrated development environment. You just need a text file containing a few lines of source, an XML file containing some data, and a command line compiler to have a fairly rich presentation of bound data. Another thing to notice is that if you had a bunch of controls on a form, the code for initializing the properties could become very tedious very quickly. Luckily the Windows Forms designer of Visual Studio will write all this code for you, and Visual Studio gives a much more intuitive environment for visually laying out the controls on a form rather than trying to figure out what code to write yourself. So you will rarely hand-code that initialization code for the controls as you have done here. Finally, the complexity of data binding and rendering the data in the form is completely encapsulated for you in the grid control, and happens just from setting the DataSource property on the control to a valid source of data.

Creating Windows Forms Applications with Visual Studio

Writing a form by hand is instructive for understanding the simplicity of the model, but you will rarely write Windows Forms code completely by hand. Usually you will use the Visual Studio IDE to design your forms the Rapid Application Development (RAD) way. Let’s step through an example.

Creating an Empty Windows Forms Project

1.   Open Visual Studio 2005 and create a new project by selecting File > New > Project. This displays the New Project dialog.

2.   Select Windows Application as the project type.

3.   Specify the name VSDataApp for the project. This will become the name of the solution and the project file, and a folder will be created for the solution, with a project folder created under that.

      If you want to override the default behavior of creating a separate solution folder, or change the name of that folder, you can do so with the options at the bottom of the dialog (see Figure C.1).

FIGURE C.1: New Project Dialog

New Project Dialog

      Once the project is created, Visual Studio will create the main form for your application and will display it in the designer window within the main work area.

4.   Grab the bottom right corner of the form, and drag it down and to the right to resize the default size for the form to be more rectangular and about the same size as the form you created with code in the last section (500 × 400). You can monitor the resizing of the form by looking in the bottom right of the status bar in the Visual Studio designer, which shows the current size of the form.

Working with the Toolbox

1.   If the Toolbox isn’t visible, do one of the following (see Figure C.2):

–   Display it by choosing View > Toolbox.

–   If there is a Toolbox tab on the left side of the Visual Studio window, click on that and it will slide out.

FIGURE C.2: Docking the Toolbox Window

Docking the Toolbox Window

      You can then dock the window by clicking on the pushpin icon at the top right.

2.   If the All Windows Forms group tab isn’t expanded at the top of the Toolbox (indicated by a plus sign next to it), expand it by clicking on the plus sign icon.

      The Toolbox displays a graphical palette of controls and components that you can drag and drop onto a form or designer surface to create instances of the control. The controls and components in the Toolbox are grouped into categories to help organize them into logical groups.

      You can customize the Toolbox to add your own or third-party controls to it or you can add additional group tabs. The All Windows Forms group at the top includes all the built-in controls in alphabetical order, so if you cannot remember what group a particular control belongs to, you can always find it there.

3.   From the Toolbox, drag and drop a Button control from the Toolbox palette onto the form, and do the same for the DataGridView control (new in 2.0).

      When you first drag the DataGridView control onto the form, a little window will pop up (see Figure C.3) called a smart tag (new in 2.0). This is an in-place control panel for some of the most common properties and actions you may need to manipulate on a control when you are working with it in the designer. You can dismiss the tag by clicking somewhere else in the form or on another control, or by pressing the Esc key.

FIGURE C.3: Data Control Smart Tags

Data Control Smart Tags

Once you have the controls on the form, you can click and drag on them to reposition them on the form. You can also click and drag on the corners or edges of the controls to resize them as desired. Note that as you drag the controls around the form, snap-lines will appear to help you align the control you are dragging with the other controls on the form. These subtle blue and magenta lines appear between the control being dragged and other controls or a dashed gray line between the control and the edges of the form. These help you precisely align the tops, bottoms, and sides of controls, as well as the baseline of the text in a control (the line that runs from under label2 through the text box shown in Figure C.4). When you drag and drop controls or components on a form like this, you are actually declaring and initializing member variables in the form class using a visual drag-and-drop metaphor. You will get to the generated code shortly, but the net result is very similar to what you coded by hand in the last section.

FIGURE C.4: Snap Lines Simplify Control Layout

Snap Lines Simplify Control Layout

Adding Members to the Form

1.   Select the Data grouping in the Toolbox to display the palette of data components.

2.   Drag and drop a DataSet component onto the form, and a dialog will pop up letting you customize the kind of data set that will be added.

      For this example, select Untyped dataset, and a data set member will be added to the form.

      It is displayed at the bottom of the designer in the components tray, and it is named dataSet1 by default. The components tray is where any components that are added through the designer and that don’t have a runtime graphical rendering are displayed. This allows you to still select them and access any design-time features that they have, and set their properties through the Properties window.

3.   Set the properties on the member controls that you created by dropping them on the form. Some of the properties are set automatically by the designer based on you moving and resizing the controls on the form, for example, the Size and Location properties.

4.   If the Properties window is not displayed in the IDE, show it by selecting Properties Window from the View menu or press the F4 key.

5.   Select the button on the form by single clicking on it, and then find the (Name) property in the Properties window.

      By default, the Properties window displays a categorized list of properties, where the categories correspond to related behavior or presentation aspects of the control.

      If you are trying to locate a control by name, you can sort the list alphabetically by clicking the button at the top of the Properties window with an AZ and down arrow on it (see Figure C.5). The (Name) property will appear at the top of the list with parentheses around it when sorted alphabetically in the Properties window. This is because the name is really just the variable name, which is a code artifact and doesn’t present itself as a runtime accessible property value.

FIGURE C.5: Properties Window

Properties Window

6.   With the (Name) property selected, change the name of the control member to m_LoadButton.

7.   Find the Text property of the button, and set its value to Load.

8.   Select the grid in the form by single clicking on it, and set its (Name) property to m_CustomersGrid.

9.   Select the data set in the components tray at the bottom and set its name to m_CustomersDataSet.

10.   Select the form itself by single clicking in a blank area of the form or on the title bar, and set the Text property in the Properties window to My First VS 2005 Windows Forms Data App. Note that the text in the title bar of the form changes to this value.

      Your IDE should now look something like Figure C.6.

FIGURE C.6: A Work in Progress in the IDE

A Work in Progress in the IDE

At this point, you haven’t written a single line of code, but Visual Studio has in fact written a bunch of lines of code for you. For starters, when it created the default form for the project, it declared a class named Form1 derived from the Form base class for you. It also added a Program class with the Main method in it to start up the application (new in 2.0). Next, when you dropped each control on the form, a member field was declared in the Form1 class for that control, and properties were set for it based on your interactions in the designer and Properties windows. So at this point you effectively have the same code as you had just before declaring and coding the button click event handler in the previous section.

To create the event handler for the button, the simplest (although not the best) approach is to double-click on the button in the designer. This will add an event handler with the method name based on the name of the control instance and the event that you are subscribing to. This follows the naming convention <control name>_<event name>. In the case of your m_LoadButton button and the Click event, this translates to m_LoadButton_Click.

A better way, in my opinion, is to use the Events view of the Properties window to explicitly specify a method name for the handler, which lets you use whatever naming convention makes sense to you. To do this, click on the little lightening bolt icon at the top of the Properties window, find the name of the event you want to add a handler for, and then type in the name you want the handler method to have. When you press Enter, a handler method will be added to the class with the name you specified, the appropriate method signature based on the event delegate type, and the method will be hooked up so that it is called when the event occurs. The Code view of the form will also be brought up with the input focus in the new event handler method, allowing you to start typing your handling code immediately.

Hooking Up an Event Handler and Data Binding

Let’s hook up the event handler using the Properties window to get a different naming convention.

1.   Make sure the button is selected in the designer, and then go to the Properties window and click on the lightening bolt to get the events listing for the button.

2.   Click in the input field next to the Click event, and type the handler name OnLoadData.

3.   Press Enter. You will be dropped in Code view into the method body of the handler that was created (see Figure C.7), so you can start writing the event handling code.

FIGURE C.7: Adding an Event Handler Through the Properties Window

Adding an Event Handler Through the Properties Window

Even though you are working with two different views of the Form1 class, a Designer view and a Code view, the Designer view is really just rendered dynamically based on the code by Visual Studio. What happens is that when you open a source file containing a form class definition by double-clicking on it, the IDE sees that the class defined within the source file is derived from the Form base class. The Form base class (and some of its base classes) provides specific support for being used in the designer, so the IDE presents a Designer view of the class by default when you open the file.

You can switch to the Code view by either right-clicking anywhere within the designer and selecting View Code from the context menu, or by selecting the View Code button in the middle of the small toolbar at the top of the Solution Explorer window, as long as the form’s class file is selected in the Solution Explorer tree. You can also use the F7 key to toggle between a form’s Code view and Designer view.

To read in the data from the XML file and data bind the results to the grid, add the following lines of code to the OnLoadData method:

private void OnLoadData(object sender, System.EventArgs e)
{

   m_CustomersDataSet.ReadXml("CustomersDataSet.xml");
   m_CustomersGrid.DataSource = m_CustomersDataSet.Tables[0];
}

You will notice that you don’t have to create an instance of the data set like you did in the first example, because you created it as a member by dropping it in the form in the designer. When you did that, code was added to create the instance as well. You are still setting the data binding between the grid and the data source by setting the DataSource property on the grid. Depending on how you create your data set in the designer, you may be able to set this data binding in the designer. (Setting up data binding with the designer is covered in detail in Chapter 5.) In this case, the structure of the data set isn’t determined until it loads the data set from the XML file at runtime, thus the designer doesn’t have enough information available at design time to bind to the first table, so you do it in code here.

In order for this code to work, you need to add the CustomersDataSet.xml file into the project files.

1.   Right-click on the project in Solution Explorer, and select Add > Existing Item.

2.   Find the XML file in the Appendix D sample code folder. You will need to download the sample code from the book’s Web site, or type it into a new XML file from Listing D.1. Don’t forget to change the file filter at the bottom of the dialog, because it defaults to only showing source code file types.

3.   Click the Open button to complete the action. This copies the file to your project folder and displays it as part of the project in Solution Explorer.

4.   Select that file in Solution Explorer, and change the Copy To Output Directory property to Copy Always (new in 2.0). When you run a program in the Visual Studio debugger, it will run from the build output directory, which is under the inDebug subdirectory of the project folder for a C# application. In order for the executable, which is running in that subdirectory, to locate the XML file in the ReadXml method call, it will need the file to be in that output folder, or it will need a relative or absolute path to that file wherever it resides.

You should now be able to run the application by selecting Start Debugging from the Debug menu. Pressing the F5 key with the default key mappings in Visual Studio will also start a debug run, as will the VCR-like start button shaped like a triangle pointing to the right in the toolbar. If you click the Load button while running, you should get the bound data displayed in the grid, just like you did in the first section when you wrote all the code by hand (see Figure C.8).

FIGURE C.8: Running the Application

Running the Application

Windows Forms Designer-Generated Code (New in 2.0)

In the previous section, I mentioned that all your designer interactions were really just generating code for you. But when you went into Code view for the Form1.cs file, you didn’t see much there besides the constructor for the form calling some method called InitializeComponent and the event handler method that you created through the Properties window. So where is all the code going that declares the member variables for the controls, sets their properties, hooks up the event, and so on? The answer to this question is one of the new and improved features of Visual Studio 2005.

In previous versions of Visual Studio, the designer-generated code was embedded in a region in the form code file itself. The problem with this was twofold. First, it often got deleted by mistake, killing the form. Second, the code that is generated by the designer should in general never be modified by hand, because any changes you make can be overwritten by the designer the next time you make changes from the Design view. Having the code inline in the main source file made it all too tempting to go in there and make changes (despite a comment that was generated with the code telling you to leave it alone), resulting in lost code and confusion for beginners.

A new language feature in .NET 2.0 is the ability to define partial classes. This lets you split the definition of a single class over multiple source files. Visual Studio 2005 leverages this capability to separate the designer-generated form code into a separate source file that is nested under the main form source file in the project in the Solution Explorer tree. If you want to view the contents of this file, expand the tree by clicking on the plus sign next to Form1.cs in the Solution Explorer window. Under Form1.cs you will see a Form1.Designer.cs file. If you open that file, you will find the designer-generated code in all its glory.

The first things you see at the bottom of the file are member variable declarations for all the controls that you added through the designer. You will also see a Dispose method and a collapsed region labeled Windows Form Designer generated code, just like you used to get inside the Form1.cs file in earlier versions of Visual Studio. If you expand that region, you will see the definition of the InitializeComponent method that was called by the constructor in your main Form1 class file. This method is the single place that the designer injects all of the code it writes when you perform drag-and-drop operations or set properties in the designer. You can see that the code creates the instances of the member controls and components, then sets properties for them, and finally adds them to the Controls collection of the form, much like the constructor code you wrote by hand earlier in this appendix. You will see that the designer has set more properties than you did, and it does a few other little things in the InitializeComponent method to begin and end initialization and to suspend and resume layout. I won’t go into detail about these things at this point because they are just some of the magic that the designer needs to do to give you a smooth design and runtime experience. See Chapter 8 for a discussion about initialization methods.

Controls have default values for all of the properties they expose, and those defaults are what will be used if no other value is set explicitly. The designer-generated code will only include code for setting properties to values that differ from the default values. For example, when you coded the form class by hand earlier in this appendix and ran the application, the title bar was blank because you hadn’t set the Text property of the form itself. When you create a form in the designer, it automatically sets the Text property to the name of the form initially, so that is what is displayed in the title bar for the window at runtime and in the designer. You can change this property just like any other property through either the Properties window or in the code. Property values that have been set to something different from their default value will be displayed in a bold font within the Properties window in the designer.

As the comments for the InitializeComponent method indicate, you should avoid modifying the code in this method directly. A lot of the code in that method gets rewritten each time you modify something on your form in the designer. So depending on the kind of code you might add or modify in the method, your changes could be lost the next time you tweak something in the designer. Go ahead and close that file if you have it open; I just wanted to give you a sneak peek into what was going on behind the scenes.

As mentioned before, the first thing the constructor does is call InitializeComponent to get all the controls initialized to whatever you specified through the designer. You can insert any custom initialization code you need into the constructor after the call to InitializeComponent if you want to programmatically set the properties differently or initialize other members that haven’t been declared through drag-and-drop operations in the designer.

If you are brand new to Windows Forms programming and Visual Studio, this example may have seemed a lot more complicated than just writing the code by hand, because of the descriptions of the different windows and designer features and the code that gets generated as a result of designer actions. But the fact is that for many things you will need to do in Windows Forms programming, you will be able to get these done significantly faster in the designer than you can by writing all the code by hand, especially the more familiar and comfortable you become with the designer and the IDE.

If you come from a Visual Basic 6 or prior background, or certain other RAD design environments like FoxPro or Delphi, you may not be all that impressed—yet. After all, VB6 had an outstanding design-time experience for laying out and programming simple forms, which is where a lot of the ideas for the Visual Studio design-time experience came from. The key thing to realize about the .NET experience is that you don’t lose any insight or flexibility in overriding what the designer has done for you. Designer interactions just generate .NET code, and you can both view and modify that code as needed to make your application do exactly what you want it to do at runtime. Or you can stay in the designer and get almost everything you need done there.

A Brief Tour of the Windows Forms Architecture

A key thing to realize is that a form is simply another kind of .NET type. Specifically, a form is a class that you define that derives from the System.Windows.Forms.Form base class. In your derived class, you can include member variables, properties, methods, and events like any other .NET class. You will commonly define member controls, which are essentially variables that have types derived from the Control base class and that represent the UI elements that will be presented within your form. These members are referred to as child controls and will be contained within a collection of controls that is inherited from a base class, ContainerControl. In fact, there is a deep inheritance hierarchy in the Windows Forms Framework, and a lot of the functionality that many people think of as provided by a form or control is actually obtained from one of the many base classes in the hierarchy. A portion of the Windows Forms hierarchy is shown in Figure C.9.

FIGURE C.9: Windows Forms Class Hierarchy

Windows Forms Class Hierarchy

As you can see from Figure C.9, Windows Forms classes derive from Object just like every .NET class. The next step in the parental lineage of forms and controls is MarshalByRefObject. Next up the chain is the Component base class. The Component class is used for classes that will be used in containment scenarios, either to reside in a parent container themselves or to contain other components. The Component base class also enables Visual Studio to present a design surface for the class, onto which other components and controls can be dropped. Those objects can be selected and configured through their properties in the Properties window, like you have already seen with Windows Forms and controls.

The next and probably one of the most important base classes is the Control base class. This is the class where most of the common UI behavior and properties exist. The Control class provides support for things like keyboard and mouse input, specifying foreground and background colors, and specifying size and location information. In fact, the Control class encapsulates a Windows handle and implements events corresponding to all the common Windows messages that a window can receive through the operating system. The Control class implements its own message handling loop and exposes the Windows operating system message-based events as .NET events to your controls and their containers, so that you don’t have to worry about all the low-level operating system goo.

If you wanted to create your own custom control from scratch, and make that control responsible for drawing its own user interface, the Control class is the base class that you would want to derive from, as shown with the MyCustomControl class in Figure C.9. By deriving from Control, you would inherit the interface that forms and controls expect out of other controls, and then you could extend the behavior and rendering of that control as needed.

After Control in the hierarchy, there are the ScrollableControl and ContainerControl classes, whose names are fairly self-explanatory. ScrollableControl provides support for controls whose contents are larger than the area that they will be rendered into, providing derived classes with the ability to scroll the contents within a constrained area on the screen without having to write a bunch of custom painting code to accomplish that. ContainerControl provides the support for maintaining a collection of child controls and ensures that all of those child controls are called to render themselves whenever the parent control is redrawn.

Finally, there is the Form class, which will often be the direct base class for any forms you create in your Windows applications. The Form class provides a default rendering that includes a title bar, frame, and a dialog-like background surface onto which you can add child controls. There is a great deal of flexibility programmed into the Form class to let you customize almost any aspect of its presentation as needed.

A sibling of the Form class is a class called UserControl. This is the class you will want to derive custom controls from if you want them to start with a default drawing surface and have built-in support for child controls. A common example of this is if you wanted to design a login control that contained inputs for the username and password, and you wanted those controls to be contained on multiple forms in your application. You would probably want those controls to have the same layout and appearance wherever they were presented, but you wouldn’t want to have to repeat code in multiple forms to declare and use those controls. This is the kind of scenario where a composite control derived from the UserControl class makes a lot of sense. You can encapsulate the layout and event handling from a set of controls within your custom composite control, and then add your composite control to one or many forms as needed. You would have one set of code to maintain, and you would get rich designer support for creating the control and adding it to other forms. See the section “Creating a Custom User Control” later in this appendix for an example of implementing a custom user control.

Forms have a specific lifecycle with which you should become familiar. Just like all classes in .NET, forms and controls have a constructor, which is where you will do the majority of your initialization of members and child controls. Forms and controls follow the .NET pattern of implementing the IDisposable interface, which provides a pattern-based way for clients to tell a class when they are done using the class, thus allowing it to clean up any unmanaged resources it contains. In forms and controls, there is a base class implementation of the Dispose method that you can override to provide any custom cleanup code for unmanaged resources that your form may encapsulate. If you override the Dispose method, that code will be called by the Framework when the form is closing. You should always remember to call your base class’ implementation of Dispose to give it a chance to clean up any resources it is encapsulating. The Windows Form designer code file (<form_name>.Designer.cs) in Visual Studio 2005 contains an override of the Dispose method, and if you need to ensure that your own member variables are disposed, you will have to add code to that method in the designer code file to do so.

The rest of the lifecycle of a form is dictated by events fired by the base Form class, as well as events fired by controls contained by the form. One of the most important events you will often need to handle is the Load event of the form itself. This event is fired when the form is in the final stages of creation and is about to be rendered on the screen. This makes the form’s Load event a good place to do final initialization of any controls on the form, especially if the parameters used for that initialization cannot be determined until runtime. If you want to do your initialization of some variable or control in your form as late as possible, but before the form is shown on the screen, putting that code in an event handler for the Form.Load event is the way to go.

The Dawn of .NET Execution—The Main Method

Every .NET executable has to have an entry point—the place where execution first enters your code. In .NET, that entry point is the Main method. The Main method has a couple of allowable signatures for it to be treated as the entry point for your application. First off, it needs to be a static method on one of the classes in the application, typically the static Program class in a Windows Forms application in Visual Studio 2005. It can return either void or an integer value. Typically you will return void unless you expect your application to be launched by another application that will be checking the returned value when the process exits. The other variation on the Main method is that it can either take no parameters or a single string array as a parameter. The following examples are all valid Main methods for an application:

static void Main(string[] args)
{
}
static void Main()
{
}
static int Main(string[] args)
{
}
static int Main()
{
}

If the Main method you use takes a string array as a parameter, it will be passed any command line arguments used when launching the application in that array. Unlike legacy C++ and C applications, the first argument isn’t the name of the program itself. In .NET, the first argument passed to the Main method in the argument array will be the first argument after the name of the executable from the command line, if any. If no arguments were present on the command line, the argument array passed to the Main method will still be a live instance of a .NET string array, and it will have zero elements in the array, which can be determined by checking the Length property on the array.

You can have more than one Main method in your application, but you can only have one Main method defined in a given class. If you do have more than one class with a Main method in your application, you will have to specify which class is your startup object in the project properties in Visual Studio, or using the /main command line compiler argument.

For a lot of your Windows Forms applications, you won’t need to make any modifications of the Main method that Visual Studio creates for you in the Program class. When Visual Studio creates a new Windows Forms project, the Main method has just a couple lines of code:

[STAThread]
static void Main()
{
     Application.EnableVisualStyles();
   Application.Run(new Form1());
}

The call to the EnableVisualStyles method allows your form to present themed control appearances when running on Windows XP or later. Finally and most important, the last line of code creates an instance of your form class and passes it to the Application class’ Run method. The Application class provides a number of static methods and properties that allow you to code against the context of the executing application. The Run method basically sets up a Windows message processing loop on the current thread and starts your application running. If you aren’t familiar with Windows programming from the days of old and don’t know what a Windows message is, don’t worry. Except for very advanced situations in .NET, you don’t even have to know they exist. They are just the low-level way that window objects in the operating system communicate with each other, and as mentioned before, many .NET controls are just wrappers around operating system window objects.

Execution of your application will continue until the main window is closed, which kills the application’s message pump and ends the main thread of execution. If you have spun off other threads in the running of your application, those threads may continue to run if they were started as foreground threads, which is the default threading model in .NET.

The STAThread attribute, mentioned earlier, is placed on the Main method by the Windows Forms Project wizard. This attribute is placed there to allow for easy integration of single-threaded apartment model ActiveX controls or COM components. .NET sets up a multithreaded apartment for COM interoperability by default, and that could cause problems for ActiveX controls and many COM components, particularly those developed with Visual Basic 6 or earlier. So for the maximum safety and compatibility for migration scenarios, you should always include this attribute in Windows Forms applications on the Main method that starts the application thread running.

For some applications, you may want to modify the Main method to do other forms of application initialization before the application starts. For example, say that the application was designed to take a single command line parameter that corresponded to the mode of operation for the application. The main form class could be modified to take that mode as a parameter to its constructor, so that it could initialize a member variable with the value. In that case, you might modify your Main method to do something like this:

[STAThread]
static void Main(string[] args)
{
   if (args.Length != 1)
   {
      throw new ArgumentException(
         "You must give the mode as a command line argument.");
   }
   Application.Run(new Form1(args[0]));

}

In this example, you check the arguments passed to the application to make sure you got what you expected. You then pass the first string in the array to the constructor of your form class. You would naturally also have to change or overload your form constructor to take a string argument to complete the scenario, and that constructor would then probably set a member field’s value based on that parameter.

Another common thing to modify in your Main method is to set up a global exception handler for your Windows Forms application, so that uncaught exceptions don’t bubble out to the runtime and cause your application to die with an ugly dialog presented to the user by the runtime. If you add a handler for the ThreadException event on the Application class in the Main method before calling the Run method, that event handler will be called any time an exception bubbles to the top of the stack without being caught. In your handler you can do any logging of the error you deem necessary and can then present the user with a little more user-friendly dialog that you can design. It is then up to you to decide whether to close the application or not, as shown in the following code:

using System.Threading;


[STAThread]
static void Main()
{

   Application.ThreadException += new
      ThreadExceptionEventHandler(OnUnhandledException);
   Application.EnableVisualStyles();
   Application.Run(new Form1());

}


private static void OnUncaughtException(object sender,
   ThreadExceptionEventArgs e)

{
   string msg = "I'm terribly sorry, but an unsolvable problem has"
      " occurred. The programmers will be flogged at dawn.";
   MessageBox.Show(msg);
   // Log exception using e.Exception on event arguments
   Application.Exit();

}

Handling Control Events

As mentioned earlier, most of the lifecycle of a Windows Forms application is determined by handling control events. All controls inherit a large set of events from the Control base class. These events correspond to the common Windows messages that can get passed to any window by the operating system. This includes events like mouse clicks, keyboard events, Windows timer events, and repainting events. Many of these events are of the same delegate type—the EventHandler delegate type introduced earlier. If you need to know more about delegates and events, I would suggest picking up a good book on general .NET programming. (Programming .NET Components, second edition, by Juval Löwy includes great coverage of .NET events and delegates.)

Some events are defined using more specialized delegate types that include specialized event arguments. These arguments carry additional information about the event that can be used by subscribers to perform processing based on that event in their event handlers. For example, the Control class defines events for MouseDown, MouseUp, MouseMove, and MouseWheel to name a few. These events are declared to be of type MouseEventHandler. The MouseEventHandler delegate type takes a MouseEventArgs object as the second parameter in its signature. The MouseEventArgs class defines several properties that can be used to perform conditional processing based on the specifics of the event. These properties include the X and Y locations in screen-based coordinates of where the mouse event occurred, which button was pressed and how many times if the event was a mouse click, and how many detents (notches) a mouse wheel was rotated if the event was caused by moving the mouse wheel.

The way you handle control events is no different than how you handle any .NET event. You need to subscribe to the event using the built-in mechanisms for your .NET language of choice. For C#, you subscribe to an event using the += operator, passing an instance of the appropriate delegate type initialized to point to the event handler method:

eventPublisher.MyEvent +=
   new EventHandler(mySubscriber.HandlerMethod);

Another notation to be aware of (and which I use a lot in this book) is the new C# 2.0 syntax for delegate inference (new in 2.0). In this code, I explicitly created a new instance of the delegate type for the event, EventHandler, and passed that to the += operator. In C# 2.0, the compiler is smart enough to infer what the appropriate delegate type is based on the type of the event you are setting up a subscription on. So in C#, you can compact the notation to just the name of the handler method as follows:

eventPublisher.MyEvent += mySubscriber.HandlerMethod;

Even though I don’t include any samples in Visual Basic in the text, all the sample code is available in Visual Basic for download. Because event handling syntax is one of the few places that Visual Basic code looks distinctly different than the C# equivalent, here’s a Visual Basic example. In Visual Basic you can use the AddHandler keyword, passing the event and the handler method:

AddHandler publisher.MyEvent, _
   AddressOf subscriber.HandlerMethod

In Visual Basic, you also have the option of declaring the instance of the control using the WithEvents keyword, then using the Handles keyword modifier on the end of a method declaration to hook it up as an event handler:

Public Class MyForm
    Inherits Form
    Private WithEvents m_LoadButton As Button

    Private Sub MyButtonHandler(ByVal sender As Object, _
       ByVal e As EventArgs) Handles m_LoadButton.Click

    End Sub
End Class

Displaying Other Forms

You will frequently need to create additional windows or forms as part of your Windows Forms application. You might need to pop up a dialog to prompt the user for preference settings, or you might need to launch a separate viewing window to display the results of a particular query. The way you do these things with Windows Forms is very straightforward.

If you simply want to launch a new window to present some additional data, you create an instance of the form class for that window and call the Show method to display it:

private void ShowResults()
{
   DataForm df = new DataForm();
   df.Show();

}

This will bring up the new window in a nonmodal way—the form will be presented as a top-level window and you will be able to interact with it independently of the main form that launched it. However, the new form will still be executing on the same thread as the main form. You can interact with the forms at the same time because they are being serviced by the Windows message pump that was set up in the Main method when the application started. If you close the main form that hosts the message pump, the other forms will be closed and the application will shut down, because the main form message pump determines the lifetime of the main application thread. So you can think of forms launched in this way as child windows, or nonmodal dialogs.

If you want to present a form as a modal dialog, you do so by calling the ShowDialog method instead of the Show method. Doing so will block the interaction with the launching form until the form that was launched with ShowDialog is closed.

Containing Forms Within a Parent Form

The previous section showed how to launch a new form that can be moved around the screen independently of the form from which it was launched, even though the lifetime of that window is tied to the one that created it. What if you want to create a form that will be fully contained within the client area of its parent window? This style of user interface is called a multiple-document interface (MDI), even though what you contain in the child forms may not be document-oriented at all. The terminology is a holdover from the days when earlier versions of programs like Microsoft Word and Excel opened each document in a child window that was contained within the outer window, or frame, which represented the application itself. Most of those programs have migrated to being more document-centric instead of application-centric and now present a separate top-level window for each document that is open. However, the application style that they introduced is still around and may make sense for many situations.

Creating MDI applications in Windows Forms is much easier than it was in C++ or VB6. First you will need to design your main form just like you would for any application. Because you are designing an MDI application, you will want to leave the client area of the main form blank since this is where the child forms will be presented. Typically you will just have a menu, toolbar, and status bar in the main form of an MDI application. You will also need to design any forms that you want to be contained within the main form’s client area as child forms.

Once you have designed the forms, there are only two steps required to launch the child forms so they will reside within the parent form’s client area. First you need to set the IsMdiContainer property on the parent form to true. Then when you construct the child forms, but before you call their Show method, you need to set the MdiParent property of the child form to a reference to the parent form:

// Method in parent form
private void ShowChildForm()
{

   ChildForm child = new ChildForm();
   child.MdiParent = this;
   child.Show();

}

The result is an application that presents its child windows, as shown in Figure C.10.

FIGURE C.10: An MDI Application

An MDI Application

Common Data Display Controls

Visual Studio ships with over 65 controls and components in the Toolbox that you can add to your Windows forms simply by dragging and dropping them onto the design surface. The chapters in this book focus on the use of controls for presenting data that is bound to data sources of various types. This section covers some of the most common controls you will use to present data, with or without data binding. A later section of this appendix covers some of the cool new controls in .NET 2.0 that are used for purposes other than data binding in your data applications. The main chapters of this book drill down into all the gory details of the data-bound controls, both those that existed in .NET 1.X and still remain viable today, and those that are new in .NET 2.0.

Because all of the Windows Forms controls derive from the base Control class, they share a common set of properties that can be used on any control. These include properties such as Font, BackColor, ForeColor, and BorderStyle that affect the presentation of the control; Position, Anchor, and Dock that affect the layout of the control; and Locked, Visible, and Enabled that affect the user’s ability to see or use the control. Later sections cover how you lay out controls and set their tab order so that users can use the Tab key to move from control to control in the expected order.

All of the controls described here can be used by writing code against their properties, methods, and events. You can also do almost everything you need with these controls through designer and property window interactions. Finally, all of these controls, except the ListView and TreeView controls, also support some form of data binding through their properties and the inherent data-binding capabilities of Windows Forms.

Label Controls

Labels are a simple but important control for your forms. Many controls provide information about their purpose as part of their presentation, but others need a helping hand to tell the user what to do with them. Labels are exactly what they sound like: a simple control that can contain a label to indicate some piece of information to the user. The piece of information is most often text that indicates the name, content, or purpose of another control on a form. However, a label can also contain an image. If your primary purpose is to present an image to a user, though, you should use a PictureBox control, because it includes much better functionality for displaying an image.

You usually just drag and drop a label on a form, then set its Text property to whatever you want the label to display using the Properties window or in code. You should set the name of the label control as well, to make it clear which label is which, when you are working in the Code view. You should set the label’s tab order to immediately precede the control it refers to. This will help users with disabilities who use the accessibility features of Windows to access functionality in your application.

Button Controls

A button control is certainly one of the most common controls for interactive applications. The Button class presents a standard Windows button on your form that can be used to trigger actions or invoke processing logic in your application. To use a button, you again typically drag and drop it on a form, set the Text property to whatever you want to display on the face of the button, and create an event handler for the Click event, so that you can perform whatever processing is appropriate in the event handler. Windows Forms buttons can also have an image set to display on the face of the button, in addition to or in place of the text.

Check Box Controls

The CheckBox control lets users select values that can be either true or false, yes or no, or on or off. The CheckBox control supports a three-state mode, where its value can be Checked, Unchecked, or Indeterminate, as determined by the control’s CheckState property. To use this mode, you have to set the ThreeState property to true. The indeterminate state is rendered as a shaded square inside the check box.

A check box doesn’t imply any relation between the value of the CheckBox control and the state or value of another control. The CheckBox control includes both the actual check box itself and the text caption that accompanies it. You will typically use a CheckBox control by dragging it onto a form, setting its Text property to indicate the meaning of the check box, and setting the Checked property to either true or false to indicate the default state of the control. You can wire up an event handler for the CheckChanged event to monitor when the checked state of the control changes, or you can simply check the value of the Checked property when needed to make decisions on other processing in your code. Or, if you want a three-state check box, you can set the ThreeState property to true and set the CheckState property to one of the three enumerated values: Checked, Unchecked, or Indeterminate.

Radio Button Controls

The RadioButton control is intended to be used in conjunction with other radio buttons. Radio buttons have an implicit intent of indicating mutually exclusive value selections among a set of values. For example, if you were implementing a control to set the current color of a traffic light, you would have three radio buttons—one red, one yellow, and one green—and only one of those buttons could be selected at one time. Otherwise, you could be the cause of some serious traffic accidents, and you wouldn’t want that.

Like the CheckBox control, you can handle the CheckChanged event to monitor changes to the selection of radio buttons, or you can just check the value of the Checked property whenever needed for processing. The RadioButton class derives from CheckBox, which is where it gets the definition of these properties.

Please don’t commit the cardinal sin of using a radio button where a check box is appropriate, or vice versa. Radio buttons are for mutually exclusive selections; check boxes are for independent selections. This is an often-abused user interface design point that you should keep in mind.

Text Box Controls

The TextBox control in .NET lets you create single and multi-line text boxes for user input and display. Like many controls, the Text property is the one that determines the string that is displayed within the text box. When you first place a text box on a form, the default mode is single line. If you want multi-line, you change the Multiline property to true. You also have control through the Scrollbars property over whether you have scroll bars when you are in multi-line mode. You don’t have to use a different control for different kinds of text boxes in .NET like you needed to do in some previous development tools and languages. You are limited to plain text content within the text box control, but if you need to allow the user to format the text they input, you can provide a RichTextBox control.

RichTextBox Controls

The RichTextBox control lets the user enter and format text using formatting that is supported by the Rich Text Format (RTF) specification. Like the TextBox control, the string contents of the control are accessible through the Text property. The Rtf property exposes the contents with all of the embedded formatting codes. If the user pastes formatted text into the control from some other program that supports formatted text, such as Word or a Web page, the formatting will be preserved within the text in the control. If you want the user to be able to format text that they type into the control, you will have to provide other controls or hot keys that let them execute formatting commands on the selected text in the control.

DateTimePicker Controls

The DateTimePicker is a fairly complex control that allows the display and editing of formatted date and time information. It looks like a ComboBox with the date and time information displayed in text form within it. The formatting of that display can be set based on a number of predefined formats or by using a custom formatting string. When the drop-down arrow is selected, a calendar control appears, allowing an intuitive and rich date selection mechanism. The ValueChanged event is the default event that can be handled to know when a date, time change, or date selection has occurred in the control.

List Box Controls

You use the ListBox control to present a list of items to users, typically allowing them to select one or more items in the list as part of a data input task. A list box is usually used when you want more than one item in a selection list to be visible to users at the same time, often in conjunction with allowing them to select multiple items.

List boxes maintain the items presented in the list as a set of object references held in a collection of type ListBox.ObjectCollection, a nested type inside the ListBox class. When the list box goes to render the items it holds, it will call the ToString method on each object reference that it holds, and the results of that method call are what will be presented to users. You can place any kind of object into this collection, so you can keep all the data associated with the displayed value in one place, and just the string representation of that object will be rendered into the items in the list.

For example, say you want to present a list of customers for selection in some task to users. However, when the form is processed, you will need the customer ID and perhaps other information associated with the customer to perform processing based on the user selection. To support this scenario, you could create a Customer class that overrides the ToString method inherited from the System.Object base class. In that method, you could return the customer name for presentation in the list box or other controls, but the Customer class definition could include members for the ID and any other associated data that you need to process a customer selection. At the point where you need to do that processing (typically a button click in the form or perhaps just a changing of the focus in the form), you can determine the current selection from the SelectedItem property, getting back a reference to the corresponding Customer object. Because SelectedItem is of type Object, you will have to cast the returned reference to the expected type to access the actual contents of that object. The following code shows this in action for a ListBox member control in a form named m_CustomersListBox.

private void OnProcessCustomer(object sender, EventArgs e)
{
   Customer customer = (Customer)m_CustomersListBox.SelectedItem;
}

You can monitor changes to the selection of the items in the list through the SelectedIndexChanged event. When you handle that event, you can use the SelectedIndex property to get the zero-based index in the list for single selections, or the SelectedItem or SelectedItems properties to get the object reference back for selected items in the list. SelectedItems applies if you allow multiple selections in the list box by setting the SelectionMode property to MultiSimple or MultiExtended. The difference between these two values is that with MultiSimple, selections in the list can be toggled as selected or not by clicking on them more than once. With MultiExtended, you can use the Shift and Ctrl keys in combination with mouse clicks to select multiple items or ranges of items. You should favor using MultiExtended for most list boxes, since that is the most common interaction model for list boxes and therefore will be most intuitive for your users. You can also set this mode to None if you are using a ListBox for display purposes only and don’t want to let users be able to select any of the items.

Combo Box Controls

The ComboBox control presents a list of data as a drop-down list with a text box that can either be used to present the current selection or to let the user input a new value. Other than the way it presents the list and the fact that you can choose to make the current selection editable, it works a lot like a list box that only allows a single selection, but it takes up a lot less screen real estate. Like the list box, it maintains a list of items of type ComboBox.ObjectCollection in the Items property, which it uses to render the list and that you can use to store more complex objects than just strings if desired. You set whether the current selection is editable by setting the DropDownStyle of the combo box. And like the list box, you can handle the SelectedIndexChanged event if needed.

List View Controls

The ListView control is a fairly complex control that lets you present a collection of information using a number of different views. This class wraps the underlying operating system control that is used by Windows Explorer, which you can use to switch between small icon, large icon, details, and list views of the items it contains. To populate the control with items for display, you have to construct instances of the ListViewItem class. This class provides a data container for each item, allowing you to specify the item text and an image that will be used for large and small icon displays. You can also associate a collection of subitems with an item, and those subitems will be used for display when showing the Details view of a list view. The ListView control doesn’t support any direct data binding, because the need to properly construct these ListViewItem objects doesn’t map in a clean, generic way to a relational set of data.

Tree View Controls

The TreeView control lets you display a hierarchical, navigable view of data within your application. The tree view presents a collection of nodes in a fashion similar to the Folder view in Windows Explorer. Each node in the tree is an object of type TreeNode, which has Image and Text properties that control what is displayed for the node. You can set whether lines are displayed between nodes, whether the plus/minus image is displayed at each node for tree expansion, and whether check boxes are shown at each node to select multiple nodes. Like the ListView control, the tree view doesn’t directly support data binding, because there isn’t a clean mapping of relational data onto a tree structure.

Picture Box Controls

The PictureBox is a powerful and convenient little control to use whenever you need to display an image within a form. All that is required to display an image in the PictureBox control is to construct a Bitmap object from the System.Drawing namespace and assign it to the Image property. The PictureBox control also has a SizeMode property that you can set to Normal, AutoSize, CenterImage, StretchImage, or Zoom. Based on the value you set for SizeMode, the PictureBox will automatically handle resizing the image when the form it is contained on is repainted or resized.

Data Grid Controls

The DataGrid control was the mainstay of tabular data presentation in .NET 1.X. It lets you bind data sets, tables, and collections of data to the grid for presentation in a tabular display. The control can discover the relations between multiple tables in a data set and provides a navigation scheme between parent and child rows of data. It allows easy sorting of rows based on a column selection, and lets you customize the appearance of columns and the table through style classes.

However, because the DataGrid had a lot of problems in terms of both usability and customization, the DataGridView control was added in .NET 2.0. The DataGridView control supersedes the DataGrid as the primary control for tabular presentation of data for new applications. If you are migrating existing applications that use the DataGrid control, you don’t necessarily need to switch over to the DataGridView. Your existing DataGrid code will continue to work as it did before, but I don’t recommend using the DataGrid control for any new code in .NET 2.0 applications.

DataGridView Controls (New in 2.0)

This new control is the focus of Chapter 6. It is the control of choice for presenting tabular data of many forms in Windows Forms applications. It provides easy to use data-binding mechanisms and the ability to customize the appearance, handle large data sets, include unbound columns, as well as many other advanced features.

Creating a Custom User Control

Sometimes when you are designing an application, your forms will get a little complex, consisting of a large number of controls. In general you should avoid this, because too many controls on a single form means you better have a pretty sophisticated user. Often there will be clusters of controls on those forms that are functionally related, and the code that supports them is mostly separable from the rest of the controls on the form. You also may find situations where you have a common grouping of controls that need to be repeated on more than one form.

These situations cry out for encapsulation, and Windows Forms provides a nice design mechanism to address encapsulation of groups of controls—specifically user controls, which are controls that you design that derive from the UserControl base class. As you saw in Figure C.9, the UserControl class derives from the same base class hierarchy as the Form class. As such, it provides both control containment for child controls and a design surface that lets you easily add those child controls in a visual way.

Using custom user controls, you can encapsulate a set of controls into a parent control, including both their layout and all the code that initializes the controls and services events from them. By doing so, you can simply add the custom user control to other forms or even other user controls as an atomic unit, treating it as a single control from the perspective of its parent. You can even expose a data-binding experience around the custom control that models that exposed by other built-in .NET controls. This can lead to much easier-to-maintain code because of the encapsulation of code that results and the ease of use from the consuming code perspective. It also leads to more consistent user interface design, because you can present the same group of controls in exactly the same way in multiple places because they are actually running from the same code.

Let’s take a look at a simple example: implementing a search user control that can be embedded on multiple forms that present tables of data. The control will be designed to encapsulate the process of gathering the search criteria from the user based on the fields presented in the table on the form, and it will create the search command that can be executed directly to return the matching rows ready to data bind. Follow these steps to create a project with a single user control.

1.   Create a new project in Visual Studio with a project type of Windows Control Library, and name it SearchControlLibrary.

2.   Select the UserControl1 file in Solution Explorer, and then click on it again after a pause to edit the file name.

3.   Change the file name to SearchControl.cs. When you press the Enter key, Visual Studio will detect that you have changed the file name (which is expected to match the name of the contained class), and it will prompt you, asking if you would like it to perform a rename throughout the project.

4.   Click on the Yes button. This uses the Refactor-Rename capability in C# to find all the places that UserControl1 was used as a class name and replaces them with SearchControl.

5.   Grab the bottom right corner of the control and drag it to make the control a little wider.

6.   Add two labels, a combo box, a text box, a check box, and two buttons. Set the Text properties on the controls so you end up with something like Figure C.11, and use control names that make sense to you. I named the controls m_FieldSelect, m_SearchTextBox, m_ClearButton, m_SearchButton, and m_ExactMatch going clockwise from the combo box, according to the naming convention for member variables and controls on a form.

FIGURE C.11: The SearchControl User Control

The SearchControl User Control

You can find the full code for this control in the download code for this book. To try out the process, you don’t actually need any functional code behind the controls. You could just do the layout, compile the project, and then investigate adding the custom user control to a form in another project. If you open your control library project in the same Visual Studio solution as another Windows Forms project, the control should show up automatically in the SearchControlLibrary Components grouping in the Toolbox, ready to drag and drop onto the form (see Figure C.12). Or, you can use the Choose Items context menu option from the Toolbox to add the control manually to the Toolbox by navigating to the compiled assembly. If you do that, you will see the control with any custom icon assigned, which the sample code includes.

FIGURE C.12: Custom User Controls in the My User Controls Toolbox Group

Custom User Controls in the My User Controls Toolbox Group

If you add some search command creation functionality to the control like the download code sample does, you could then drag and drop this control on other forms, like those shown in Figure C.13. In this sample application that uses the SearchControl, by using just a few lines of code in each form that includes the control, it is easy to search the table on the form because the SearchControl encapsulates the creation of an appropriate search command. Then it is up to the form to execute the command and data bind the results.

FIGURE C.13: A Data Application That Uses the Search Control in Multiple Forms

A Data Application That Uses the Search Control in Multiple Forms

Laying Out Controls on a Form

Windows Forms includes several different approaches you can choose from for laying out the controls on your forms. You can mix and match the approaches, using whichever approach is appropriate for a given control. The three modes of positioning and sizing a control on a form include absolute positioning and sizing, anchoring, and docking controls. In addition to these layout modes for individual controls, you can achieve more complex layouts using some of the new container controls available for Windows Forms 2.0, including the FlowLayoutPanel, TableLayoutPanel, and SplitContainer.

Absolute Positioning and Sizing of Controls

When you drag and drop a control onto a form, place it in the position that you want it to appear, and possibly resize it by dragging the edges or corners of the control, you are using absolute positioning and sizing. What is happening behind the scenes when you do these things is that the designer is adjusting the Location and Size properties of the control to explicit values in pixels. Whatever values are set for these properties will be used if the Dock property is set to None, which is the default. You can also set the Location and Size values programmatically, as you did earlier in the section “Your First Windows Forms Data Application,” using the Point and Size structures defined in the System.Drawing namespace:

myControl.Location = new Point(50,50);
myControl.Size = new Size(200,150);

One of the problems with absolute positioning is that if you allow the user to resize the form, the controls will just stay where they are. However, if you are going to let the user resize the form, you usually want the controls that are on the form to do something sensible to adapt to the new size of the form, either by repositioning themselves, resizing themselves, or both. You could handle this in Windows Forms applications in the same way you did in most legacy Windows applications: you could intercept the events associated with the form resizing, perform manual calculations based on the new size of the form, and then explicitly set the position and size of the controls to some appropriate value based on the new size of the form. However, this kind of coding is very tedious and error prone, and in most cases there are some common behaviors you want the controls to adopt for positioning and sizing themselves. Those common behaviors have been encapsulated as built-in capabilities of Windows Forms through the Anchor and Dock properties.

Anchoring Controls

For many kinds of controls and form layouts, you will want controls to stay in a constant position relative to one or more of the sides of a form. You may want the control to maintain its size, but only reposition itself to stay in the same location relative to two adjacent sides of a form, such as the top-left or bottom-right corner of the form. Or you may want the control’s outer edges to stay in the same location relative to two opposite sides of the form, meaning that it will stretch or shrink as the form expands or contracts.

For example, take a look Figure C.14—this is the same form, but this shows it in different sizes. For a form like this, you would probably want the data grid to fill all the real estate of the form not being used by the buttons as it is resized. You would also probably want the button in the bottom left corner to stay in a fixed position relative to that corner, and likewise for the button in the bottom right. Finally, you would probably want the middle button to stay centered in the form and in a fixed location relative to the bottom edge of the form. As you can see by looking at the size of the grid and the location and size of the buttons in the two forms in Figure C.14, you can get exactly that behavior without writing a single line of code by hand by setting the Anchor property appropriately for each control through the Properties window.

FIGURE C.14: Data Form Before and After Resizing

Data Form Before and After Resizing

The Anchor property takes an enumeration of type AnchorStyles, which has flag values Top, Bottom, Left, Right, and None. Because it is a flags enumeration type, you can combine the values using the Boolean “or” operator ( | ). The Windows Forms designer gives you a nice graphical property editor, shown in Figure C.15, that lets you visually select the sides that you want the control to be anchored to. The resulting code is equivalent to the following for anchoring the data grid to all four sides so that it will resize to keep each of its four sides in the same relative position to the form’s corresponding sides:

FIGURE C.15: Anchor Property Editor

Anchor Property Editor

m_CustomersGrid.Anchor = AnchorStyles.Top | AnchorStyles.Bottom
   | AnchorStyles.Right | AnchorStyles.Left;

Docking Controls

Anchoring controls solve a lot of common layout requirements, but there is another form of layout that is very handy for certain situations as well. Windows Forms includes a docking feature through the Dock property of the Control base class. The Dock property is a DockStyle enumeration that works similarly to the Anchor enumeration just described. You can dock a control to one of five locations: Top, Bottom, Left, Right, and Fill. You can also choose to set Dock to None, which is the default.

When you dock a control to a given side, it will change its location so that the corresponding side of the control is always glued to that side of the form. Additionally, it will resize itself to fill out to the adjacent sides, filling the entire side of the form selected. The remaining side will be fixed based on the position you set, and the Anchor settings will be ignored.

For example, in Figure C.16, a TreeView control was added to the form and its Dock property set to Left. This made it automatically resize and relocate so that the left side of the control is always attached to the left side of the form, and the top and bottom edges of the control expanded until they filled out to the top and bottom of the form. The right edge of the control stays wherever it is placed.

FIGURE C.16: Docked Tree and List Views

Docked Tree and List Views

A ListView control was also added to the form and its Dock property was set to Fill. This means that it will fill out the rest of the center of the form. In this case, since there are no other controls that are docked top, bottom, or right, it fills out the rest of the form. On the left side, it will fill to the border of the tree control that was docked left. If there were other docked controls on the form, they arrange themselves to their respective borders and then try to fill out to the adjacent borders. If you laid out your form this way, the two portions of the form wouldn’t be resizable, so for this kind of layout you will usually want to use a SplitContainer (described later). In that case, the TreeView and ListView each become a child of the containers in the SplitContainer and can be set to Dock.Fill to fill their portions on either side of the split.

When you select a control on the form and select the Dock property in the Properties window, you get the graphical Properties editor shown in Figure C.17.

FIGURE C.17: Dock Property Editor

Dock Property Editor

The order that you place controls on the form and set their Dock properties matters. If there is already another docked control in the way when a control tries to fill out to the adjacent sides of the one it is docked to, it will fill out to the edges of the other docked control that is in its way. So if you place one control on a form and set its Dock property to Top, then place another control on the form and set its Dock property to Top, the second control will dock to the bottom of the first control, and the first control will be docked to the top of the form. In this way, you can achieve pretty sophisticated layouts with Docking. However, it can be challenging to get the controls all on the form in the right order and set their Dock properties accordingly. The sequence is based on the order that they are added to the Controls collection of the container (z-order), which corresponds to the sequence you added them to the form through the designer.

Probably one of the most useful scenarios for using the Dock property is to get a child control to dock-fill the client area of a parent container control. For example, if you place a TabControl on a form and want to put a grid on each tab in that control, the best way is to select each tab in turn, drag and drop a grid onto its child control area (the center of the displayed tab control), and then set the grid’s Dock property to fill. The drag-and-drop operation makes the grid a child control to the tab control, which is a container control itself. When you set the Dock property to Fill, the docking is relative to a control’s container client area, which isn’t necessarily the form.

Using Layout Container Controls (New in 2.0)

The FlowLayoutPanel, TableLayoutPanel, and SplitContainer controls can be extremely useful for achieving sophisticated layouts of controls on your forms with minimal effort. These three controls are new in .NET 2.0.

The FlowLayoutPanel control lets you achieve an effect similar to what the default layout behavior is for Web pages. Specifically, any controls in a FlowLayoutPanel container will rearrange themselves to fill the area of the container in the order that they were added to the parent container, flowing from left to right, and wrapping to a new line when they run out of room to the right. That is the default behavior, but you can also modify the FlowDirection property, setting it to one of the enumerated values LeftToRight (the default), TopDown, RightToLeft, or BottomUp.

The TableLayoutPanel control allows you to achieve tabular layouts of controls, where the controls maintain relative positions to one another and are laid out as a table or grid of controls on the form. You can use this control to achieve many of the same layout affects that advanced Web page designers use tables for in HTML. You can nest table layout controls within the cells of other table layout controls to get sophisticated but well-organized layout of controls that do sensible things when the form is resized. This is particularly useful for localization scenarios, where the labels or text in controls will be dynamically determined from resource files or the database, and the respective control will AutoSize itself based on its content width or height.

Finally, the SplitContainer control replaces the notorious Splitter control that was part of .NET 1.0 and 1.1. The Splitter control was very unintuitive to use, being based on the z-order of controls within a collection (basically the order that they were added to the parent container). The Splitter control is still around for backward compatibility, but the SplitContainer is much easier and more intuitive to use. It provides a splitter bar and a panel on each side of it that can act as container controls for anything you want to add to the respective sides of the splitter bar. This gives a better design-time and runtime experience in creating a split-window appearance.

Figure C.18 and C.19 show a simple sample application that demonstrates the effects of these three layout controls. In this application, a SplitContainer control was added to the form, a TableLayoutPanel container was added to the left pane of the SplitContainer and dock-filled, and a FlowLayoutPanel container was added to the right pane of the SplitContainer and dock-filled. Two labels, two text boxes, and two radio buttons were added to the six cells of the table layout panel. Four PictureBox controls were added to the flow layout panel. When the radio buttons for English or German are selected, the label prompts for the text boxes in the table layout panel are switched to that language.

FIGURE C.18: SplitContainer, FlowLayoutPanel, and TableLayoutPanel Example (First View)

SplitContainer, FlowLayoutPanel, and TableLayoutPanel Example (First View)

FIGURE C.19: SplitContainer, FlowLayoutPanel, and TableLayoutPanel Example (Second View)

SplitContainer, FlowLayoutPanel, and TableLayoutPanel Example (Second View)

Figure C.18 shows the application running with the thumbnail images initially, with just the fourth picture box scrolled around to a second row. The labels in the table on the left are flush against their text box, and they fill most of the available space. When I resize the form to make it a little narrower and switch the language to German, you can see the effects in the two layout panels in Figure C.19. The longer text of the German prompts automatically resizes the labels. The table layout panel is a fixed width and its parent split container has the left panel set to a fixed width, so the table layout panel resizes the column containing the text boxes, shrinking them slightly to make room for the expanded labels. You could also set the table layout panel to AutoSize = true and change the split container to not be a fixed width for the left column, and it would expand without shrinking the text boxes. On the right you can see that the reduced width of the form caused the flow layout panel to become narrower, which caused the third picture box control to flow down onto the second row of controls, which then forced the fourth picture box to flow to the third row. You can play with the LayoutContainers sample in the download code to get a better idea of this dynamic behavior than still figures can give you.

Setting Tab Order

An important thing to remember when laying out a form is to set the tab order of the controls properly. Windows Forms controls follow the standard Windows convention of letting a user move the input focus from one control to another using the Tab key. The order that the focus will transition from control to control is determined by the tab order. The tab order is set by a TabIndex property on each control. You can set these values manually and try to get them all straight, but it is much easier using the Tab Order view in the Visual Studio designer. If you select Tab Order from the View menu in Visual Studio when the form is displayed in the designer, the Form view will be modified to show the tab index of each control in a little box next to it, as shown in Figure C.20.

FIGURE C.20: Tab Order View

Tab Order View

You can set the tab order while in Tab Order view by clicking on each control in the order you want the tabs set, starting with the first control. When you click through to the last control, the new tab order will be automatically set. Pressing Esc or reselecting the Tab Order menu item in the View menu will exit the Tab Order view.

Keep in mind that you should set the tab order of labels that describe other controls, such as a label that describes what goes in a text box, as the control immediately preceding the described control. This is because people with disabilities often use additional tools to help discern inputs on the screen, and many of those tools depend on the tab order. So for a vision-impaired user of your application, their screen reader tool may read the label text as they tab into the text box that follows the label in tab order. When the user tabs through, the focus will skip over Label controls because its TabStop property is set to false from the base Control class. Input controls such as TextBox override that property and set it to true by default, so they will accept the focus when the user tabs through the controls.

Command and Control of Your Windows Forms Applications (New in 2.0)

Three new controls that you will probably want to use in almost every Windows Forms application you write are the MenuStrip, ToolStrip, and StatusStrip controls. These controls are new in .NET 2.0 and replace their predecessors (MainMenu, ToolBar, and StatusBar) from .NET 1.X. Like the Splitter mentioned earlier, the old controls are still part of the Framework for backward compatibility. However, for any new applications you design, you should start using the three strip controls as your primary controls for these types of functionality.

As the names imply, the MenuStrip and StatusStrip are really just specialized versions of the ToolStrip. The ToolStrip itself is a sophisticated container control that lets you add other controls to the strip (labels, buttons, combo boxes, progress bars, etc.) that will be laid out in a row. The tool strip supports docking to any of the four sides of its container. You can also embed the ToolStrip into a ToolStripContainer. This allows multiple tool strips to be arranged in whatever order the user wants, just like most people are used to from the toolbars in Microsoft Office. Additionally, the tool strips support an overflow area, which allows controls to disappear off the right or bottom of the strip if there isn’t enough room for the controls, but still be accessible through a drop-down arrow that appears on the end of the strip.

The MenuStrip defaults to docking to the top of the form, and lets you easily add menus and menu items, with text, images, and submenus as you would expect. Because the menu strip is a specialization of the tool strip, you can add all the same kinds of controls to a menu strip that you can to a tool strip. The StatusStrip is conceptually very similar, but it defaults to docking to the bottom of the form and provides StatusStripPanels that you add information to within the Status bar, as well as other controls, such as buttons and progress bars.

Each of the strip controls have smart tags (described earlier in this appendix) associated with them to let you quickly customize the control, including populating it with standard items such as the ones shown in Figure C.21 in the menu and a second tool strip. You also get some rich in-place editing capabilities with each control in the designer, with smart tags available for each item in the strip. You can also work with the items in the strip or the strip itself through the Properties window and the property editors presented from there.

FIGURE C.21: Strip Controls in Action

Strip Controls in Action

In addition to supporting most of the behavior of the toolbars and menus in Microsoft Office, the strip controls also support a themed look and feel. The default look and feel is almost identical to the look and feel of Microsoft Office 2003. The look and feel is managed through a renderer. There are several renderers that come with .NET 2.0, and you can create your own to customize the look and feel to a great extent.

Where Are We?

In this appendix, I have given you a quick but comprehensive overview of the things you need to understand to develop Windows Forms applications. You learned how to code Windows Forms by hand, and saw how the designer can save you a lot of coding with a much more intuitive and productive design-time experience for laying out your forms and controls and customizing the properties that drive their behavior and appearance at runtime. You got exposure to the event model and underlying infrastructure provided by the Windows Forms base classes, and learned about the many options for laying out your controls on a form. You learned to encapsulate functionality and related controls in user controls, and got exposure to some of the most common controls you will be dealing with in Windows Forms applications. Finally, you got a brief glimpse into the capabilities and features of some of the new Windows Forms 2.0 controls that were designed to make it easier to create rich functionality with well laid out and presented form controls in minimal time.

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

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