Chapter 4. Windows Forms I: Developing Desktop Applications

Windows Forms is a set of classes that encapsulates the creation of the graphical user interface (GUI) portion of a typical desktop application. Previously, each programming language had its own way of creating windows, text boxes, buttons, etc. This functionality has all been moved into the .NET Framework class library—into the types located in the System.Windows.Forms namespace. Closely related is the System.Drawing namespace, which contains several types used in the creation of GUI applications. The capabilities provided by the types in the System.Drawing namespace are commonly referred to as GDI+ (discussed more fully later in this chapter).

In this chapter, we’ll examine the form (or window) as the central component in a classic desktop application. We’ll look at how forms are programmatically created and how they’re hooked to events. We’ll also examine how multiple forms in a single application relate to one another and how you handle forms in an application that has one or more child forms. Finally, we’ll discuss two topics, printing and 2-D graphics, that are relevant to desktop application development.

Creating a Form

The easiest way to design a form is to use the Windows Forms Designer in Visual Studio .NET. The developer can use visual tools to lay out the form, with the designer translating the layout into Visual Basic .NET source code. If you don’t have Visual Studio .NET, you can write the Visual Basic .NET code directly and not use the designer at all. This section will demonstrate both methods.

Programmatically, a form is defined by deriving a class from the Form class (defined in System.Windows.Forms). The Form class contains the know-how for displaying an empty form, including its title bar and other amenities that we expect from a Windows form. Adding members to the new class and overriding members inherited from the Form class add visual elements and behavior to the new form.

Creating a Form Using Visual Studio .NET

To create a GUI application in Visual Studio .NET:

  1. Select FileNewProject. The New Project dialog box appears, as shown in Figure 4-1.

    The New Project dialog box

    Figure 4-1. The New Project dialog box

  2. Select Visual Basic Projects in the Project Types pane on the left side of the dialog box.

  3. Select Windows Application in the Templates pane on the right side of the dialog box.

  4. Enter a name in the Name text box.

  5. Click OK. Visual Studio .NET creates a project with a form in it and displays the form in a designer, as shown in Figure 4-2.

The Windows Forms Designer

Figure 4-2. The Windows Forms Designer

To see the code created by the form Windows Forms Designer, right-click on the form, then select View Code. Doing this for the blank form shown in Figure 4-2 reveals the code shown here:

Public Class Form1
    Inherits System.Windows.Forms.Form

 Windows Form Designer generated code

End Class

This shows the definition of a class named Form1 that inherits from the Form class. The Windows Forms Designer also creates a lot of boilerplate code that should not be modified by the developer. By default, it hides this code from view. To see the code, click on the “+” symbol that appears to the left of the line that says “Windows Form Designer generated code.” Doing so reveals the code shown in Example 4-1.

Example 4-1. The Windows Forms Designer-generated code for a blank form

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Public Sub New(  )
      MyBase.New(  )

      'This call is required by the Windows Form Designer.
      InitializeComponent(  )

      'Add any initialization after the InitializeComponent(  ) call

   End Sub

   'Form overrides dispose to clean up the component list.
   Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
         If Not (components Is Nothing) Then
            components.Dispose(  )
         End If
      End If
      MyBase.Dispose(disposing)
   End Sub

   'Required by the Windows Form Designer
   Private components As System.ComponentModel.Container

   'NOTE: The following procedure is required by the Windows Form Designer
   'It can be modified using the Windows Form Designer.  
   'Do not modify it using the code editor.
   <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )
      components = New System.ComponentModel.Container(  )
      Me.Text = "Form1"
   End Sub

#End Region

End Class

The Windows Forms Designer autogenerates the code for four class members:

New method (the class constructor)

The constructor calls the base class’s constructor and then invokes the InitializeComponent method. Developer-supplied initialization code should follow the call to InitializeComponent. After the constructor is generated, the designer doesn’t touch it again.

Dispose method

The Dispose method is where the object gets rid of any expensive resources. In this case, it calls the base class’s Dispose method to give it a chance to release any expensive resources that it may hold, then it calls the components field’s Dispose method. (For more on the components field, see the next item.) This in turn calls the Dispose methods on each individual component in the collection. If the derived class uses any expensive resources, the developer should add code here to release them. When a form is no longer needed, all code that uses the form should call the form’s Dispose method. After the Dispose method is generated, the designer doesn’t touch it again.

Components field

The components field is an object of type IContainer (defined in the System.ComponentModel namespace). The designer-generated code uses the components field to manage finalization of components that may be added to a form (for example, the Timer component).

InitializeComponent method

The code in this method should not be modified or added to by the developer in any way. The Windows Forms Designer automatically updates it as needed. When controls are added to the form using the designer, code is added to this method to instantiate the controls at runtime and set their initial properties. Note also in Example 4-1 that properties of the form itself (such as Text and Name) are initialized in this method.

One thing missing from this class definition is a Main method. Recall from Chapter 2 that .NET applications must expose a public, shared Main method. This method is called by the CLR when an application is started. So why doesn’t the designer-generated form include a Main method? It’s because the Visual Basic .NET compiler in Visual Studio .NET automatically creates one as it compiles the code. In other words, the compiled code has a Main method in it even though the source code does not. The Main method in the compiled code is a member of the Form1 class and is equivalent to this:

<System.STAThreadAttribute(  )> Public Shared Sub Main(  )
   System.Threading.Thread.CurrentThread.ApartmentState = _
      System.Threading.ApartmentState.STA
   System.Windows.Forms.Application.Run(New Form1(  ))
End Sub

Note that the Visual Basic .NET command-line compiler doesn’t automatically generate the Main method. This method must appear in the source code if the command-line compiler is to be used.

The next steps in designing the form are to name the code file something meaningful and to set some properties on the form, such as the title-bar text. To change the name of the form’s code file, right-click on the filename in the Solution Explorer window and select Rename. If you’re following along with this example, enter HelloWindows.vb as the name of the file.

Changing the name of the file doesn’t change the name of the class. To change the name of the class, right-click the form in the designer and choose Properties. In the Properties window, change the value of the Name property. For this example, change the name to “HelloWindows”.

To change the form’s caption, set the form’s Text property to a new value. Set the Text property in this example to “Programming Visual Basic .NET”.

Next, controls can be added to the form from the Visual Studio .NET toolbox. To display the toolbox, select ViewToolbox from the Visual Studio .NET main menu. For this example, double-click on the Label control in the toolbox to add a Label control on the form. Use the Properties window to change the label’s Text property to “Hello, Windows!” and its Font property to Arial 24pt.

Next, double-click on the Button control in the toolbox to add a Button control to the form. Use the Properties window to change the button’s Name property to “OkButton” and its Text property to “OK”.

Finally, position the controls as desired, size the Label control and the form to be appealing, and set the form’s FormBorderStyle property to “FixedToolWindow”. The resulting form should look something like the one shown in Figure 4-3.

A form with controls

Figure 4-3. A form with controls

Press the F5 key to build and run the program. The result should look something like Figure 4-4.

Hello, Windows!, as created by the Windows Forms Designer

Figure 4-4. Hello, Windows!, as created by the Windows Forms Designer

The code generated by the designer is shown in Example 4-2.

Example 4-2. Hello, Windows! code, as generated by the Windows Forms Designer

Public Class HelloWindows
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Public Sub New(  )
      MyBase.New(  )

      'This call is required by the Windows Form Designer.
      InitializeComponent(  )

      'Add any initialization after the InitializeComponent(  ) call

   End Sub

   'Form overrides dispose to clean up the component list.
   Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
         If Not (components Is Nothing) Then
            components.Dispose(  )
         End If
      End If
      MyBase.Dispose(disposing)
   End Sub
   Friend WithEvents Label1 As System.Windows.Forms.Label
   Friend WithEvents OkButton As System.Windows.Forms.Button

   'Required by the Windows Form Designer
   Private components As System.ComponentModel.Container

   'NOTE: The following procedure is required by the Windows Form Designer
   'It can be modified using the Windows Form Designer.  
   'Do not modify it using the code editor.
   <System.Diagnostics.DebuggerStepThrough(  )> _
   Private Sub InitializeComponent(  )
      Me.Label1 = New System.Windows.Forms.Label(  )
      Me.OkButton = New System.Windows.Forms.Button(  )
      Me.SuspendLayout(  )
      '
      'Label1
      '
      Me.Label1.Font = New System.Drawing.Font("Arial", 24!, _
         System.Drawing.FontStyle.Regular, _
         System.Drawing.GraphicsUnit.Point, CType(0, Byte))
      Me.Label1.Location = New System.Drawing.Point(8, 8)
      Me.Label1.Name = "Label1"
      Me.Label1.Size = New System.Drawing.Size(264, 48)
      Me.Label1.TabIndex = 0
      Me.Label1.Text = "Hello, Windows!"
      '
      'OkButton
      '
      Me.OkButton.Location = New System.Drawing.Point(280, 16)
      Me.OkButton.Name = "OkButton"
      Me.OkButton.TabIndex = 1
      Me.OkButton.Text = "OK"
      '
      'HelloWindows
      '
      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
      Me.ClientSize = New System.Drawing.Size(362, 58)
      Me.Controls.AddRange(New System.Windows.Forms.Control(  ) _
         {Me.OkButton, Me.Label1})
      Me.FormBorderStyle = _
         System.Windows.Forms.FormBorderStyle.FixedToolWindow
      Me.Name = "HelloWindows"
      Me.Text = "Programming Visual Basic .NET"
      Me.ResumeLayout(False)

   End Sub

#End Region

End Class

Note that the designer made the following modifications to the code:

  • Two Friend fields were added to the class, one for each of the controls that were added to the form:

    Friend WithEvents Label1 As System.Windows.Forms.Label
    Friend WithEvents OkButton As System.Windows.Forms.Button

    The Friend keyword makes the members visible to other code within the project, but it hides them from code running in other assemblies.

    The WithEvents keyword allows the HelloWindows class to handle events generated by the controls. In the code shown, no event handlers have been added yet, but you’ll see how to do that later in this section.

    Note that the field names match the control names as shown in the Properties window.

  • Code was added to the InitializeComponent method to instantiate the two controls and assign their references to the member fields:

    Me.Label1 = New System.Windows.Forms.Label(  )
    Me.OkButton = New System.Windows.Forms.Button(  )
  • Code was added to the InitializeComponent method to set various properties of the label, button, and form. Some of these assignments directly correspond to the settings made in the Properties window, while others are the implicit result of other actions taken in the designer (such as sizing the form).

Adding event handlers

The Hello, Windows! application built thus far has an OK button, but the application doesn’t yet respond to button clicks. To add a Click event handler for the OK button, double-click on the button in the Windows Forms Designer. The designer responds by switching to the form’s code view and inserting a subroutine that handles the Click event (i.e., it will be called when the user of the running application clicks the OK button). The subroutine the designer creates looks like this (note that I added the line-continuation character for printing in this book):

Private Sub OkButton_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles OkButton.Click

End Sub

The body of the subroutine can then be added. This would be a likely implementation for this event handler:

Private Sub OkButton_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles OkButton.Click
   Me.Close(  )
   Me.Dispose(  )
End Sub

An alternative way to add an event handler is to use the drop-down lists at the top of the form’s code-view window. In the lefthand drop-down list, choose the object for which you would like to add an event handler. Then, in the righthand drop-down list, choose the desired event. See Figure 4-5.

Adding an event handler using the code view’s drop-down lists

Figure 4-5. Adding an event handler using the code view’s drop-down lists

Event handlers can be typed directly into the form’s code if you know the correct signature for the handler. Event-handler signatures are documented in the Microsoft Developer Network ( MSDN) Library.

Creating a Form in Code

Although form designers are convenient, it is certainly possible to code a form directly. To do so, follow these steps:

  1. Define a class that is derived from the Form class (defined in the System.Windows.Forms namespace). If the form is to be the startup form for an application, include a public, shared Main method. For example:

    Imports System.Windows.Forms
    
    Public Class HelloWindows
       Inherits Form
    
       ' Include this method only if this is the application's startup form.
       ' Alternatively, place this method in a separate module in the
       ' application. If it is placed in a separate module, remove the
       ' Shared keyword.
       <System.STAThreadAttribute(  )> Public Shared Sub Main(  )
          System.Threading.Thread.CurrentThread.ApartmentState = _
             System.Threading.ApartmentState.STA
          Application.Run(New HelloWindows(  ))
       End Sub ' Main
    
    End Class
  2. Declare a data member for each control that is to appear on the form. If you want to handle events from the control, use the WithEvents keyword in the declaration. For example:

    Imports System.Windows.Forms
    
    Public Class HelloWindows
       Inherits Form
    
       Private lblHelloWindows As Label
       Private WithEvents btnOK As Button
    
       <System.STAThreadAttribute(  )> Public Shared Sub Main(  )
          System.Threading.Thread.CurrentThread.ApartmentState = _
             System.Threading.ApartmentState.STA
          Application.Run(New HelloWindows(  ))
       End Sub ' Main
    
    End Class

    The visibility (Private, Friend, Protected, or Public) of these data members is a design issue that depends on the project and on the developer’s preferences. My own preference is to make all data members private. If code external to the class needs to modify the data held by these members, specific accessor methods can be added for the purpose. This prevents internal design changes from affecting external users of the class.

  3. Declare a constructor. Perform the following operations in the constructor:

    1. Instantiate each control.

    2. Set properties for each control and for the form.

    3. Add all controls to the form’s Controls collection.

    For example:

                         
    Imports System.Drawing
    Imports System.Windows.Forms
    
    Public Class HelloWindows
       Inherits Form
    
       Private lblHelloWindows As Label
       Private WithEvents btnOK As Button
    
       Public Sub New
    
          ' Instantiate a label control and set its properties.
          lblHelloWindows = New Label(  )
          With lblHelloWindows
             .Font = New Font("Arial", 24)
             .Location = New Point(16, 8)
             .Size = New Size(248, 40)
             .TabIndex = 0
             .Text = "Hello, Windows!"
          End With
    
          ' Instantiate a button control and set its properties.
          btnOK = New Button(  )
          With btnOK
             .Location = New Point(320, 16)
             .TabIndex = 1
             .Text = "OK"
          End With
    
          ' Set properties on the form.
          FormBorderStyle = FormBorderStyle.FixedToolWindow
          ClientSize = New Size(405, 61)
          Text = "Programming Visual Basic .NET"
    
          ' Add the controls to the form's Controls collection.
          Controls.Add(lblHelloWindows
          Controls.Add(btnOK)
       End Sub
    
       <System.STAThreadAttribute(  )> Public Shared Sub Main(  )
          System.Threading.Thread.CurrentThread.ApartmentState = _
             System.Threading.ApartmentState.STA
          Application.Run(New HelloWindows(  ))
       End Sub ' Main
    
    End Class

    An Imports statement was added to give access to types in the System.Drawing namespace, such as Point and Size.

Adding event handlers

Define event handlers directly in code for any events that you wish to handle. For example:

Private Sub btnOK_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles btnOK.Click
   Close(  )
   Dispose(  )
End Sub

The complete code for a standalone Windows Forms application is shown in Example 4-3. Compile it from the command line with this command:

vbc HelloWindows.vb /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll /t:winexe

(Note that the command should be typed on a single line.)

Example 4-3. Hello, Windows! code generated outside of Visual Studio

Imports System.Drawing
Imports System.Windows.Forms 
Public Class HelloWindows 
   Inherits Form
   Private lblHelloWindows As Label 
   Private WithEvents btnOK As Button

   Private Sub btnOK_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles btnOK.Click
      Close()
      Dispose()
   End Sub 
   Public Sub New 
      ' Instantiate a label control and set its properties.
      lblHelloWindows = New Label ()
      With lblHelloWindows
         .Font = New Font("Arial", 24)
         .Location = New Point(16, 8)
         .Size = New Size(248, 40)
         .TabIndex = 0
         .Text = "Hello, Windows!"
      End With 
      ' Instantiate a button control and set its properties. 
      btnOK = New Button()
      With btnOK
         .Location = New Point(320, 16)
         .TabIndex = 1
         .Text = "OK"
      End With
      ' Set properties on the form. 
      FormBorderStyle = FormBorderStyle.FixedToolWindow
      ClientSize = New Size(405, 61)
      Text = "Programming Visual Basic .NET"
      ' Add the controls to the form's Controls collection. 
      Controls.Add(lblHelloWindows)
      Controls.Add(btnOK)
   End Sub 

   <System.STAThreadAttribute()> Public Shared Sub Main()
      System.Threading.Thread.CurrentThread.ApartmentState = _
         System.Threading.ApartmentState.STA
      Application.Run(New HelloWindows())
   End Sub ' Main
End Class

Get Programming Visual Basic .NET now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.