Training
Certifications
Books
Special Offers
Community




 
Programming Microsoft® Windows® with Microsoft Visual Basic® .NET (Core Reference)
Author Charles Petzold
Pages 1344
Disk N/A
Level All Levels
Published 07/31/2002
ISBN 9780735617995
ISBN-10 0-7356-1799-6
Price(USD) $59.99
To see this book's discounted price, select a reseller below.
 

More Information

About the Book
Table of Contents
Sample Chapter
Index
Companion Content
Related Series
Related Books
About the Author

Support: Book & CD

Rate this book
Barnes Noble Amazon Quantum Books

 


Chapter 2: Hello, Windows Forms continued


Inheriting Forms

So far, you've seen how you can create a form, give it some properties (such as a text string to show in its caption bar and a nondefault background color), and attach some event handlers. Just as you attached a Paint event handler, you can attach handlers for the keyboard, mouse, menus, and so forth.

But I'm afraid the truth is this: it's not usually done like that.

To exploit the full power of everything implemented in the Form class, you can't just create a form. You must become a form. For just as Control begat ScrollableControl, and ScrollableControl begat ContainerControl, and ContainerControl begat Form, then Form can now beget some truly amazing form that only you can create.

You create such a form in your program by defining a class that inherits from Form. Let's take a look.

InheritTheForm.vb

'-----------------------------------------------
' InheritTheForm.vb (c) 2002 by Charles Petzold
'-----------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Module InhertTheForm
    Sub Main()
        Dim frm As New InheritFromForm()
        frm.Text = "Inherit the Form"
        frm.BackColor = Color.White
 
        Application.Run(frm)
    End Sub
End Module
 
Class InheritFromForm
    Inherits Form
End Class

InheritTheForm.vb has both a module (named InheritTheForm) and a class named InheritFromForm. As the name suggests, InheritFromForm inherits from Form:

Class InheritFromForm
    Inherits Form
End Class

The Inherits statement indicates that InheritFromForm is a descendent of Form and inherits every method and property of Form.

As usual, the module (InheritTheForm) has a Main method that is the entry point to the program. However, Main creates a new instance of InheritFromForm rather than Form. Because InheritFromForm derives from Form, of course it also has properties named Text and BackColor, which the program sets next. Just as an object of type Form can be passed to Application.Run, any object of a type derived from Form can also be passed to Application.Run.

The InheritTheForm program creates the form, performs initialization (which in this case just involves setting the Text property), and then passes the form object to Application.Run. A more conventional approach is to perform all form initialization in the class's constructor.

InheritWithConstructor.vb

'------------------------------------------------------ -
' InheritWithConstructor.vb (c) 2002 by Charles Petzold
'------------------------------------------------------ -
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Module InheritWithConstructor
    Sub Main()
        Application.Run(New InheritAndConstruct())
    End Sub
End Module
 
Class InheritAndConstruct
    Inherits Form
 
    Sub New()
        Text = "Inherit with Constructor"
        BackColor = Color.White
    End Sub
End Class

You'll recall that a constructor is a Sub named New, and a default constructor has an empty argument list.

Form has a pedigree starting at Object and encompassing five other classes. When an InheritAndConstruct object is created in Main, first the default constructor for Object is called, then the default constructor for the MarshalByRefObject class, and so forth on through the default constructor for the Form class, and finally the default constructor for the InheritAndConstruct class.

Notice that I don't have to preface the Text and BackColor properties with an object name, an object that I called frm in previous programs in this chapter. These properties don't need anything in front of them because they are properties of the InheritAndConstruct class. They are properties of InheritAndConstruct because this class derives from Control and Form, in which these properties and many others were originally defined.

If I wanted to preface these properties with anything, it would be the keyword Me:

Me.Text = "Inherit with Constructor"
Me.BackColor = Color.White

The Me keyword indicates the current object.

The OnPaint Method

What advantages do you get by inheriting Form rather than just creating an instance of it? Although most of the methods and properties implemented in Form are defined as Public, some essential ones are defined as Protected. These protected methods and properties can be accessed only by a descendent of Form. One such protected property is ResizeRedraw, which I'll be discussing in Chapter 3.

One protected method inherited by Form by way of Control is named OnPaint. You don't want to call this method, however; you want to override it, for if you do, you don't have to install a Paint event handler. The OnPaint method has a single argument, which is an object of type PaintEventArgs. You can use this argument to obtain a Graphics object just as in a Paint event handler.

InheritWithPaint.vb

'-------------------------------------------------
' InheritWithPaint.vb (c) 2002 by Charles Petzold
'-------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Module InheritWithPaint
    Sub Main()
        Application.Run(New InheritAndPaint())
    End Sub
End Module
 
Class InheritAndPaint
    Inherits Form
 
    Sub New()
        Text = "Hello World"
        BackColor = Color.White
    End Sub
 
    Protected Overrides Sub OnPaint(ByVal pea As PaintE ventArgs)
        Dim grfx As Graphics = pea.Graphics
        grfx.DrawString("Hello, Windows Forms!", Font,  Brushes.Black, 0, 0)
    End Sub
End Class

Notice in OnPaint that I don't have to preface the Font with anything. That's because OnPaint is a member of the same class of which Font is a property. Also notice that, unlike the Paint event handler, the OnPaint method doesn't need a first argument that indicates the sender. The form that the OnPaint method applies to is always Me.

Is the Module Necessary?

The InheritWithPaint.vb file contains both a module and a class. The sole method in the module is Main, which has the job of creating an instance of the class and calling Application.Run. This architecture may seem very clean and straightforward, but there's actually a somewhat simpler way to do it.

You can get rid of the module entirely by moving the Main method into the class. At first, you may find this very odd. It may appear as if the program is pulling itself up by its bootstraps. How can the Main method execute at all when an instance of the class hasn't been created yet?

The solution is to define the Main method as Shared. Shared methods exist independently of any objects that are instantiated from the class. For example, Color.Chocolate is a shared property of the Color class that returns an instance of the class. Conceptually, the operating system loads a program into memory and begins execution by making a call to the Main method. It couldn't make this call unless Main were in a module or defined as Shared within a class. (It's actually possible to dispense with the Main method entirely, and specify that the startup object is the class, but I don't feel comfortable with programs that don't have real entry points.)

And here's my final version of a Windows Forms hello-world program.

HelloWorld.vb

'-------------------------------------------
' HelloWorld.vb (c) 2002 by Charles Petzold
'-------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Class HelloWorld
    Inherits Form
 
    Shared Sub Main()
        Application.Run(New HelloWorld())
    End Sub
 
    Sub New()
        Text = "Hello World"
        BackColor = Color.White
    End Sub
 
    Protected Overrides Sub OnPaint(ByVal pea As PaintE ventArgs)
        Dim grfx As Graphics = pea.Graphics
        grfx.DrawString("Hello, Windows Forms!", Font,  Brushes.Black, 0, 0)
    End Sub
End Class

This is the official, certified, programmer-tested and mother-approved way to create a form in Visual Basic using the Windows Forms class library. That's why this is the first program in this book to be called simply HelloWorld. (In the next chapter, I'll show you a better way to specify the background and text colors, however.) And here's what it looks like:

Click to view graphic
Click to view graphic

Of course, there's always some smart aleck in the back row with a raised hand and the impudent question, "Can you now center that text in the window?"

Yes, and in the next chapter, I'll show you three different ways to do it.

Events and "On" Methods

Earlier, when we were working with modules rather than classes derived from Form, we installed Paint event handlers. As you'll recall when you create an instance of Control or any class derived from Control (such as Form), you can install a Paint event handler by defining a method with the same return types and arguments as the PaintEventHandler delegate:

Sub MyPaintHandler(ByVal obj As Object, ByVal pea As Pa intEventArgs)
     ' Painting code
End Sub

You then install this paint handler for a particular object (named frm, for example) using the code

AddHandler frm.Paint, AddressOf MyPaintHandler

In a class derived from Control, however, you don't need to install a Paint event handler (even though you can). You can simply override the protected OnPaint method:

Protected Overrides Sub OnPaint(ByVal pea As PaintEvent Args)
     ' Painting code
End Sub

You'll find that all events defined in Windows Forms are similar. Every event has a corresponding protected method. The method has a name that consists of the word On followed by the event name. For each event that we'll encounter, I'll show a little table like this:

Control Events (selection)

EventMethodDelegateArgument
PaintOnPaintPaintEventHandlerPaintEventArgs

The table indicates the name of the event, the corresponding method, the delegate you use to define an event handler, and the argument to the event handler and the method.

You might assume—as I did originally—that the OnPaint method is basically just a preinstalled Paint event handler. But that's wrong. It's really implemented the other way around: the OnPaint method in Control is actually responsible for calling all the installed Paint handlers.

Let's explore this concept a bit. First, just as the HelloWorld class shown earlier inherited from Form, here's a class named InheritHelloWorld that inherits from HelloWorld.

InheritHelloWorld.vb

'--------------------------------------------------
' InheritHelloWorld.vb (c) 2002 by Charles Petzold
'--------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Class InheritHelloWorld
    Inherits HelloWorld
 
    Shared Shadows Sub Main()
        Application.Run(New InheritHelloWorld())
    End Sub
 
    Sub New()
        Text = "Inherit " & Text
    End Sub
 
    Protected Overrides Sub OnPaint(ByVal pea As PaintE ventArgs)
        Dim grfx As Graphics = pea.Graphics
        grfx.DrawString("Hello from InheritHelloWorld!" , _
                        Font, Brushes.Black, 0, 100)
    End Sub
End Class

Let me take care of some housekeeping issues first. When I created the InheritHelloWorld project in Visual Basic .NET, I created a new Visual Basic file named InheritHelloWorld.vb, as usual, but I also needed to include HelloWorld.vb in the project. I did that by using the Add Existing Item option and specifying Link File in the drop-down menu next to the Open button. That avoids making a second copy of the HelloWorld.vb file.

Notice that the Main method includes the Shadows keyword, indicating that it is supposed to replace any Main methods that may be in any parent classes (such as HelloWorld). You also have to tell Visual Basic .NET which Main you want to be the entry point to the program. You do this with the project's Property Pages dialog box. In the General Common Properties, specify the Startup Object as InheritHelloWorld.

If you're running the command-line Visual Basic compiler, specify both source code files in the command line and use the compiler switch

/Main:InheritHelloWorld

to indicate which class has the Main method you want as the entry point to the program.

As I mentioned earlier, when you create a new object based on a derived class using a default constructor, all the ancestral default constructors are called starting with Object. Toward the end of this process, the HelloWorld constructor gets called and responds by setting the Text property of the form to "Hello World." Finally, the InheritHelloWorld constructor is executed and sets the Text property like so:

Text = "Inherit " + Text

That the caption bar of this program reads "Inherit Hello World" demonstrates that this sequence of events is correct.

The OnPaint method in InheritHelloWorld overrides the OnPaint method in HelloWorld. When InheritHelloWorld runs, it displays "Hello from InheritHelloWorld!" I've positioned the text at the coordinate position (0, 100) so you can see that the OnPaint method in HelloWorld isn't also executed. The OnPaint method in HelloWorld is overridden.

Now let's take a look at a program that does something a little different. This program doesn't define a class that inherits from HelloWorld; this one instantiates the HelloWorld class.

InstantiateHelloWorld.vb

'------------------------------------------------------
' InstantiateHelloWorld.vb (c) 2002 by Charles Petzold
'------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms
 
Module InstantiateHelloWorld
    Sub Main()
        Dim frm As New HelloWorld()
        frm.Text = "Instantiate " & frm.Text
        AddHandler frm.Paint, AddressOf MyPaintHandler
        Application.Run(frm)
    End Sub
 
    Sub MyPaintHandler(ByVal obj As Object, ByVal pea A s PaintEventArgs)
        Dim frm As Form = DirectCast(obj, Form)
        Dim grfx As Graphics = pea.Graphics
        grfx.DrawString("Hello from InstantiateHelloWor ld!", _
                        frm.Font, Brushes.Black, 0, 100 )
    End Sub
End Module

Take a close look at this code. First, notice that InstantiateHelloWorld is a module; it can't inherit from HelloWorld or Form or anything else. Instead, it creates a new instance of the HelloWorld class and saves it in the variable frm, just as early programs in this chapter created instances of the Form class:

Dim frm As New HelloWorld()

During the creation of the HelloWorld object, the HelloWorld constructor is called, which gives the form a Text property of "Hello World." The next statement prepends the word Instantiate to the Text property. The program then installs a Paint event handler for the form.

But what appears in InstantiateHelloWorld's client area is not the text "Hello from InstantiateHelloWorld!" but instead the text "Hello, Windows Forms!" which is what the OnPaint method in HelloWorld displays. What happened?

The OnPaint method in Control is responsible for calling the installed Paint event handlers. Because the HelloWorld class overrides OnPaint, that job doesn't get done. That's why the .NET documentation recommends that when you override one of the protected methods beginning with the On prefix, you should call the On method in the base class like so:

MyBase.OnPaint(pea)

Try inserting this statement at the top of HelloWorld's OnPaint method and rebuilding InstantiateHelloWorld. Now the program works as you probably wanted it to. InstantiateHelloWorld displays its text string ("Hello from InstantiateHelloWorld!") and also the "Hello, Windows Forms!" text string.

The sequence of events in the revised version is this:

  • Whenever the client area becomes invalid, the OnPaint method is called. This is the OnPaint method in the HelloWorld class, which overrides any OnPaint method in ancestral classes.
  • The OnPaint method in HelloWorld calls the OnPaint method in its base class. (Remember, I'm talking about a revised version of HelloWorld that includes the MyBase.OnPaint call.) That would normally be the OnPaint method implemented in Form, but it's likely Form doesn't override the OnPaint method and what really gets called is the OnPaint method back in Control.
  • The OnPaint method in Control calls all the installed Paint event handlers. The only one in this process is the MyPaintHandler method in InstantiateHelloWorld. That method displays some text at position (0, 100).
  • When all the installed Paint event handlers have been called, the OnPaint method in Control returns back to the OnPaint method in HelloWorld.
  • The OnPaint method in HelloWorld displays some text at position (0, 0).

The Windows Forms documentation recommends that whenever you override an On method you call the base class On method. However, in most cases, you need to do this only if you're defining a class that you'll also be instantiating, and that the instantiated classes are also installing event handlers for On methods you've overridden. This scenario doesn't happen very often. Still, at times, you need to call the base class in overrides of On methods regardless. As we'll see in the next chapter, one of these is OnResize.


Previous   |  Table of Contents   |  Next



Last Updated: August 7, 2002
Top of Page