Forms Inheritance

One of the .NET Framework's powerful features is that it is fully object-oriented, which, among other things, allows class inheritance. Inheritance promotes reuse of objects, potentially saving the developer (and his employer) from a great deal of work. Reusing tested and working code reduces development time and cuts down on the number of bugs.

With inheritance, a class can derive (inherit) from a base class. The derived class is a specialized case of the base class. For example, if you were modeling the animal kingdom, your Dog class might derive from Mammal, indicating that a Dog is a specialized type of Mammal.

All forms in Windows Forms are members of the Form class. As such, they derive from the System.Windows.Forms class. Any form you create can also be the base class for other forms derived from it. A derived form will have all the properties, controls, and code contained by the base form, plus any additional properties, controls, and code of its own.

This organization provides a powerful way to impose a consistent look and feel across multiple forms. Any changes made in a base form will automatically propagate to all its derived forms when the application is recompiled. In addition, the derived form can override properties and methods. This will be demonstrated in the examples below.

Inheritance also allows controls with common functionality to be placed in a base form and be available to any derived form. By overriding methods in the derived form, the same base control can perform vastly different functions, yet retain a set of common functionality.

For a complete discussion of inheritance and polymorphism, see Jesse Liberty's Programming C# or Programming VB. NET (O'Reilly).

 

5.4.1 Programmatic Inheritance

To examine many of the issues relating to forms inheritance without the clutter introduced by Visual Studio .NET, you will create an example using a text editor. This example creates two forms: a base form, called BaseForm, and an inherited form, called InheritedForm, which will derive from BaseForm.

To create the base form, open your favorite text editor and enter the code shown in Example 5-1 for C# or Example 5-2 for VB.NET. Save the source code file with the name indicated in the caption for each example. You will want to save and compile the source code for both BaseForm and InheritedForm in the same directory.

Example 5-1. BaseForm source code in C# (BaseForm.cs)

using System; using System.Drawing; using System.Windows.Forms; namespace WinFormsInheritance { public class BaseForm : System.Windows.Forms.Form { private Button btnClose; private Button btnApp; protected Label lbl; public BaseForm( ) { Text = "Inheritance Base Form"; BackColor = Color.LightGreen; btnClose = new Button( ); btnClose.Location = new Point(25,100); btnClose.Size = new Size(100,25); btnClose.Text = "&Close"; btnClose.Click += new System.EventHandler(btnClose_Click); btnApp = new Button( ); btnApp.Location = new Point(200,100); btnApp.Size = new Size(150,25); btnApp.Text = "&Base Application"; btnApp.Click += new System.EventHandler(btnApp_Click); lbl = new Label( ); lbl.Location = new Point(25,25); lbl.Size = new Size(100,25); lbl.Text = "This label on BaseForm"; Controls.AddRange(new Control[ ]{lbl, btnClose, btnApp}); } static void Main( ) { Application.Run(new BaseForm( )); } private void btnClose_Click(object sender, EventArgs e) { Application.Exit( ); } private void btnApp_Click(object sender, EventArgs e) { MessageBox.Show("This is the Base application."); SomeMethod( ); } protected virtual void SomeMethod( ) { MessageBox.Show("This is SomeMethod called from BaseForm."); } } }

Example 5-2. BaseForm source code in VB.NET (BaseForm.vb)

imports System imports System.Drawing imports System.Windows.Forms namespace WinFormsInheritance public class BaseForm : inherits System.Windows.Forms.Form private WithEvents btnClose as Button private WithEvents btnApp as Button protected lbl as Label public Sub New( ) Text = "Inheritance Base Form" BackColor = Color.LightGreen btnClose = new Button( ) btnClose.Location = new Point(25,100) btnClose.Size = new Size(100,25) btnClose.Text = "&Close" btnApp = new Button( ) btnApp.Location = new Point(200,100) btnApp.Size = new Size(150,25) btnApp.Text = "&Base Application" lbl = new Label( ) lbl.Location = new Point(25,25) lbl.Size = new Size(100,25) lbl.Text = "This label on BaseForm" Controls.AddRange(new Control( ){lbl, btnClose, btnApp}) end sub Public Shared Sub Main( ) Application.Run(new BaseForm( )) end sub private sub btnClose_Click(ByVal sender As Object, _ ByVal e As EventArgs) _ Handles btnClose.Click Application.Exit( ) end sub private sub btnApp_Click(ByVal sender As Object, _ ByVal e As EventArgs) _ Handles btnApp.Click MessageBox.Show("This is the Base application.") SomeMethod( ) end sub protected Overridable Sub SomeMethod( ) MessageBox.Show("This is SomeMethod called from BaseForm.") end sub end class end namespace

To run either of the forms in Example 5-1 or Example 5-2, the source code must first be compiled to an EXE file. Enter the appropriate line at a command prompt:

csc /out:BaseForm.exe /target:winexe BaseForm.cs

vbc /out:BaseForm.exe /r:system.dll,system.drawing.dll, system.windows.forms.dll /target:winexe BaseForm.vb

Chapter 2 gives a complete discussion of the command-line compiler.

Remember to use a command prompt window with the proper path already set by clicking on Start, and then Programs Microsoft Visual Studio .NET 2003 Visual Studio .NET Tools Visual Studio .NET 2003 Command Prompt.

Any command entered on a command line must be entered without pressing Enter until the command is to be executed. The command will wrap to new lines if it is long enough, but you should not enter any carriage returns.

Notice that the command-line compilation for VB.NET requires explicit referencing of all assemblies, while the C# version requires only "non-default" assemblies.

When BaseForm.exe is run, the result is shown in Figure 5-3. Although you can't see it in the printed book, the background color of the form is light green.

Figure 5-3. BaseForm

BaseForm is a fairly simple and straightforward Windows Form. The first three lines reference namespaces used by the form:

using System; using System.Drawing; using System.Windows.Forms;

imports System imports System.Drawing imports System.Windows.Forms

System.Drawing is necessary because it contains the Color structure, used in the code to specify the BackColor property.

The namespace of the form itself was set to WinFormsInheritance. The class name for the form is BaseForm, and, like all Windows Forms, it inherits from System.Windows.Forms.Form.

The next three lines declare objects on the formtwo buttons and a label:

private Button btnClose; private Button btnApp; protected Label lbl;

private WithEvents btnClose as Button private WithEvents btnApp as Button protected lbl as Label

The two buttons are declared as private, while the label is declared as protected. Objects declared as private cannot be referenced in code in any derived class, while objects declared as protected can be referenced in classes derived from the current class. The upshot is that neither button can be modified by InheritedForm, while the Label control is accessible in InheritedForm.

The constructor is the next section in the code. In C#, it is the method:

public BaseForm( ) {

and in VB.NET it is:

public Sub New( )

This is where the Text and BackColor properties of the form are set and the controls are instantiated and fully specified. Notice that event handlers are defined for the Click event for the two buttons in C#, while in VB.NET, this definition is achieved in the control declarations (using the WithEvents keyword) in conjunction with the event-handler method declarations (using the Handles keyword):

public BaseForm( ) { Text = "Inheritance Base Form"; BackColor = Color.LightGreen; btnClose = new Button( ); btnClose.Location = new Point(25,100); btnClose.Size = new Size(100,25); btnClose.Text = "&Close"; btnClose.Click += new System.EventHandler(btnClose_Click); btnApp = new Button( ); btnApp.Location = new Point(200,100); btnApp.Size = new Size(150,25); btnApp.Text = "&Base Application"; btnApp.Click += new System.EventHandler(btnApp_Click); lbl = new Label( ); lbl.Location = new Point(25,25); lbl.Size = new Size(100,25); lbl.Text = "This label on BaseForm"; Controls.AddRange(new Control[ ]{lbl, btnClose, btnApp}); }

public Sub New( ) Text = "Inheritance Base Form" BackColor = Color.LightGreen btnClose = new Button( ) btnClose.Location = new Point(25,100) btnClose.Size = new Size(100,25) btnClose.Text = "&Close" btnApp = new Button( ) btnApp.Location = new Point(200,100) btnApp.Size = new Size(150,25) btnApp.Text = "&Base Application" lbl = new Label( ) lbl.Location = new Point(25,25) lbl.Size = new Size(100,25) lbl.Text = "This label on BaseForm" Controls.AddRange(new Control( ){lbl, btnClose, btnApp}) end sub

The event handler for the btnApp button Click event first puts up a message box ("This is the Base application.") and then calls the method SomeMethod. Notice that SomeMethod is marked virtual in C# and overridable in VB.NET, indicating that the developer expects this method to be overridden in derived classes. SomeMethod puts up a message box with the words "This is SomeMethod called from BaseForm."

To create an inherited form that derives from BaseForm, enter the code from Example 5-3 for C# or Example 5-4 for VB.NET in your text editor and save the files as named in the example captions.

Example 5-3. InheritedForm source code in C# (InheritedForm.cs)

using System; using System.Drawing; using System.Windows.Forms; namespace WinFormsInheritance { public class InheritedForm : WinFormsInheritance.BaseForm { private Button btn; public InheritedForm( ) { Text = "Inherited Form"; btn = new Button( ); btn.Location = new Point(25,150); btn.Size = new Size(125,25); btn.Text = "C&lose on Inherited"; btn.Click += new System.EventHandler(btn_Click); Controls.Add(btn); lbl.Text = "Now from InheritedForm"; BackColor = Color.LightBlue; } static void Main( ) { Application.Run(new InheritedForm( )); } private void btn_Click(object sender, EventArgs e) { Application.Exit( ); } protected override void SomeMethod( ) { MessageBox.Show("This is the overridden SomeMethod called " + "from InheritedForm."); } } }

Example 5-4. InheritedForm source code in VB.NET (InheritedForm.vb)

imports System imports System.Drawing imports System.Windows.Forms namespace WinFormsInheritance public class InheritedForm : inherits WinFormsInheritance.BaseForm private WithEvents btn as Button public sub New( ) Text = "Inherited Form" btn = new Button( ) btn.Location = new Point(25,150) btn.Size = new Size(125,25) btn.Text = "C&lose on Inherited" Controls.Add(btn) lbl.Text = "Now from InheritedForm" BackColor = Color.LightBlue end sub Public Shadows Shared Sub Main( ) Application.Run(new InheritedForm( )) end sub private sub btn_Click(ByVal sender As Object, _ ByVal e As EventArgs) _ Handles btn.Click Application.Exit( ) end sub protected Overrides Sub SomeMethod( ) MessageBox.Show("This is the overridden SomeMethod called " + _ "from InheritedForm.") end sub end class end namespace

InheritedForm is also a straightforward Windows Form. The first thing of note is that it does not inherit directly from System.Windows.Forms.Form, but rather from WinFormsInheritance.BaseForm.

Inside the constructor, the Text property of the form is set to "Inherited Form." Also inside the constructor, the Text property of the Label control on the base form is changed and the BackColor property of the base form is set to a different color. This is done with the lines:

lbl.Text = "Now from InheritedForm"; BackColor = Color.LightBlue;

lbl.Text = "Now from InheritedForm" BackColor = Color.LightBlue

The Label control was declared protected. If it had been private, then InheritedForm would have caused a compiler error when the Label control was referenced; derived classes cannot access private members in the base class.

The Label control could also have been declared public, but good object-oriented practice dictates using the minimum accessibility necessary.

Recall that the btnApp_Click event handler method in the BaseForm, from Example 5-1 and Example 5-2, calls the method SomeMethod. In BaseForm, SomeMethod was declared as virtual (overridable). In InheritedForm, another version of SomeMethod is provided that does, in fact, override the base version. This is indicated by the override keyword in C# and the Overrides keyword in VB.NET.

In the VB.NET version, the keyword Shadows is used in the declaration of the Main method. This keyword, which prevents a compiler warning, indicates that an identically named element is in a base class, and the shadowed element, i.e., the method in the derived class, is unavailable to that derived class.

The command line used to compile InheritedForm.cs is as follows:

csc /out:InheritedForm.exe /r:BaseForm.exe /target:winexe InheritedForm.cs

and to compile InheritedForm.vb, use:

vbc /out:InheritedForm.exe /r:system.dll,system.drawing.dll,system.windows.forms.dll, BaseForm.exe /target:winexe InheritedForm.vb

Remember, even though these command lines wrap in the book and probably also on your screen, they are each a single line and must be entered without pressing Enter until you are ready to execute the line.

Note that both versions of these compiler commands now reference the previously compiled BaseForm.exe. This is necessary so that InheritedForm can inherit from BaseForm.

When InheritedForm is executed and the BaseApplication button is clicked, the first message is "This is the Base application." The second is "This is the overridden SomeMethod called from InheritedForm," as shown in Figure 5-4.

Because of the way event handling works, the base class event handler has not been replaced, but rather the new stuff is added on top of it. This often surprises people because it's not how things usually work in derivation; if you implement something in the derived class, it typically replaces the base class implementation unless you explicitly call down to the base class. But events are different.

Figure 5-4. InheritedForm with MessageBox

5.4.2 Visual Inheritance

You will now use Visual Studio .NET to create a base form and a form derived from it. Visual Studio .NET provides a means of inheriting forms known as Visual Inheritance. Visual Studio .NET recognizes that a form is inherited and displays the inherited form differently from the base form, indicating which controls are from the base form and which from the derived form. An Inheritance Picker dialog box makes this even easier to accomplish. Visual Inheritance makes deriving forms a very simple task, although there are a few gotchas that we will point out along the way.

Open Visual Studio .NET and create a new Windows Application project named Inheritance in the language of your choice. Add a new form. You'll add several Label controls, some with private accessibility and some with protected accessibility. You will also add a button that, when clicked, will cause the current time to display in one of the Label controls.

When coding a form directly, outside of Visual Studio .NET, the accessibility level of a control is specified with an accessibility keyword (public, private, protected, internal, or protected internal in C#; public, private, protected, friend, or protected friend in VB.NET) as part of the control declaration. For example, the control declarations from Example 5-1 and Example 5-2 looked like:

private Button btnClose; private Button btnApp; protected Label lbl;

private WithEvents btnClose as Button private WithEvents btnApp as Button protected lbl as Label

In Visual Studio .NET, it is very easy to set the accessibility level of a control. Select the control in the Design window. In the Property window, set the Modifiers property to the desired accessibility level. You can also set the accessibility in code directly.

C# and VB.NET diverge here on default-access modifiers for controls placed in Visual Studio .NET. C# defaults to private, while VB.NET defaults to Friend, the equivalent to Internal in C#.

The default name of the form will probably be Form1. Rename the form by selecting it in the Design window and changing the (Name) property in the Properties window from Form1 to BaseForm.

The design view of the form should look like that shown in Figure 5-5. (Note that the project shown in these examples was actually named vbInheritance, to distinguish it from my C# version, csInheritance.)

Figure 5-5. BaseForm in Visual Studio .NET

Table 5-4 lists all relevant properties of the controls on BaseForm.

Table 5-4. Inheritance application BaseForm control properties

Control type

Property

Value

Form

(Name)

BaseForm

 

Size

300,300

 

Text

Visual Form Inheritance

 

BackColor

Control (the default color)

Label

(Name)

lblHeading

 

Font

Microsoft SansSerif Bold 16 pt.

 

Location

24,16

 

Modifiers

Protected

 

Size

248,23

 

Text

Base Form

 

TextAlign

MiddleCenter

Button

(Name)

btn

 

Location

96,72

 

Modifiers

Protected

 

Size

75,23

 

Text

Time

Label

(Name)

lblOutput

 

Font

Microsoft SansSerif Regular 8 pt.

 

Location

64,128

 

Modifiers

Protected

 

Size

136,23

 

Text

blank

 

TextAlign

MiddleCenter

Label

(Name)

label3

 

Font

Microsoft SansSerif Regular 8 pt.

 

Location

80,200

 

Modifiers

Private

 

Size

100,23

 

Text

Base Form

 

TextAlign

MiddleCenter

Label

(Name)

label2

 

Font

Microsoft SansSerif Regular 8 pt.

 

Location

40,232

 

Modifiers

Private

 

Size

216,23

 

Text

Created in Visual Studio .NET

 

TextAlign

MiddleCenter

If you run the form at this point, there should be no problems if you are working in C#. If you are working in VB.NET, on the other hand, the first gotcha rears its ugly head when the IDE reports build errors. Click on No to stop. The Task List window will open with the following description of an error:

'Sub Main' was not found in 'Inheritance.Form1'.

Wait a minute, there is no Form1. You renamed the form to BaseForm. What is going on here? The answer is that the IDE is trying to make life easier for VB.NET developers. In doing so, it hides the Main method.

In C#, a static void Main( ) method is required and is automatically inserted into the code by Visual Studio .NET. In VB.NET, a sub Main is explicitly required when compiling source code with the command-line compiler, but is neither required nor provided by Visual Studio .NET. Instead, the VB.NET compiler working with Visual Studio .NET automatically creates a sub Main and inserts it into the compiled code. Even though you renamed the form legitimately through Visual Studio .NET, once this autogenerated sub Main gets the original form name, it does not let go of it until you explicitly tell it to do so.

To verify that a Main method is in fact created for your VB.NET program developed in Visual Studio .NET, you can use ILDASM, a tool provided as part of the .NET SDK, to look into the EXE file created by the compiler. Open a .NET command line by clicking the Start button, and then Programs Microsoft Visual Studio .NET 2003 Visual Studio .NET Tools Visual Studio .NET 2003 Command Prompt. Enter the following command line:

ildasm e:projectsInheritanceininheritance.exe

substituting the actual path to your executable.

Once inside ILDASM, you can drill down through the contents of the file until you see Main::void( ) as one of the nodes under BaseForm. Double-clicking on any node will display the IL code that comprises the object. Although much of the IL code is cryptic, you will see the name of the Form class in the Main::void( ) method.

To change the name of the startup form, right-click on the project name in the Solution Explorer and select Properties. This will open a Property Page for the project. (Note that it is distinct from the Properties window that is normally used for object properties.) One of the drop-down fields will be labeled Startup Object. Select BaseForm from this list, and then run the form. It should run with no errors (although it does not do much at this point).

As an alternative to changing the startup object in the Property Page, you could also double-click on the error message in the Task List window and select BaseForm from the Startup Object dialog box that presents itself.

Add functionality to the button by double-clicking on the button in the Design window. When the event handler code skeleton for the default Click event comes up in the code editor, add the highlighted lines of code from Example 5-5 in C# or from Example 5-6 in VB.NET.

Example 5-5. BaseForm btn_Click in C#

protected virtual void btn_Click(object sender, System.EventArgs e) { lblOutput.Text = "The time is: " + DateTime.Now.ToString("T"); }

Example 5-6. Example 5-6. BaseForm btn_Click in VB.NET

Private Sub btn_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btn.Click lblOutput.Text = "The time is: " + DateTime.Now.ToString("T") End Sub

Running the application and clicking on the button will now display the current time as the Text property of the lblOutput control.

Now add a derived form to the project, using the Inheritance Picker. Right-click on the project name in the Solution Explorer. Select Add, then Add Inherited Form. A familiar Add New Item dialog box will appear. Change the name of the form from Form2.vb to InheritedForm.vb. The dialog box will look like Figure 5-6.

Figure 5-6. Visual inheritance Add New Item dialog box

Clicking on the Open button will present the Inheritance Picker dialog box, shown in Figure 5-7. Select the form to inherit from (in this example, there is only one other form in the project), and then click OK.

Figure 5-7. Inheritance Picker

A new form, named InheritedForm, derived from BaseForm, will be opened in a new Design window. The source file for the new form, called InheritanceForm.vb, will be visible in the Solution Explorer. The IDE will look like Figure 5-8. Although the original form object was renamed to BaseForm, it's source file is still named Form1.vb, and appears as such in the Solution Explorer.

Figure 5-8. InheritedForm in design mode

All controls inherited from the original base class, i.e., BaseForm, are indicated with a small glyph () in their upper-lefthand corner.

The top three controls on the form, lblHeading, lblOutput, and btn, are protected, while label2 and label3 are private. This means that in the derived form the latter two controls will be visible, but unable to be changed in the IDE or otherwise accessed in code.

You can now see the effects of marking controls in a derived form as protected or private. Click on any of the protected controls. Resizing handles will appear, and you can move or resize the controls. The control properties will be visible and accessible in the Properties window. In contrast, click on one of the private controls. It will have no resizing handles, and cannot be moved or resized. In addition, it's properties are visible in the Properties window, but grayed out and inaccessible.

Change some of the properties and functionality of the inherited form. Click on the form in the Design window, and change its BackColor property to something snazzy, like Yellow.

You want to change the Text property of some of the inherited controls. To do so, enter some code in the constructor. If you are working with C#, the constructor will be the method in the code window with the same name as the Form class, i.e., InheritedForm. Add the following two lines of code in the constructor:

btn.Text = "Date"; lblHeading.Text = "Inherited Form";

If you are working with VB.NET, first expand the region in the code window labeled Windows Form Designer generated code, and then find the Sub called New( ). Add the following two lines of code:

btn.Text = "Date" lblHeading.Text = "Inherited Form"

Double-click on the button to bring up the code skeleton for the Click event handler. Enter the highlighted code shown in Example 5-7 for C# and in Example 5-8 for VB.NET.

Example 5-7. InheritedForm btn_Click event handler in C#

protected void btn_Click(object sender, System.EventArgs e) { lblOutput.Text = "Today is: " + DateTime.Now.ToString("D"); }

Example 5-8. InheritedForm btn_Click event handler in VB.NET

Private Sub btn_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btn.Click lblOutput.Text = "Today is: " + DateTime.Now.ToString("D") End Sub

If you run the program, you will not get the InheritedForm, but rather the BaseForm. There can be only a single entry point into the program, i.e., a single Main method, unless you tell the compiler otherwise and point it in the right direction. You have not yet done this.

If you are handcoding and compiling a project with multiple forms or other classes, and there is more than one Main method, use a /main compiler switch to tell the compiler which class to use as the entry point. (See Chapter 2 for a complete description of command-line compilation.)

Working in Visual Studio .NET, this is accomplished by right-clicking on the project in the Solution Explorer, selecting Properties to get the Property Page for the project, and changing the Startup Object to the desired form.

If you are working in C#, you probably will not see any forms listed other than the first form entered in the project. This is because Visual Studio .NET includes only classes with a Main method in the list of possible startup objects, and it automatically includes a Main method only in the first form. Copy the Main method from the first form to any additional forms, remembering to change the name of the form in the Application.Run statement to the current form, and it will now show up in the list of potential Startup Objects.

Running the program, you will now see the inherited form. Clicking on the button, now labeled Date, you will get something similar to that shown in Figure 5-9. Not only is the form now a garish yellow (at least on the screen, if not in the book) and the button is relabeled and repurposed, but the heading label, lblHeading, now says Inherited Form, while the two labels at the bottom still say Base Form and Created in Visual Studio .NET.

Figure 5-9. InheritedForm

If you want to see either form without having to go through the step of changing the startup object, you need to go through several steps. Add a new form to the project by right-clicking on the project name in the Solution Explorer, selecting Add, then Add Windows Form..., and name it Startup.

Drag two buttons onto the form and change their Text properties to BaseForm and InheritedForm, respectively. Double-click on each button to enter code in their Click event handlers, and enter the code highlighted in Example 5-9 for C# or Example 5-10 for VB.NET.

Example 5-9. Startup form button event handlers in C#

private void button1_Click(object sender, System.EventArgs e) { Form frm1 = new BaseForm( ); frm1.Show( ); } private void button2_Click(object sender, System.EventArgs e) { Form frm2 = new InheritedForm( ); frm2.Show( ); }

Example 5-10. Startup form button event handlers in VB.NET

Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click dim frm1 as new BaseForm( ) frm1.Show( ) End Sub Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click dim frm2 as new InheritedForm( ) frm2.Show( ) End Sub

Remember to make the Startup form the actual Startup Object. If working in C#, add a Main method manually, and then, in either language, right-click on the project in the Solution Explorer, go to the Properties Page for the project, and set the Startup Object accordingly.

Категории