Using the Microsoft .NET Framework to Create Windows-based Applications

 

Shawn Burke
Microsoft Corporation

Updated December 2001

Summary: This article introduces Windows Forms, a new forms package that enables developers to take full advantage of the UI features available in the Microsoft Windows operating system. (13 printed pages)

Contents

Introduction
What Is Windows Forms?
Smaller Learning Curve
Layout
GDI+
Access to the Underlying System
Conclusion

Introduction

With all of the current talk about the Web, it may appear that the Microsoft® Visual Studio® .NET development system has de-emphasized support for building traditional Microsoft Windows®-based applications. Actually, Microsoft is investing heavily in Windows-based application development.

Windows Forms is a new forms package that enables developers building Windows-based applications to take full advantage of the rich user interface features available in the Microsoft Windows operating system. Windows Forms is part of the new Microsoft .NET platform and leverages many new technologies, including a common application framework, managed execution environment, integrated security, and object-oriented design principles. In addition, Windows Forms offers full support for quickly and easily connecting to Web Services and building rich, data-aware applications based on the ADO+ data model. With the new shared development environment in Visual Studio .NET, developers will be able to create Windows Forms applications using any of the languages supporting the .NET platform, including Microsoft Visual Basic® .NET and C#.

What Is Windows Forms?

As just mentioned, Windows Forms is the .NET Framework's namespace dedicated to Windows client UI programming. It shares the same design principals as the ASP+ UI package, known as Web Forms, but the classes and their implementations are completely different. There are no classes that magically metamorphose between the Microsoft Win32® API and Web components. However, as with all of the .NET Framework, consistency has been a priority. The goal is for a Windows Forms developer to feel quickly comfortable writing code in Web Forms and vice versa. For example, both namespaces have a Button class, each of which has text, a default OnClick event, and the ForeColor, BackColor, and Font properties.

Windows Forms controls are all based on the class System.Windows.Forms.Control. Control has all the basic HWND functionality built in, and it handles most of the common WM_xxxx messages we've all come to know and love, as well as layout logic and painting code. Most of the controls in the System.Windows.Forms namespace are actually derived from Control. ScrollableControl adds support for scrolling the client area of a window. Generally, that scrolling support is accessed through ContainerControl, which derives from ScrollableControl and adds support for managing child controls, focus issues, and tabbing. Derived from ContainerControl is Form, Windows Form's top-level control, which has properties to control caption bars, system menus, non-rectangular windowing, and default controls. Also derived from ContainterControl is UserControl, which is the base class for controls that developers can build. UserControl is intended to host other child controls but to be exposed as a single unit to outside clients. UserControl and Form both have visual designers in Microsoft Visual Studio .NET and you'll find project items for adding and designing classes derived from them.

Figure 1. Windows Forms controls hierarchy

Now that we've covered the (very) basics of Windows Forms, let's talk about some of the great features that lie just beneath the surface.

Smaller Learning Curve

The primary mission of Windows Forms is to enable developers to be as productive as possible when targeting the Win32 platform. Whether it's Graphics Device Interface (GDI) or window state management, programming for Win32 can often be difficult. For example, certain window styles, such as WS_BORDER or WS_CAPTION, can only be specified or modified at window creation. However, other styles, such as WS_VISIBLE or WS_CHILD, can be modified on an already created window. Windows Forms strives to eliminate such subtleties and ensure that operations work in a consistent manner at all times. Properties on Windows Forms controls can be set at any time in any order and produce the desired effect. In the instance of changes that require a new HWND to be created, the Windows Forms framework automatically and transparently re-creates the window and applies all the appropriate settings to it.

Getting notification or events from controls is also easier from Windows Forms. All Windows Forms events are based on a feature of the Common Language Runtime (CLR) called Delegates. Delegates are basically type-safe, secure function pointers. For any event on any control, a delegate handler can be added; you are never forced to create a derived class to handle an event through an override, create an event map, or implement an interface for all the events on a class to handle just one. Events can also be handled by overriding derived classes, but this is generally for control creators and more advanced applications. Sinking a button's Click event is simple:

public class ButtonClickForm: System.Windows.Forms.Form {
private System.Windows.Forms.Button button1;
public ButtonClickForm() {
// create the button
button1 = new System.Windows.Forms.Button();      
// add the handler
button1.Click += new System.EventHandler(button1_Click);
// add the button to the form
this.Controls.Add(button1);
  }

private void button1_Click(object sender, EventArgs e) {
MessageBox.Show("button1 clicked!");
  }
    }

Here, we've created a button and added a handler method, button1_Click, which will be invoked with just a few lines of code when the button is clicked. Notice that even though the handler method is marked private, the code that created the hookup had access to the method, and the button will be able to fire the event to this method when it is clicked.

Getting started with Windows Forms projects is also made easy. Creating a Windows Forms project with Visual Studio .NET creates only one project file that will be compiled: Form1.cs. There are no header files, no interface definition files, no bootstrap application files, no resource files, and no library files. All of the information needed for the project is contained in the code for the form. The result is projects that scale much more readily from a simple one-form application to a complex, multi-form application with many code files. There are no intermediate object files required for linking, just the code and any managed DLLs that have already been built. As you get used to this methodology, the difference in complexity of just building a .NET Framework application and that of a C/C++ application becomes obvious. And because the information is contained in just the code files, creating build processes outside of the Visual Studio .NET environment is also easy to do, be it Visual Basic code, C# code, or code written in any other language that targets the .NET Framework.

Because Windows Forms is built on the CLR, it allows developers to choose any one of the many languages that are now targeting the CLR to build Win32 applications. Developers can now write Windows Forms applications (or Web Forms applications or Data applications) in a stunning variety of languages ranging from C# to COBOL to Eiffel to Perl, with many languages (17 at last count) in between. Ease of use plus broad access adds up to developers of many backgrounds quickly being productive with Windows Forms to build real-world applications.

Layout

If you have ever tried to create a form that responds properly to resizing, you know how difficult it can be. Microsoft Foundation Classes (MFC) or prior versions of Visual Basic don't have any built-in support for this. But with just a few lines of code (usually you don't even write them because the features are available through the Property Browser at design time!), you can create a dialog box that resizes properly.

Basic Layout is made up of two pieces: Anchoring and Docking. Control has an Anchor property, which is an enumerated type whose values can be OR'd together to describe from which edges of a control's parent control it will maintain a constant distance. For example, if you put a button on a form and set the Anchor property to AnchorStyles.BottomRight, the button will remain the same distance from the bottom and right edges of the form when it is resized. Moreover, if you set Anchor to AnchorStyles.All, all sides of the button will remain anchored to the corresponding edges of the form, and the button will resize to meet these constraints.

Docking is really just a special case of Anchoring. The Dock property in Control describes which edge of the parent control a control is to fasten itself to. Docking can be Top, Left, Right, Bottom, or Fill. In each case, the control is moved as close as possible to the specified edge and sized to fill that edge, where it will remain when the parent is resized. Moving a control to the bottom of a parent and setting Anchor to AnchorStyle.BottomLeftRight can simulate Docking Bottom. In the example here, the list box is Docked Left, and the buttons are anchored to the top, left, and right of the form so they maintain their relative positions and size. The example dialog box to follow (Figure 2) was created completely with the Windows Forms Designer in Visual Studio .NET. It took about two minutes to create and did not require writing a single line of code.

Figure 2. Resizable dialog box created using the Windows Forms Designer

// ResizableSample.cs
namespace ResizableSampleNamespace {
    
using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;

/// <summary>
///    Summary description for ResizableSample.
/// </summary>
public class ResizableSample : System.Windows.Forms.Form {
/// <summary> 
///    Required by the Windows Forms designer 
/// </summary>
private System.ComponentModel.Container components;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.ListBox listBox1;
public ResizableSample() {
// Required for Windows Forms Designer support
InitializeComponent();

        }

/// <summary>
///    Clean up any resources being used
/// </summary>
public override void Dispose() {
base.Dispose();
components.Dispose();
        }


/// <summary>
///    The main entry point for the application.
/// </summary>
public static void Main(string[] args) {
Application.Run(new ResizableSample());
        }


/// <summary>
        ///    Required method for Designer support - do not modify
        ///    the contents of this method with an editor
/// </summary>
private void InitializeComponent()
   {
this.components = new System.ComponentModel.Container();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.button1 = new System.Windows.Forms.Button();
this.listBox1 = new System.Windows.Forms.ListBox();
//@design this.TrayLargeIcon = false;
//@design this.TrayHeight = 0;
this.Text = "Resizable Dialog";
this.IMEMode = System.Windows.Forms.IMEMode.Off;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(256, 173);
button2.Location = new System.Drawing.Point(152, 60);
button2.Size = new System.Drawing.Size(92, 32);
button2.TabIndex = 2;
button2.Anchor = System.Windows.Forms.AnchorStyles.TopLeftRight;
button2.Text = "Cancel";
button3.Location = new System.Drawing.Point(152, 120);
button3.Size = new System.Drawing.Size(92, 44);
button3.TabIndex = 3;
button3.Anchor = System.Windows.Forms.AnchorStyles.All;
button3.Text = "Filler";
button1.Location = new System.Drawing.Point(152, 8);
button1.Size = new System.Drawing.Size(92, 32);
button1.TabIndex = 1;
button1.Anchor = System.Windows.Forms.AnchorStyles.TopLeftRight;
button1.Text = "OK";
listBox1.Size = new System.Drawing.Size(120, 173);
listBox1.Dock = System.Windows.Forms.DockStyle.Left;
listBox1.TabIndex = 0;
listBox1.Items.All = new object[] {"Item One",
"Item Two",
"Item Three",
"Item Four"};
this.Controls.Add(button3);
this.Controls.Add(button2);
this.Controls.Add(button1);
this.Controls.Add(listBox1);
   }

    }
}

GDI+

Windows Forms takes full advantage of GDI+, Microsoft's next generation 2-D graphics system. The graphics programming model in Windows Forms is fully object-oriented and the assorted Pens, Brushes, Images, and other graphics objects are designed following the same ease-of-use guidelines as the rest of the .NET Framework. Developers can now include great new drawing features, such as alpha blending, color gradients, textures, anti-aliasing, and image formats other than bitmaps. When coupled with the Windows 2000 operating system's layered and transparent windows features, developers can create richer, more graphical Win32 applications with much less effort.

When a control's OnPaint event is fired, the System.Drawing.Graphics object that is accessible from the PaintEventArgs is a GDI+ graphics object. All of the operations that the graphics object can perform execute through GDI+. As an example, we can create a button that paints a gradient background using GDI+.

Figure 3. Button created using GDI+

Here's the code used to implement the button:

public class GradientButton : Button {
// members to hold our color settings
private Color startColor;
private Color endColor;
            
// we'll need this to paint the text
private static StringFormat format = new StringFormat();
public GradientButton() : base() {
// initialize our colors 
startColor = SystemColors.InactiveCaption;
endColor = SystemColors.ActiveCaption;
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
    }
            
/// <summary>
/// The end color for our gradient
// </summary>
public Color EndColor {
get {
return this.endColor;
         }
set {
this.endColor = value;
// cause a repaint if necessary
if (this.IsHandleCreated && this.Visible) {
Invalidate();
            }
         }
   }

            
/// <summary>
/// The start color for our gradient
// </summary>
public Color StartColor {
get {
return this.startColor;
         }
set {
this.startColor = value;
// cause a repaint if necessary
if (this.IsHandleCreated && this.Visible) {
Invalidate();
           }
        }
   }
    
protected override void OnPaint(PaintEventArgs pe) {
// paint the regular button background to get the 
// borders, etc.               
base.OnPaint(pe);
Graphics g = pe.Graphics;
Rectangle clientRect = this.ClientRectangle;
// deflate the rect so we don't paint over the border
clientRect.Inflate(-1,-1);
// create our gradient brush which will run from
// the top left to the bottom right.
Brush backgroundBrush = new LinearGradientBrush(
new Point(clientRect.X,clientRect.Y), 
new Point(clientRect.Width, clientRect.Height), 
startColor, 
endColor);
// fill the background with the gradient....
g.FillRectangle(backgroundBrush, clientRect);
// draw the text in the center of the client area.
g.DrawString(this.Text, 
this.Font, 
new SolidBrush(this.ForeColor), 
clientRect, 
format);
   }
}

As you can see, it's not very difficult. The object-oriented design of Windows Forms and GDI+ allows us to implement our GradientButton with out any complex code, and in the designer, Text, Font, StartColor, and EndColor can be manipulated through the Property Browser.

Access to the Underlying System

One shortfall the framework has is that although it works great for people who are building exactly the types of applications featured in samples and demonstrations, sometimes developers find that once they try to do something creative with the framework, they run into roadblocks or get bitten by special cases. The Windows Forms framework always allows developers to access the system underpinnings if such cases are encountered. Of course, the hope is that a well-designed framework like Windows Forms will not fall victim to such circumstances, but there is an infinite number of possible scenarios. All controls have a Handle property that allows access to the control's window handle (HWND), and the GDI objects offer similar handle access. Moreover, Control actually has a protected virtual method called WndProc that can be overridden to add handling for the few messages Windows Forms doesn't already support.

For example, say your application was resource intensive and needed to respond to WM_COMPACTING. WM_COMPACTING is broadcast to all top-level windows when the system detects memory is low, and you learn that the Windows Forms framework does not have built-in support for this message. You could add handling support as follows:

/// <summary>
///    Summary description for Win32Form1.
/// </summary>
public class CompactableForm : System.Windows.Forms.Form {
public event EventHandler Compacting;


protected override void OnCompacting(EventArgs e) {
// see if the runtime can free anything
System.GC.Collect();
// call any handlers.
if (Compacting != null) Compacting(this, e);
   }



protected override void WndProc(ref Message m) {
case (m.msg) {
case win.WM_COMPACTING:
OnCompacting(EventArgs.Empty);
break;
      }
base.WndProc(m);
   }

    }

With just a few lines of code, any classes that derive from or utilize your new CompactableForm class can be notified and respond when the system is trying to gather unused resources.

Conclusion

Even though Web-targeted development is the current focus of many developers' plans, targeting the familiar Win32 platform is still a compelling scenario. With Windows Forms, novice and veteran Windows programmers will find it easy to create sophisticated applications with rich interfaces, and that interface well with many of the Web-enabled and data-enabled technologies in the .NET Framework.

By leveraging the great productivity features of the Common Language Runtime, such as cross-language inheritance, garbage collection, and security, developers will benefit from the .NET Framework and Windows Forms.