Get on Form with VB.NET
Counterintuitive as it might seem, .NET isn't only about the Web. Microsoft also wants us to use .NET technology, notably Windows Forms, to create what it refers to as "rich" clients. In this column, Rob Macdonald investigates Windows Forms.
Most of you realize that VB, Access, and VC++ forms are incompatible and that their underlying forms engines were developed by different teams. Well, all of that changes in .NET, where all languages use exactly the same forms engine.
Most VB programmers will feel at home with .NET Windows Forms. You place controls on a form and write event handlers, while .NET runs a background loop that scoops up mouse moves, key presses, and the like and routes them through to the handler you've written. There are differences, however. For starters, there's no Form_Load event!
A long time ago, Load was the first event that fired on a form. Then, when VB4 turned forms into objects, the Initialize event became the front-runner. Although some developers found creative ways to use the Initialize event (so long as you avoided referring to the form's controls), most developers stuck to using the Load event.
A .NET Windows Form is just like any other .NET object and has a constructor that runs when it's created. You can place all of your initialization code in the constructor—making it essentially the replacement for Form_Load. In VB.NET, this means coding a Sub New method, as I described in my November 2000 column ("It's VB, Jim, but Not as We Know it"). The details of how to perform housekeeping on a form when it's no longer needed are likely to change between the time of this writing and the release of Beta 2, so I'll skip the full story. However, it's helpful to know that forms have a "Closed" event that fires when the user closes a form or when its Close method is called.
If you're creating a one-form application, there isn't much more you need to know about the basic life-cycle of a form to get started (set the StartUp object in the Project Properties dialog box and away you go), but if you want to be able to display one form from another one, there is a "fudge" that most of us exploited in VB6 that no longer applies in VB.NET. Consider the following VB6 code:
'THIS IS VB 6 Form2.Show
In object terms, we know that Form2 is really a class, and we shouldn't be able to display a class. We know that, in reality, VB creates a hidden instance of the Form2 class, so that when we say "Form2.Show;" VB assumes we're referring to the hidden instance (I wrote extensively about this phenomenon in one of my very first articles for Visual Basic Developer, back in November 1997—"Forms as Objects").
VB.NET doesn't play this kind of trick (no—it has its very own trick, which I'll show you shortly!). Although Windows Forms do have a Show method, the following code will generate a compile-time error:
'THIS IS VB.NET and it doesn't work Form2.Show()
Instead, we have to be well-behaved object-oriented programmers, like so:
'THIS IS VB.NET and it DOES work! Dim frm As New Form2() frm.Show()
In this case, the Show method displays an instance of Form2 modelessly. To display it modally, use the ShowDialog method.
For your own sanity, it's worth understanding VB.NET's special little trick right from the start, and to help you do so, Listing 1 shows you the code that Visual Studio generates when you add a new form to a project. I've stripped out some comments and shown some code that's usually hidden from view so that you can see the whole picture.
Figure 1. Auto-generated code for a new Windows Form.
Imports System.Drawing Imports System.WinForms Imports System.ComponentModel Public Class Form2 Inherits System.WinForms.Form Private components As _ System.ComponentModel.Container Dim WithEvents Form2 As System.WinForms.Form Public Sub New() MyBase.New Form2 = Me InitializeComponent() End Sub Overrides Public Sub Dispose() MyBase.Dispose components.Dispose End Sub Private Sub InitializeComponent() components = New System.ComponentModel.Container Me.Text = "Form2" End Sub End Class
There are several interesting things about this code, but I'd ask you to focus on the Dim WithEvents statement that declared a private property with the same name as the class (Form2). Notice also that in the constructor code, Form2 is given the value Me, so that it refers to the current instance of the form.
What this means is that inside the code for this form, I can assign a new caption (the Caption property has been replaced with the Text property) for the current instance, using code such as this:
'Form2 is a synonym for Me Form2.Text = "Hello World"
If I tried to use the same code outside of Form2, the compiler would complain because Form2 refers to a class, not an instance. By creating a private property with the same name as the class, VB.NET tricks us into thinking we can continue to mix up the concepts of class and instance when referring to a form (well, some of the time, anyway). Personally, I find this confusing and have decided to always use Me inside my form's code (which is just what the code generator has done in the InitializeComponent subroutine in Listing 1!).
.NET doesn't natively use ActiveX or COM, and therefore VB.NET ships with a completely new set of controls, implemented as .NET components. There are .NET versions of all of the old favorites, as well as some completely new controls. Table 1 lists a small subset of those available to give you a flavor of what to expect.
Table 1. A sample of Windows Forms controls.
|Informational||Label, LinkLabel, StatusBar|
|Text Edit||TextBox, RichEdit|
|Selection||CheckedListBox, ComboBox, DomainUpDown, ListBox, ListView, NumericUpDown, TreeView|
|Graphics Display||PictureBox, Panel, ImageList|
|Value Setting||CheckBox, CheckedListBox, RadioButton, Trackbar, DateTimePicker|
|Dialog Boxes||ColorDialog, FontDialog, OpenFileDialog, PrintDialog, SaveFileDialog|
|Menu Controls||MainMenu, ContextMenu|
|Action Controls||Button, Toolbar, TrayIcon|
|Grouping Controls||Panel, GroupBox, TabControl|
On the off-chance that your "must-have" ActiveX control isn't available, Visual Studio.NET will automatically generate a wrapper for an existing ActiveX control. All you need to do is right-click on the ToolBox and select "Customize ToolBox." You can then select your ActiveX control (or add additional .NET controls to your ToolBox), and Visual Studio will do the rest. Needless to say, it's better to use a .NET equivalent if there is one.
It won't take you long to notice that even the most familiar-looking .NET controls have subtle differences relative to the VB6 controls. It always drove me mad that the TextBox control displayed its output in a Text property, while the Label control used the Caption property. These inconsistencies are gone. Although it still takes a while to get used to typing Label.Text, it makes me smile every time I have to check myself—some things are a pleasure to re-learn. Be aware, however, that there are no longer any default properties on controls (or any .NET objects, for that matter). If this grates, console yourself that the payback is losing the Set keyword. Also note that Height, Width, Left, and Top have been replaced by Location and Size, which are both Points (with an X and a Y part).
The actual practice of placing controls on a form and coding their events will be second nature to VB developers. All of the standard formatting and alignment features introduced in VB6 are there, as well as a graphical editor for assigning tab orders (see Figure 1).
One feature in the original VB "Ruby" forms engine that no one is likely to miss is the end of the distinction between design time-only and runtime-only properties. In Windows Forms, all properties are modeless, meaning, for example, that you can change the style of a ComboBox from DropDown to DropDownList at runtime. There are many situations where I used to need to use two controls, one always hidden, simply because some properties that you can change at design time became read-only at runtime. Sound familiar? Well, with Windows Forms, we won't have to resort to that sort of subterfuge any more.
Another change is that a control's property settings aren't written to a .FRM file. In fact, there aren't any .FRM files—forms are just a specific type of class, and they're defined in VB files along with all other code. When you place or edit a control using the form's designer, Visual Studio maintains code in the InitializeComponent subroutine (see Listing 1). This code (which is usually hidden from view) is called from the form's constructor and is responsible for creating and initializing all controls dynamically. It follows that writing your own code to create and manipulate controls at runtime is a breeze.
Many forms and dialog boxes have a fixed sized because there's nothing to be gained from allowing users to resize them. Many more, however, are fixed-sized because developers decided that the overhead associated with writing the code that's required to reposition and resize controls when a form's size is changed simply wasn't worth the effort. One of the most endearing new features in Windows Forms promises to eradicate much (no, not all) resizing code.
Consider the form at the top of Figure 2. Without modifying the controls, enlarging this form will lead to the misproportioned result shown in the middle of Figure 2. Windows Forms add a new Anchor property to controls, and Anchor can take on values in any combination of Left, Top, Right, and Bottom, with the default being TopLeft. This means that when the form is resized, each control will maintain a fixed distance from the Top and Left edges of the form. This default results in the same behavior you get in VB6—the controls just stay as they are when the form is resized. Changing the Anchor property to BottomRight for the two buttons, however, will cause them to stick to the bottom right hand side of the form as it's resized, which is typically just what I'd want such buttons to do. The Anchor property gets even more interesting when you set it to opposing sides (for example, TopBottom), because this means that the control has to change its size in order to honor its fixed-distance commitments. The special value "All" basically means TopLeftBottomRight and is ideal for my TreeView control (shown at the bottom of Figure 2), which resizes elegantly without a single line of code.
VB.NET has a far more powerful model for handling events than was available in VB6. For example, take a look at the following event handler, which uses the new keyword Handles:
Public Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Button1.Click MessageBox.Show("Hello") End Sub
The Handles keyword is a powerful new way of associating event handlers with events. The preceding event handler is called Button1_Click, but I could just as easily rename it:
Public Sub HandleClick(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Button1.Click MessageBox.Show("Hello") End Sub
The Handles statement makes the relationship between an event and a handler explicit. In VB6, the relationship is implied by a subroutine's name (VB.NET Beta 1 still supports the idea that if a subroutine is called Button1_Click it must handle the Click event for Button1, but this is expected to be removed in Beta 2, which will rely completely on the Handles statement).
I can supply more than one argument to the Handles statement, as you can see here:
Public Sub HandleClick(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click, Button2.Click Select Case sender Case Button1 MessageBox.Show("Hello 1") Case Button2 MessageBox.Show("Hello 2") Case Else MessageBox.Show("Hello ?") End Select End Sub
This way, I can use one event handler to handle several events. And there's an even more powerful technique that relies on the new AddHandler and RemoveHandler keywords, which can be used to add or remove events to or from a handler dynamically. For example, I could add the following code to my Sub New constructor:
AddHandler Button3.Click, _ New System.EventHandler(AddressOf Me.HandleClick)
Although VB.NET doesn't support control arrays in the same way that VB6 supports them, by using AddHandler and RemoveHandler, you can create not only one-dimensional arrays of controls, but also collections, dictionaries, and two-dimensional arrays of controls—and use them far more flexibly than ever before.
You might have been wondering about the two arguments used for all of the preceding event handlers. All events in VB.NET have only two arguments—sender is a reference to the object that raised the event, and e is an instance of the System.EventArgs class or one of its subclasses. The System.EventArgs class actually isn't very interesting and is used for events such as Click—events that don't really need any arguments. For an event such as a MouseMove event, e is an instance of the MouseEventArgs class:
Public Sub Form1_MouseMove(ByVal sender As Object, _ ByVal e As System.WinForms.MouseEventArgs) _ Handles Form1.MouseMove Label1.Text = e.X & " " & e.Y End Sub
MouseEventArgs provides an X and a Y property, which tell you the mouse position. This pattern is repeated for all events—the EventArgs argument provides an object that has properties appropriate to the event in question.
VB's Menu Designer was never terribly popular (to put it kindly), so it's not surprising that VB.NET has an all-new, WYSIWYG menu designer (see Figure 3) that supports both main menus and context (pop-up) menus.
Somehow, I can't bring myself to write an introductory article on Windows Forms and not mention Visual Inheritance, which allows us to apply the general principles of inheritance to the way we design forms. For example, suppose you needed to write an application that presented different types of information to a user. The user's task is to look at the information and decide whether to accept or reject it. The information could range from a list, to full text, to a grid, to a date, to anything else you might think of. You might decide to create a form for each type of information. Clearly, certain aspects of each form would be identical, while some would always differ. This is where Visual Inheritance can be useful. Consider the form in Figure 4, which contains all of the elements (controls, properties, code in event handlers, and so on) that will be common to all of my forms.
The two forms in Figure 5 inherit from the base form.
To create these two forms, all I had to do was drop on the controls that weren't part of the base form, and then add new code or override inherited code to work with the new controls. In return, I get all of the benefits that you'd expect from inheritance (reusability and consistency, for example), and some of the design challenges too.
As with everything in .NET, Windows Forms leaves me with two initial impressions. The first is that I like what I see—just about everything makes sense, and seems an improvement on what went before—and that's even without getting into any of the snazzier topics such as GDI+. [GDI+ is Microsoft's new Graphics Device Interface, formerly known as GDI2K, which adds alpha blending, anti-aliasing, texturing, advanced typography and imaging, and so on. GDI+ resides in the System.Drawing.DLL assembly, and all GDI+ classes reside in the System.Drawing, System.Text, System.Printing, System.Internal, System.Imaging, System.Drawing2D, and System.Design namespaces.—Ed.] The second is that there's a great deal to learn. I have to admit that my productivity took a step backward when I started using VB.NET, and you should expect yours to take a hit as well.
Since this column started, I've been taking you on a "grand tour" of the major components of VB.NET. It's my opinion that it's essential to appreciate the big picture before drilling down into the details—which have a tendency to morph during the beta stage anyway. Next month, however, completes our grand tour with a fitting finale—Web Forms.
To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at http://www.pinpub.com/
Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.
This article is reproduced from the May 2001 issue of Visual Basic Developer. Copyright 2001, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.