|
Chapter 2: Hello, Windows Forms continued
Inheriting FormsSo 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 has both a module (named InheritTheForm) and a class named InheritFromForm. As the name suggests, InheritFromForm inherits from Form:
Class InheritFromForm 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
'------------------------------------------------------ - 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" The Me keyword indicates the current object.
The OnPaint MethodWhat 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
'------------------------------------------------- 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
'------------------------------------------- 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: 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" MethodsEarlier, 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) 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) 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)
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 assumeas I did originallythat 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
'-------------------------------------------------- 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
'------------------------------------------------------ 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:
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.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||