InformIT

Working with .NET Windows Forms

By

Date: Mar 7, 2003

Sample Chapter is provided courtesy of Sams.

Return to the article

Chris Payne focuses on all aspects of the System.Windows.Forms.Form object, and shows you the multitude of ways that you can customize your applications.

In the first two days you learned quite a bit about the way Windows Forms applications work. You can identify and describe each part of a Windows Forms application, and how it works with .NET, and you can infer the usage from those you don't know.

Since you have the basics now on how to construct Windows Forms, it's time to get into the details. Today's lesson focuses on all aspects of the System.Windows.Forms.Form object, and shows you the multitude of ways that you can customize your applications.

Today you will learn

The Object-Oriented Windows Form

If you haven't started thinking of your forms as objects yet, now is a good time to start. Imagine the Windows Form on your screen as an actual window in your house. You can change the color of the window, the opacity (how transparent the window is), the type of knobs and locks, curtain dressing, type of glass, and on and on. You can do virtually anything you want to your window (assuming you have the necessary resources).

The same statement can be said about Windows Forms. You know from yesterday's lesson that you can change the caption and size of a form. You can also change the title bar, the way objects inside the form are arranged, the color, the opacity and visibility, the borders, the buttons, and so on. Figure 3.1 shows just a few styles your form can take on.

Figure 3.1 Forms can be transparent, have no borders, and be resized.

Thus, it is helpful to think of Windows Forms as generic objects that can be manipulated and customized however you want. You can go to the .NET Class Library "store" and pick out a Form object, and then tailor it as you see fit.

Working with the Object Object

You worked with a few objects in Days 1 and 2, but you haven't yet examined the Object object, the base class for all .NET classes, including Windows Forms. Because it is inherited by every class, you will deal it with from now on, so it's a good idea to learn a bit more about it—specifically, the methods it provides you.

Object is the most generic class you can use in .NET. As such, it is used to represent any otherwise unknown object. For example, if you use the following statement to create a variable, CLR doesn't know what type to create it as, and just creates an Object:

dim MyObjectVariable

Because your variable is just an Object, you lose a lot of functionality that you would gain from declaring an explicit data type. For example, you can add an integer to an integer, but you cannot add Objects together; doing so would result in an error no matter your intentions. Therefore it's a good idea to always declare a specific type, rather than using Object for everything.

NOTE

By default, your applications must always declare a type for your variables to prevent misuse and increase performance. However, you can turn this safety feature off by disabling the Option Strict feature. See the "Compiling Your Windows Forms" section in Day 2 for more information on how to do this.

There are many functions you'll use in Windows Forms programming that create objects for you. For example, the ConfigurationSettings.GetConfig method retrieves configuration settings from a file. This function returns an Object data type, because it doesn't know specifically what you're going to do with the returned results. By returning an Object, you are free to transform the results to any data type you want by casting, a process that transforms one data type into another. In C#, you cast a variable with the following:

myDataType myVariable;  //declare variable of myDataType
myVariable = (myDataType) ConfigurationSettings.GetConfig ("some setting");
 //retrieve settings and cast to myDataType

There are five key methods of the Object object: Equals, ReferenceEquals, GetHashCode, GetType, and ToString. These all come in handy, so let's cover them individually. Remember that every object inherits from Object, so these methods are available to every object you use.

The Equals method is both a static and non-static member. This means that you can call it from either a specific instance of an object, or from the Object type itself. For example, Listing 3.1 shows a snippet using both static and non-static versions of the Equals method.

Listing 3.1 Using the Equals Method

1: int intA;
2: int intB;
3: 
4: intA = 4;
5: intB = 5;
6: 
7: Console.WriteLine("{0}", intA.Equals(intB)); //non-static method
8: 
9: Console.WriteLine("{0}", Object.Equals(intA, intB)); //static method

Lines 7 and 9 will both print "False" to the command line because the intA and intB objects are not equal (intA is 4, and intB is 5). Line 7, however, calls the Equals method from an instance, while line 9 calls Equals from the Object class itself. There are many times you need to compare two objects, so this method will prove useful.

TIP

The Console.Writeline method prints a string to the command line. The first parameter is the string to be printed. A number in brackets means the method should refer to one of the additional supplied parameters: {0} means print the next parameter, {1} means print the parameter after that, and so on. You could also simply use the parameter in place of the numeric identifiers.

To use this method, you must compile your application as a regular executable file (use the /t:exe option when compiling).

The ReferenceEquals method is a static-only member that is similar to the Equals method. The difference is that ReferenceEquals compares two objects to see if they are the same instance, rather than the same value. This can be done because an instance in computer terms is defined by a location in memory. Therefore, two objects that point to the same memory location are the same instance. Two different memory locations can have the same value, but they are not the same instance. (Note that null—or Nothing in VB.NET—always equals the same instance as another null.) Take a look at Listing 3.2, which compares different variables.

Listing 3.2 Comparing References

1: object a = null;
2: object b = null;
3: object c = new Object();
4: 
5: Console.WriteLine(Object.ReferenceEquals(a, b));
6: Console.WriteLine(Object.ReferenceEquals(a, c));
7: a = c;
8: Console.WriteLine(Object.ReferenceEquals(a, c));

Line 5 returns true, because all null values equate to the same instance. Line 6 returns false because a and c are two different objects. Line 7 looks like it sets a and c to the same value, but when you do an assignment of one variable to another, you're actually setting the memory locations equal, and therefore, line 8 returns true.

NOTE

If you have previous programming experience, you'll know where the name ReferenceEquals comes from: this method compares the variables' memory locations (or references) rather than simply their values.

Most often, the Equals method suffices, but it is useful to have the ReferenceEquals method around as well.

The next three methods are all non-static. GetHashCode returns a hash code for your object. A hash code is a numeric representation of an object; it doesn't actually hold any meaning. Objects A and B may be different objects but generate the same hash code, for example, the number 7. However, you cannot reverse the process and go from the number 7 to an object. Thus, hashing is a one-way mechanism.

GetType, as the name implies, simply returns the specific data type of an object. This is very useful when you don't know what kind of data you're dealing with; for instance, when a function returns an unidentifiable object:

string a = "hi";
Console.WriteLine(a.GetType());

This snippet will return System.String, the data type of the variable a. Note that this method returns a type of data, rather than just the type's name. In other words, it returns a type, and not a string. This method is often used to compare data types. For example, using the GetConfig method discussed previously:

myType myVariable;
if (myVariable.GetType() != ConfigurationSettings.GetConfig ("my settings").GetType()) return false;

This code snippet tests whether the type returned by the GetConfig method (a Type object) is of the same type as myVariable. And in this case, it is not.

Finally, probably the most common method, ToString, returns a string representation of a variable's type. For example, the following code snippet would print "system.object" to the command line:

Object objA = new Object();
Console.WriteLine(objA.ToString());

Note that it does not print out an object's value, but the name of the type. Sometimes the ToString method is overridden for a particular class. The string data type, for instance, overrides ToString so that it prints out the actual value of the variable, rather than the name of the type. The following code prints out "hello world."

string strA = "hello world";
Console.WriteLine(strA.ToString());

Form Properties

The System.Windows.Forms.Form object has 101 properties (no exaggeration!) that let you control nearly every imaginable aspect of the form. That's too many to cover in just one day, so let's just look a few of the more useful ones here, grouping by function.

Controlling Size and Location

You already know two ways to control the size of your form: the Width and Height properties. These properties simply take numeric values to set the size of the form in pixels. For example:

form1.Width = 100
form1.Height = 200

You can also set the size using the Size property and a Size object:

form1.Size = New Size(100, Form1.Size.Height)
form1.Size = New Size(100, Form1.Size.Width)

Both methods do the same thing, but you'll typically use the first method because it's simpler.

You can set the size relative to the user's screen as well by using the Screen object. Listing 3.3 shows an example.

Listing 3.3 Setting Form Height Based on Screen Size in VB.NET

1: Imports System
2: Imports System.Windows.Forms
3: 
4: Public Class MultiForm : Inherits Form
5:   Public Sub New()
6:    Me.Text = "Main form"
7:    Me.Height = Screen.GetWorkingArea(Me).Height / 2
8:   End Sub
9: End Class
10: 
11: Public Class StartForm
12:   Public Shared Sub Main()
13:    Application.Run(New MultiForm)
14:   End Sub  
15: End Class

This code should be familiar to you by now, so the only line we'll examine is line 7. The GetWorkingArea method of the Screen object returns a Rectangle object that represents the user's screen. You pass in the current form object (using the Me keyword) to tell the CLR which screen to use (this is just in case the user has more than one monitor). Rectangle objects in turn have Height and Width properties that return integers describing the height and width of the screen. We then divide the height value by two. Compile this application and run it; note that the window now takes up half the vertical height of your screen!

To control where the form pops up on the screen, you can use the Location, DesktopLocation, Top, or Left properties. The last two set the position of the upper left corner of your form to a location on the user's desktop. They work just like the Height and Width properties. Note that you can set these properties to some location way off the user's screen, such as Top = 9999 and Left = 9999, but that's generally not a good idea.

The Location and DesktopLocation properties are similar. For a Form object, they do the same thing: set the starting location for the top left corner of your form. For example, both of the following lines do the same thing:

Form1.DesktopLocation = New Point(100,300)
Form1.Location = New Point(100,300)

Location is a property inherited from the Form's great-grandparent class, Control. This property is used to set the location of a control within another control. In this case, the containing control for the form is simply the user's desktop. You could use the Location property for any other object that inherits from the Control object, such as the TextBox or Label controls, to set its location within another object, such as your form. The DesktopLocation property, however, only applies to the Form object. For consistency, we'll use the Location property from now on, so you only need to use one property for all Windows Forms objects.

Controlling Appearance

You already know about the Text property, which controls the text displayed in the form's title bar, so let's examine a few things you can do with font (the typeface in which the text appears).

The Font property sets the font that will be used on the form, unless otherwise overridden by a control's Font property. The ForeColor property then sets the color of that text. Take a look at the following code snippet:

Form1.Font = New Font(new FontFamily("Wingdings"), 23)
Form1.ForeColor = Color.Blue

The first line creates a new Font object (you're now beginning to see how everything in .NET is an object, even fonts and colors). There are quite a few different ways to create Font objects (in other words, it has many constructors), and this is just one method. You specify the font face by using the FontFamily object, which contains predefined font names. The second parameter is the size of the font. The second line sets the text color to blue, using the Blue property of the Color object. This code, when used in a Windows Form, produces the output shown in Figure 3.2 (you'll have to trust my word that the color is really blue).

Figure 3.2 Font face and colors are specified in properties.

The BackColor and BackgroundImage properties enable you to change the default appearance of the form. BackColor is used just like ForeColor:

Form1.BackColor = Color.LightSalmon

The BackgroundImage property takes an Image object as a parameter. Typically, you'll use the Image's FromFile method to load an image; you must provide a path name. For example:

Form1.BackgroundImage = Image.FromFile("c:\winforms\day3\coffee bean.bmp")

FromFile is a static method, as you've probably inferred since you don't have to create a new Image instance to use it. Using the form from Figure 3.2, we now have a tiled background, as shown in Figure 3.3.

Figure 3.3 You can use a graphic to tile a background in your form.

The background color of the Label is still gray, but you'll learn how to change that in Day 6, "Enhancing Your Windows Forms with More Controls."

Another image property you can customize is the Icon property. This icon is used in the upper left of your form's title bar, as well as in any other representation of your application, such as in the taskbar. Here's how you set this property:

Me.Icon = New Icon("c:\winforms\day3\VSProjectApplication.ico")

You must create a new instance of the Icon object, supplying a valid path to an image. The image you select must be an icon file (.ico) or your application will generate an error.

The mouse cursor image can be controlled via the Cursor property:

Me.Cursor = Cursors.Hand

The Cursors object has quite a few built-in properties for the default set of Windows cursors, such as Arrow, IBeam, WaitCursor, and Help. See the .NET Framework documentation for more cursors. You can also load a custom cursor from a file:

Me.Cursor = New Cursor("path name")

The cursor file must be a .cur file. Animated cursors (those with the .ani extension) are not supported by the CLR.

The ShowInTaskBar property determines if your application should be shown in the Windows taskbar (this doesn't affect the onscreen window, only the taskbar button). The default value is true. Occasionally you may want to change this to not allow a user to select your form. For example, if you create a "splash screen" that displays your company's logo, you probably don't want the user to be able to select it. Simply set the ShowInTaskBar property to false, and the user won't be able to select it from the taskbar.

The FormBorderStyle property represents the type of border around a Windows Form. The main reason you modify this property is to allow or disallow resizing of the form, although changing border styles sometimes also changes the form's appearance. For example:

Form1.FormBorderStyle = FormBorderStyle.Sizable

The FormBorderStyle enumeration (not to be confused with the Form.FormBorderStyle property) has several predefined styles to choose from, as shown in Table 3.1. (An enumeration is simply a collection of properties or styles.) Figure 3.4 shows a collection of the different styles.

Figure 3.4 There are seven predefined border styles available.

Table 3.1 FormBorderStyle Styles

Style

Description

Fixed3D

Non-resizable. 3D border around form.

FixedDialog

A thick border, non-resizable.

FixedSingle

Thin border, non-resizable.

FixedToolWindow

Form with a smaller title bar, useful for displaying ToolTips and Help windows. Non-resizable. Does not include minimize or maximize buttons.

None

No border and non-resizable.

Sizable

Resizable form. The default style.

SizableToolWindow

Form with a smaller title bar, useful for displaying ToolTips and Help windows. Resizable. Does not include minimize or maximize buttons.


Finally, there are three properties that control how, and if, your form is displayed on screen. First, the Visible property determines if your form is visible to the user. If a form is not visible, the user cannot interact with it. This is a good way to hide things from the user—if you want your application to stay open but don't want it interfering with the user's desktop, for example. By default, a form is not visible.

To make a form partially visible (in other words, transparent), you must set the Visible property to true, and use the Opacity property. This property takes a value from 1 (fully opaque—in other words, fully visible) to 0.0 (invisible). To perform some really interesting transparency techniques, you can use the TransparencyKey property to specify that only a certain color should be transparent. For example, the following code will make all gray areas on the form invisible, while everything else remains opaque:

Form1.TransparencyKey = Color.Gray

If you set the form's BackColor to gray as well, then you'll end up with a form like the one shown in Figure 3.5 (the form is placed over a command prompt window to show the effect of transparency.

Figure 3.5 Use the TransparencyKey property to make only a certain color invisible.

Controlling Interactivity

When you look at any Windows application, you'll notice that most of them have standard features: Minimize, Maximize, Close, and sometimes Help buttons in the upper right corner of the window, and a "gripper" on the bottom right to resize the form. These control boxes are shown in Figure 3.6.

Each of these control boxes can be hidden or shown on your forms with the following properties:

Figure 3.6 These control boxes are among standard Windows features.

The first four properties simply take a true or false value. ControlBox determines whether the previous buttons should be shown at all. Be careful, though; if you set ControlBox to false, you may not be able to close your application!

NOTE

The Help button appears only if the Maximize and Minimize buttons are not visible. This is a standard feature of .NET.

The SizeGripStyle property takes a SizeGripStyle enumeration value: Auto, Hide, or Show. Auto displays the sizing grip when necessary (in other words, depending on the FormBorderStyle), while Hide and Show prevent display of and display the sizing grips, respectively.

There are often two special keys associated with applications: the Enter key and the Esc (Escape) key. For example, many applications exit if you press the Esc key. You can control these functions with the AcceptButton and CancelButton properties. Listing 3.4 shows an example in C# of using these properties.

Listing 3.4 The AcceptButton and CancelButton Properties

1: using System;
2: using System.Windows.Forms;
3: using System.Drawing;
4: 
5: namespace TYWinForms.Day3 {
6: 
7:   public class Listing34 : Form {
8:    Button btAccept = new Button();
9:    Button btCancel = new Button();
10:    Label lblMessage = new Label();
11: 
12:    public Listing34() {
13:      lblMessage.Location = new Point(75,150);
14:      lblMessage.Width = 200;
15: 
16:      btAccept.Location = new Point(100,25);
17:      btAccept.Text = "Accept";
18:      btAccept.Click += new EventHandler(this.AcceptIt);
19: 
20:      btCancel.Location = new Point(100,100);
21:      btCancel.Text = "Cancel";
22:      btCancel.Click += new EventHandler(this.CancelIt);
23: 
24:      this.AcceptButton = btAccept;
25:      this.CancelButton = btCancel;
26:      this.Text = "Accept and Cancel Button Example";
27:      this.Height = 200;
28:      this.Controls.Add(lblMessage);
29:      this.Controls.Add(btAccept);
30:      this.Controls.Add(btCancel);AcceptButton and 
31:    }
32:   
33:    public void AcceptIt(Object Sender, EventArgs e) {
34:      lblMessage.Text = "Accept button pressed";
35:    }
36: 
37:    public void CancelIt(Object Sender, EventArgs e) {
38:      lblMessage.Text = "Cancel button pressed";
39:    }
40:   }
41: 
42:   public class StartForm {
43:    public static void Main() {
44:      Application.Run(new Listing34());
45:    }
46:   }
47: 
48: }

Much of this code should already look familiar, so we can just breeze through most of it. Lines 1–3 import the necessary namespaces. Line 5 declares the namespace this application belongs to; following our naming scheme, the namespace is TYWinForms.Day3. Line 6 declares the Windows Form class.

Lines 8–10 declare the controls we'll be using in our form. Note that these are declared outside of any method, but inside the class so that they can be used from every method in the class.

The constructor, beginning on line 12, is where most of the work takes place. The code on lines 13–22 simply sets a few properties for our controls. Take special note of lines 18 and 22, which point to methods that will be executed when either button on the form is clicked. These methods, AcceptIt and CancelIt, are on lines 33 and 37, and simply print a message in the Label control.

Lines 24–30 set some properties and add the controls to the form. Lines 24 and 25 set the AcceptButton and CancelButton properties to the accept and cancel buttons respectively. Essentially, this means that clicking the Accept and Cancel buttons with your mouse will do the same thing as pressing the Enter and Esc keys.

Finally, lines 42–46 contain another class that is used simply to hold our Main method and call the Application.Run method. The output of the application after pressing the Esc key is shown in AcceptButton and Figure 3.7.

NOTE

If the Cancel button on the form gets the focus, then pressing Enter causes that button to be pressed and, consequently, the CancelIt method to execute. In other words, the button receives the input before the form does. Unfortunately, there's no easy way to get around this. Some controls, such as the Button and RichTextBox controls, automatically handle the Enter key press when they have the focus, before anything else can execute. As a result, once you give the Cancel button focus (by clicking it, or tabbing to it), pressing the Enter key always causes the CancelIt method to execute.

If you're curious, you can override this behavior by overriding the Button control itself. We'll cover that in Day 18, "Building Custom Windows Forms Controls."

Note that although the AcceptButton and CancelButton properties need to point to Button controls, those Buttons do not have to necessarily be visible to the user. By setting their Visible properties to false, the buttons will be invisible, but you can still retain their functionality.

Finally, the AllowDrop property specifies whether the form can handle drag-and-drop functionality—that is, when a user drags some item into the form and releases it there. This property accepts a true or false value. We'll discuss how to make your forms actually do something when this event occurs in the "Form Events" section later today.

Figure 3.7 Pressing the Esc key has the same effect as clicking the Cancel button.

For a complete reference on the properties of the Form object, see Appendix B, "Windows Forms Controls."

Form Methods

The Form object has quite a few methods as well—57 of them to be precise. Most of them are inherited from the Object and Control classes, so they will be common to almost all the objects you work with in Windows Forms. Again, we'll cover a few of them here, grouped by category.

In addition to the methods discussed here, don't forget the Object methods such as Equals and ToString that we talked about earlier today. The Form object can take advantage of these as well.

Dealing with Display Issues

The first two methods you should learn are Show and Hide. These two functions make your form visible and invisible, respectively, by modifying the Visible property. These functions don't do anything to the form—such as removing it from memory or activating other functions—they only control what the user sees.

The Close method, on the other hand, completely gets rid of a form (and its controls), removing it from memory. Use this method when you want to close your application or simply when you don't need a form anymore.

Since Windows is a multitasking environment, you can have many windows open at once. Each window must compete for the user's attention. The Form object has a few methods that help you deal with this issue.

The Activate method "activates" your form. This can mean two different things:

  1. If your application is the active one (the one the user happens to be using at the moment), Activate brings the form to the front of the screen, ensuring it is on top of all other forms.

  2. If it is not the active application, the title bar and taskbar icon flash, grabbing the user's attention. More than likely you've seen this type of attention grabber before; the most common usage is for instant messaging applications. If someone sends you an instant message while you're working on another application, the IM window pops up in the background and flashes its title bar.

The BringToFront and SendToBack methods are more direct than Activate at getting a user's attention. The first method brings your form to the front of all other windows on screen, forcing the user to look at it. This is useful, for example, when something happens with your application that demands the user's attention (like getting an instant message popup). SendToBack, conversely, places your form behind all others on screen. SendToBack isn't used as often, but it's there just in case.

TIP

You can set the TopMost property to true to have your form always stay on top of other windows. This is especially useful for forms that deliver error or warning messages.

Finally, the Refresh method works much like the Refresh button on your Web browser; it simply redraws everything on the form, updating if necessary. We'll talk more about this method when you deal with GDI+ in Day 13, "Creating Graphical Applications with GDI+."

Resetting Properties

The Form object has a series of reset methods that enable you to change modified properties back to their default values. All of these methods follow the naming scheme Resetproperty. A few of the more common ones are

These methods are very convenient when you've modified something and need to go back, but don't know or don't care what the original value was. For example, if in a word processor the user changes the font several times, but then wants to go back to default values, you could use ResetFont.

Event Handling

An event is something that happens as a result of an action. Going back to the car object analogy, imagine that you press the brake (an action). The car stops (an event). If you press the gas pedal (an action), the car moves (an event). An event is always the effect of some action taking place.

Windows Forms have events too, although many of them may not seem very obvious. For example, open and close are two events that occur when you start and stop your application. When you move your mouse cursor into the form, an event takes place, and when your mouse cursor leaves the form, another event occurs. Without events, Windows Forms would be very bland because they would never do anything, no matter what the user tried.

In this section, we'll take a brief look at how events are handled with Windows Forms and .NET. You'll examine events in more detail, and learn how to take advantage of them, in Day 5, "Handling Events in Your Windows Forms."

The Message Loop

Events are wonderful ways of dealing with user input. Let's look at two different application models—one with events and one without events—before we examine how applications use events.

First, imagine an event-driven application. Event-driven means that the application responds to events caused by user actions. In fact, without these events, the application would do nothing. The events drive the application.

In this model, an application sits around and waits for things to happen. It uses events as its cues to perform actions. For example, a user presses a letter on the keyboard. The application sees that an event has occurred (the key being pressed), performs an action to display that letter on screen, and then waits for another event.

A non–event-driven application doesn't allow users free reign of the application—they can only respond to prompts from the application. With an event-driven application, users can interact with any part of the application they wanted, in any order or time they wanted.

Imagine a non–event-driven calculator application. When you start the application, it retrieves two values from textboxes, performs the mathematical calculations, and spits out the result. If there are no values in the textboxes, the calculator does nothing. The calculator cannot detect when a number has changed because it isn't aware of events. Anytime you want to change the numbers to calculate a different value, you have to change the numbers first, and then run the application again.

Classic Active Server Pages (in other words, prior to ASP.NET) also work in this way. An ASP application runs when a user requests the page from a browser, spits out the necessary HTML, and then stops. ASPs do not care what happens after that since they don't need to wait for any events. For the ASP application to deal with user input, a user has to enter all values into a Web form before posting it back to the ASP for processing. The ASP has no knowledge other than what is given to it at the start of execution. Figure 3.8 illustrates this process.

Figure 3.8 Non–event-driven applications involve a three-step process.

Both models have their advantages and disadvantages. Non-event driven applications, once started, can execute without user intervention. Event-driven applications typically require user input, but are often more interactive. Since interactivity is a must with any Windows-based application, all your programs will use the event-driven model.

So how does an application detect events? When a typical Windows application starts, it enters a message loop. A message loop is simply a period of time when the application is waiting for input, or messages from the user. This period continues until the application quits, so it is known as a loop. During this loop, the application does nothing except wait for user input (the period of non-activity is known is idling). When some input is received, the application does some work, and then goes back into the message loop. This cycle continues over and over again until the application is closed.

NOTE

When you provide some input, the Windows OS is the first stop for processing. Windows determines to what application the event applies, and sends it along to the application. These communications are known as Windows messages, hence, the name message loop.

For example, when you first open a Microsoft Word document, nothing happens; Word just idles, waiting for you to type. When you hit a key, an event occurs, a method is executed (to display the character onscreen), and Word goes back into the message loop waiting for more input. Every time you press a key, the message loop stops for a moment to do some processing, and then continues the wait. Figure 3.9 illustrates this cycle.

Figure 3.9 The message loop waits for user input.

Windows Forms are typically the main user interface for your applications. Thus, they'll be dealing with quite a few different events.

Form Events

You've already seen how to handle a few events. In the calculator example from yesterday's lesson, you took advantage of the Button object's Click event. Handling events in Windows Forms, no matter what objects they belong to, is pretty much all the same.

In Day 5 we'll have an in-depth discussion about events, how to handle them, and the differences between various events. For now, though, let's take a look at a few of the 71 events of the Form object.

Controlling Execution

In order to give you, the developer, the most control over your applications, some events fire both before and after an action occurs. For example, when you close your form, the Closing event occurs immediately before the form begins to close, and the Closed event occurs immediately after.

TIP

These types of event names always follow the gerund/pluperfect grammar rules (using ing and ed suffixes—Closing and Closed). Not all events come in pairs like this. If you see an ing event name, though, you can be sure there's also an ed event as well, but not the other way around.

Why this two-event-per-action approach? Imagine a grocery store that closes at 10 p.m. Five minutes before the stores actually closes, a message is played over the intercom alerting customers to move to the checkout lanes. This pre-closing event is used to alert customers that something is about to occur. After 10 p.m., the store closes, all customers are kicked out (okay, so it's not a very friendly grocery store), and the doors are locked. This two-step approach allows both the store manager and customers to prepare for the actual closing event.

Windows applications are similar. For example, if a user makes a large number of changes to a word-processing document, and then closes the application using the Close control box but forgets to save the document, what would happen? If you waited for the Closed event to fire before doing anything, it would be too late; the document would have closed and all changes would be lost. With .NET, however, the Closing event fires before closing actually takes place. Here you can prompt the user to save her document before changes are lost, and conditionally save the changes. After the window closes, the Closed event fires, and then you can do whatever other processing you need to (display a message to the user, for instance).

Let's take a look at an example using this two-step process. Listing 3.5 uses the Closing and Closed events of the Form object to illustrate the previous example.

Listing 3.5 Controlling Execution with Events in VB.NET

1: Imports System
2: Imports System.Windows.Forms
3: Imports System.Drawing
4: Imports System.ComponentModel
5: 
6: Namespace TYWinForms.Day3
7: 
8:   public class Listing35 : Inherits Form
9:    public sub New() 
10:      Me.Text = "Event Example"
11:      AddHandler Me.Closing, AddressOf Me.ClosingMsg
12:      AddHandler Me.Closed, AddressOf Me.ClosedMsg
13:    end sub
14:   
15:    public sub ClosingMsg(Sender as Object, e as CancelEventArgs)
16:      Microsoft.VisualBasic.MsgBox("Form closing")
17:    end sub
18: 
19:    public sub ClosedMsg(Sender as Object, e as EventArgs)
20:      Microsoft.VisualBasic.MsgBox("Form closed")
21:    end sub
22: 
23:   end class
24: 
25:   public class StartForm
26:    public shared sub Main()
27:      Application.Run(new Listing35)
28:    end sub
29:   end class
30: 
31: End Namespace

On line 4 we import a namespace we haven't seen before: System.ComponentModel. This namespace has objects that apply to events that you'll need later in the code. Lines 6–10 are all standard fare. Lines 11 and 12 use the AddHandler method (which you've seen in Days 1 and 2) to tell the CLR to execute the ClosingMsg and ClosedMsg methods (on lines 15 and 19) when the Closing and Closed events fire respectively. Let's skip down to line 15 and the ClosingMsg method, which is executed when the Closing event fires and before the form actually closes.

First, look at the signature (the first line) of this method. It takes two parameters: an Object and a System.ComponentModel.CancelEventArgs object. You already know what the Object object is. The second parameter is a specialized object that applies only to events, and only to Closing events in particular. It has a special property, Cancel, to interrupt the process—the form's closing in this case—should that be necessary (like in the word processor example discussed earlier). If you determine that the closing should be stopped (if, for example, the user forgot to save her document), set the Cancel property to true:

e.Cancel = true

The application will stop closing and go back into the message loop.

In this case, we're not concerned about stopping the form from closing. On line 16, we call a method to display a message to the user onscreen. The MsgBox method of the Microsoft.VisualBasic simply presents a pop-up box to the user with the specified text. (Note that instead of using the MsgBox method's full name on line 16, we could have imported the Microsoft.VisualBasic namespace using Imports.)

The ClosedMsg method beginning on line 19 executes after the form has closed. Note that it takes an EventArgs object instead of CancelEventArgs for the second parameter. We'll discuss why in Day 5. This method again calls the MsgBox function to display another message, alerting the user that the form has already closed.

Finally, compile this code from VS.NET or with the following command:

vbc /t:winexe /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll listing3.5.vb 

Recall from Day 2 that the System.ComponentModel namespace is in the System.dll assembly, so we didn't need to reference any new assemblies here. Figure 3.10 shows the output after the Close control box is clicked.

Figure 3.10 The Closing event enables you to intercept the Closed event.

Not all events follow this two-step process, but a few of the Form object's do. Table 3.2 describes these.

Table 3.2 Events with Pre-Cursor Events

Event

Description

Closed

Occurs when a form closes

InputLanguageChanged

Occurs when the user attempts to change the language of the form

Validated

Occurs when the control's input is validated


There are a few important events that you should know about when dealing with Windows Forms. You've already learned about Closed, which fires when a form closes. There is also a Load event, which fires immediately before a form is displayed for the first time. The event handler for this event is often a good place to initialize components on your form that you haven't already initialized in the constructor.

The Activated event occurs when your form gains focus and becomes the active application—it corresponds to the Activate method. Deactivate, on the other hand, is fired when your form is deactivated, that is, when it loses focus or another application become the active one.

Mouse and Keyboard Events

Mouse and keyboard actions are one of the most important types of events—after all, those are typically the only forms of user input. As such, there are quite a few events that pertain to these two input devices. Let's begin with the keyboard events.

First is the KeyPress event. This occurs anytime a key is pressed, no matter what key it is (we'll see in a moment how to determine which key it was). If this event doesn't provide enough control, there are also the KeyDown and KeyUp events, which fire when a key is pressed down, and then released, respectively.

These events, due to their nature, provide additional information (such as the specific key that is pressed) that you can use in your application. As such, their event handlers (the methods that execute when the event is fired) are specialized. Declaring these handlers in VB.NET is the same process that you're used to:

'VB.NET
AddHandler Form1.KeyPress, AddressOf methodName
AddHandler Form1.KeyDown, AddressOf methodName
AddHandler Form1.KeyUp, AddressOf methodName

In C#, however, you need to take note of the specialized handlers. Instead of using EventHandler as you did on line 18 of Listing 3.4:

18:     btAccept.Click += new EventHandler(this.AcceptIt);

you need to use the KeyPressEventHandler and KeyEventHandler objects:

//C#
Form1.KeyPress += new KeyPressEventHandler(methodName)
Form1.KeyDown += new KeyEventHandler(methodName)
Form1.KeyUp += new KeyEventHandler(methodName)

Like the CancelEventArgs object, the KeyPressEventHandler and KeyEventHandler objects have special properties that aid your application in determining what action caused the event.

The KeyPressEventHandler has two properties: Handled and KeyChar. The first method is simply a true or false value indicating if your method has handled the key press (if it hasn't, the key press is sent to the Windows OS for processing). Most of the time you'll want to set this property to true, unless you specifically want the OS to process that specific key (for example, if you don't need to handle the PrintScrn button, pass it to the OS). KeyChar simply returns the key that was pressed. Listing 3.6 shows an example in VB.NET.

Listing 3.6 Handling Key Presses

1: Imports System
2: Imports System.Windows.Forms
3: Imports System.Drawing
4: Imports System.ComponentModel
5: 
6: Namespace TYWinForms.Day3
7: 
8:   public class Listing36 : Inherits Form
9:    public sub New() 
10:      Me.Text = "Keypress Example"
11:      AddHandler Me.KeyPress, AddressOf Me.KeyPressed
12:    end sub
13:   
14:    public sub KeyPressed(Sender as Object, e as KeyPressEventArgs)
15:      Microsoft.VisualBasic.MsgBox(e.KeyChar)
16:      e.Handled = True
17:    end sub
18: 
19:    public shared sub Main()
20:      Application.Run(new Listing36)
21:    end sub
22:   end class
23: End Namespace

On line 11, you see the event handler, KeyPressed, for the KeyPress event assigned to the event. On line 14 you declare the event handler. Note that it takes a KeyPressEventArgs object parameter—this name corresponds to the KeyPressEventHandler object discussed earlier. On line 15 you simply display the character pressed in a message box, and then set the Handled property to true on line 16. Figure 3.11 shows the output when the capital A key is pressed (Shift+a).

Note, however, that only character, numeric, and the Enter keys fire the KeyPress event. To handle other keys (such as Ctrl, Alt, and the F1–F12 function keys), you need to use the KeyUp and KeyDown events. Recall that these events use handlers of type KeyEventHandler, so the second parameter of your event handler methods must be KeyEventArgs:

public sub KeyReleased(Sender as Object, e as KeyEventArgs)
  'some code
end sub

Figure 3.11 As long as your form has the focus, any key press executes the KeyPressed method.

The KeyEventArgs object has several properties that are useful in determining which key was pressed:

Every key on the keyboard has unique KeyCode, KeyData, and KeyValue values. The KeyCode and KeyValue properties are typically the same. KeyData is the same as the other two for most keys, but different on modifier keys (see the Keys enumeration in the .NET Framework documentation for a complete reference).

Mouse events occur in a standard order:

  1. MouseEnter—When the mouse cursor enters the form

  2. MouseMove—When the mouse cursor moves over the form

  3. MouseHover—When the cursor simply hovers over the form (without moving or clicking)

  4. MouseDown—When you press a mouse button on the form

  5. MouseUp—When you release the mouse button

  6. MouseLeave—When the mouse cursor leaves the form (moves out from over the form)

The MouseEnter, MouseLeave, and MouseHover events don't provide any special information, and therefore use the standard EventHandler event handler and EventArgs event parameter objects. The MouseMove, MouseDown, and MouseUp events, however, provide special information, and all use the MouseEventHandler MouseEventArgs objects:

public sub MouseClick(Sender as Object, e as MouseEventHandler)

The MouseEventHandler object provides information such as the cursor's exact position onscreen, which button was clicked, and so on. The properties are summarized in Table 3.3.

Table 3.3 MouseEventHandler Properties

Property

Description

Button

Gets which mouse button was pressed (MouseButtons.Left, MouseButtons.Middle, MouseButtons.None, MouseButtons.Right, MouseButtons.XButton1, or MouseButtons.XButton2)

Clicks

The number of times the mouse was clicked (an integer value)

Delta

The number of detents (or rotational notches) the mouse wheel has moved

X

The x screen coordinate of the mouse cursor

Y

The y screen coordinate of the mouse cursor


Drag-and-Drop

Drag-and-drop, a feature introduced with Windows, allows users to take shortcuts with applications by dragging an icon on their computer onto an application. The application takes the file represented by that icon, and does some processing. For example, if you have Microsoft Word open and drag an icon of a Word document into it, Word automatically opens that document for editing. Drag-and-drop also allows you to move and copy files from one folder to another using just the mouse.

Making your Windows Forms applications take advantage of drag-and-drop is a simple process. First, you must set the DragDrop property of the form to true, and then write code for the DragDrop, DragEnter, DragLeave, or DragOver events. The DragEnter, DragLeave, and DragOver events are much like to the similarly named mouse events; they occur when an icon is moved into, out of, or over your form. All of these events use the DragEventHandler object handler, and the DragEventArgs event parameter. Let's take a look at the properties of this parameter.

The AllowedEffect property is an indicator telling you what drag-and-drop actions can take place. For example, if you try to drag and drop a read-only file, you only copy, not move, that file. The actions are indicated by the DragDropEffects enumeration: DragDropEffects.All, DragDropEffects.Copy, DragDropEffects.Link, DragDropEffects.Move, DragDropEffects.None, and DragDropEffects.Scroll. All of these effects correspond to simple Windows functions.

The DragEventArgs.Effect property, then, indicates the effect that is taking place. This is one of the DragDropEffects values listed previously. For example, if the user is dragging a file and holds down the Ctrl key, a copy operation will attempt to be performed, and the Effect property will indicate DragDropEffects.Copy.

The Data property contains the item that is being dragged-and-dropped. This item, whatever it may be, is represented by the IdataObject object, which can represent many different types of objects. See the .NET Framework documentation for more information.

The KeyState property tells you if the Shift, Ctrl, or Alt keys are pressed, just like the Alt, Control, and Shift properties of the KeyUp and KeyDown events.

Finally, the X and Y properties are the same as those for the mouse events; they indicate at which point an item was located at the time of the event.

Changing Events

Any time one of Form's properties changes, there's usually an event associated with it. For example, when you change the Text property of the form, the TextChanged event fires.

Most of the time, these types of events are used for validation routines. For instance, if you want to limit the available fonts for use with your form, you could create a handler for the FontChanged event that overrides invalid user choices.

I won't list all of these events here. You can determine them simply by adding the word "Changed" to each of the properties you learned about earlier today—TextChanged, CursorChanged, VisibleChanged, and so on. All of these methods use a handler of type EventHandler.

Summary

The base object for all other objects in the .NET Framework is, appropriately titled, the Object object. Because it is so generic, it is used in several places where a specialized object is either not needed or unable to be determined. Object has five methods—Equals, ReferenceEquals, GetHashCode, GetType, and ToString—that all other .NET objects inherit.

The System.Windows.Forms.Form object, derived indirectly from Object, is typically the main piece of your Windows Forms applications. It provides the frame and background for other user interface pieces, and it provides a lot of functionality by itself.

The Form object has 101 properties that enable you to control nearly every single visual aspect of the UI. You can make your form transparent with the Opacity property, make it non-resizable with the FormBorderStyle property, and control how the user can interact with the form by modifying the MaximizeBox, MinimizeBox, HelpButton, ControlBox, or SizeGripStyle properties.

Additionally, Form has many methods, such as BringToFront and Focus, that you can execute to control the display.

Finally, you learned about events and the message loop, which enables an application to sit idly by until a user provides some input. The Form object has events that fire for many user actions, including mouse clicks, key presses, and dragging-and-dropping.

Tomorrow you'll learn to enhance your Windows Forms by adding menus and toolbars—standard features on almost every commercial application—to your applications. Then in Day 5, you'll examine events in more detail, and make your menus and toolbars functional!

Q&A

  1. Can I inherit from a form I created? How are properties dealt with?

  2. Absolutely, you can inherit from any class unless it is declared with the NotInheritable keyword (sealed in C#).

    Dealing with inherited properties is often a complex issue. For example, look at the following code:

    1: Imports System
    2: Imports System.Windows.Forms
    3: Imports System.Drawing
    4: 
    5: Public Class ClassA : Inherits Form
    6: private lblMessage as New Label
    7: 
    8: Public Sub New()
    9: lblMessage.Location = new Point(100,100)
    10: lblMessage.Height = 100
    11: lblMessage.Width = 200
    12: lblMessage.Text = "Hello World!"
    13: Me.Controls.Add(lblMessage)
    14: End Sub
    15: End Class
    16: 
    17: Public Class ClassB : Inherits ClassA
    18: private lblMessage as New Label
    19: 
    20: Public Sub New()
    21: lblMessage.Location = new Point(100,100)
    22: lblMessage.Height = 100
    23: lblMessage.Width = 200
    24: Me.Controls.Add(lblMessage)
    25: End Sub
    26: End Class
    27: 
    28: Public Class StartForm
    29: Public Shared Sub Main()
    30: Application.Run(New ClassB)
    31: End Sub 
    32: End Class

    When you compile and run this application, the form defined by ClassB will display. Note that nowhere in this class do you set the Text property of the label declared on line 18. However, when you run the application, the label contains the text "Hello World!" This text is set in ClassA on line 12.

    This is not because the label from ClassB was inherited from ClassA, but rather because in ClassA's constructor, a label is created with the text in question, and added to the form. Recall that the first thing that occurs in any constructor is a call to its parent's constructor. Thus, by the time ClassB's constructor executes, there's already a label there with the words "Hello World!" You cannot, however, modify that label from ClassB because on line 6, it is declared as private, and therefore only accessible from ClassA. If you changed it to public, then you would receive an error because ClassB would have inherited the label lblMessage, and line 18 is trying to re-declare it—not a valid task.

    The bottom line is to be careful when inheriting from your own classes, and pay attention to the security context in which variables are declared.

Workshop

This workshop will help reinforce the concepts covered in today's lesson. It is very helpful to understand fully the answers before moving on. Answers can be found in Appendix A.

Quiz

Questions 1–3 refer to the following code snippet:

1: using System;
2: using System.Windows.Forms;
3: using System.Drawing;
4: 
5: public class MyForm : Form {
6:   private Label lblMessage = new Label();
7: 
8:   public MyForm() {
9:    this.Text = "Hello World!";
10:   }
11: }
12: 
13: public class StartForm {
14:   public static void Main() {
15:    Application.Run(new MyForm());
16:   }
17: }  
  1. What would the following statement return if placed after line 9?

  2. Console.Write(this.ToString());
  3. What would the following code return if placed after line 9?

    Label lblTemp = new Label();
    Console.Write(lblTemp.Equals(this.lblMessage).ToString());
  4. What would the following code return if placed after line 9?

    Label lblTemp = new Label();
  5. Console.Write(Object.ReferenceEquals(lblTemp, this.lblMessage).ToString());
  6. True or false: The KeyPress event takes a handler of type KeyEventHandler.
  7. What are the five properties of the MouseEventArg object?
  8. Write a single statement in VB.NET that will set the width of a form to 1/3 of the height of the screen.

  9. What property controls which button will be activated when the user presses the Escape key?

  10. What is the default FormBorderStyle?

  11. Which three events use the two-event for a single action paradigm?

Exercises

  1. Build an application, in C#, that monitors all six of the mouse events. Display a message in a label when each of the events occurs.

  2. Build an application in VB.NET that allows users to customize the Text, Height, Width, and Opacity properties by entering values in a TextBox, and pressing a Submit Button.

800 East 96th Street, Indianapolis, Indiana 46240