Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents
Bookmark and Share

Part I - Foundations
  Chapter 1 Delphi 7 and Its IDE
  Chapter 2 The Delphi Programming Language
  Chapter 3 The Run-Time Library
  Chapter 4 Core Library classes
  Chapter 5 Visual Controls
  Chapter 6 Building the User Interface
  Chapter 7 Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 The Architecture of Delphi Applications
  Chapter 9 Writing Delphi Components
  Chapter 10 Libraries and Packages
  Chapter 11 Modeling and OOP Programming (with ModelMaker)
  Chapter 12 From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 Delphi's Database Architecture
  Chapter 14 Client/Server with dbExpress
  Chapter 15 Working with ADO
  Chapter 16 Multitier DataSnap Applications
  Chapter 17 Writing Database Components
  Chapter 18 Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 Internet Programming: Sockets and Indy
  Chapter 20 Web Programming with WebBroker and WebSnap
  Chapter 21 Web Programming with IntraWeb
  Chapter 22 Using XML Technologies
  Chapter 23 Web Services and SOAP
  Chapter 24 The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 Delphi for .NET Preview: The Language and the RTL
       
  Appendix A Extra Delphi Tools by the Author
  Appendix B Extra Delphi Tools from Other Sources
  Appendix C Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

Visual Form Inheritance

When you need to build two or more similar forms, possibly with different event handlers, you can use dynamic techniques, hide or create new components at run time, change event handlers, and use if or case statements. Or, you can apply the object-oriented techniques, thanks to visual form inheritance. In short, instead of creating a form based on TForm, you can inherit a form from an existing form, adding new components or altering the properties of the existing components. But what is the advantage of visual form inheritance?

It mostly depends on the kind of application you are building. If the program has multiple forms, some of which are very similar or simply include common elements, then you can place the common components and the common event handlers in the base form and add the specific behavior and components to the subclasses. For example, if you prepare a standard parent form with a toolbar, a logo, default sizing and closing code, and the handlers of some Windows messages, you can then use it as the parent class for each of the application's forms.

You can also use visual form inheritance to customize an application for different clients without duplicating any source code or form definition code—you inherit the specific versions for a client from the standard forms. Remember, the main advantage of visual inheritance is that you can later change the original form and automatically update all the derived forms. This is a well-known advantage of inheritance in object-oriented programming languages. But there is a beneficial side effect: polymorphism. You can add a virtual method in a base form and override it in a subclassed form. Then you can refer to both forms and call this method for each of them.

Note 

Delphi includes another feature that resembles visual form inheritance: frames. In both cases, you can work at design time on two versions of a form/frame. However, in visual form inheritance, you define two different classes (parent and derived), whereas with frames, you work on a class and an instance. Frames are discussed in detail later in this chapter.

Inheriting from a Base Form

The rules governing visual form inheritance are simple, once you have a clear idea of what inheritance is. Basically, a subclass form has the same components as the parent form as well as some new components. You cannot remove a component of the base class, although (if it is a visual control) you can make it invisible. What's important is that you can easily change properties of the components you inherit.

Notice that if you change a property of a component in the inherited form, any modification of the same property in the parent form will have no effect. Changing other properties of the component will affect the inherited versions, as well. You can resynchronize the two property values by using the Revert to Inherited local menu command in the Object Inspector. You can do the same thing by setting the two properties to the same value and recompiling the code. After modifying multiple properties, you can resynchronize them all to the base version by applying the Revert to Inherited command from the component's local menu.

Besides inheriting components, the new form inherits all the methods of the base form, including the event handlers. You can add new handlers in the inherited form and also override existing handlers.

To describe how visual form inheritance works, I've built a simple example called VFI. To build it, first start a new project and add four buttons to its main form. Then select File ® New ® Other and choose the page with the name of the project in the New Items dialog box (see Figure 8.6).

Click To expand
Figure 8.6: The New Items dialog box allows you to create an inherited form.

In the New Items dialog, you can choose the form from which you want to inherit. The new form has the same four buttons. Here is the initial textual description of the new form:

inherited Form2: TForm2
  Caption = 'Form2'
end

And here is its initial class declaration, where you can see that the base class is not the usual TForm but the base class form:

type
  TForm2 = class(TForm1)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

Notice the presence of the inherited keyword in the textual description; also notice that the form has some components, although they are defined in the base class form. If you move the form and add the caption of one of the buttons, the textual description changes accordingly:

inherited Form2: TForm2
  Left = 313
  Top = 202
  Caption = 'Form2'
  inherited Button2: TButton
    Caption = 'Beep...'
  end
end

Only the properties with a different value are listed (and by removing these properties from the textual description of the inherited form, you can reset them to the value of the base form, as I mentioned earlier). I've changed the captions of most of the buttons, as you can see in Figure 8.7.

Click To expand
Figure 8.7: The two forms of the VFI example at run time

Each of the first form's buttons has an OnClick handler with simple code. The first button shows the inherited form by calling its Show method; the second and third buttons call the Beep procedure; and the last button displays a message.

In the inherited form you should first remove the Show button, because the secondary form is already visible. However, you cannot delete a component from an inherited form. An alternative solution is to set the component's Visible property to False—the button will still be there, but it won't be visible (as you can guess from Figure 8.7). The other three buttons will be visible but with different handlers. If you select the OnClick event of a button in the inherited form (by double-clicking it), you'll get an empty method that's slightly different from the default one, because it includes the inherited keyword. This keyword stands for a call to the corresponding event handler of the base form. Notice, though, that this keyword is always added by Delphi, even if the handler is not defined in the parent class (and this is reasonable, because it might be defined later) or if the component is not present in the parent class (which doesn't seem like a great idea to me). It is simple to execute the base form's code and perform some other operations:

procedure TForm2.Button2Click(Sender: TObject);
begin
  inherited;
  ShowMessage ('Hi');
end;

This is not the only choice. Alternatively, you can write a new event handler and not execute the base class's code, as I've done for the VFI example's third button: To accomplish this, simply remove the inherited keyword.

Still another choice includes calling a base-class method after some custom code has been executed, calling it when a condition is met, or calling the handler of a different event of the base class, as I've done for the fourth button:

procedure TForm2.Button4Click(Sender: TObject);
begin
  inherited Button3Click (Sender);
  inherited;
end;

You probably won't inherit from a different handler often, but you must be aware that you can. Of course, you can consider each method of the base form as a method of your form, and call it freely. This example allows you to explore some features of visual form inheritance, but to see its true power you'll need to look at real-world examples more complex than this book has room to explore. Next I want to show you visual form polymorphism.

Note 

Visual form inheritance doesn't work nicely with collections: You cannot extend a collection property of a component in an inherited form. This limitation prevents the practical use of a series of components like Toolbars or ListViews with details. Of course, you can use those components in the parent or inherited form, but you cannot extend the elements they contain, because they are stored in a collection. A solution to this problem is to avoid assigning these collections at design time, and instead use a run-time technique. You'll still use form inheritance, but lose the visual portion of it. If you try to use the Action Manager component, you'll find you cannot even inherit from a form hosting it. Borland disabled this feature, because it would cause you too much trouble.

Polymorphic Forms

If you add an event handler to a form and then change it in an inherited form, there is no way to refer to the two methods using a common variable of the base class, because the event handlers use static binding by default.

Confusing? Here is an example, which is intended for experienced Delphi programmers. Suppose you want to build a bitmap viewer form and a text viewer form in the same program. The two forms have similar elements, a similar toolbar, a similar menu, an OpenDialog component, and different components for viewing the data. So, you decide to build a base-class form containing the common elements and inherit the two forms from it. You can see the three forms at design time in Figure 8.8.

Click To expand
Figure 8.8:  The base-class form and the two inherited forms of the PoliForm example at design time

The main form contains a toolbar panel with a few buttons (real toolbars have a few problems with visual form inheritance), a menu, and an open dialog component. The two inherited forms have only minor differences, but they feature a new component: either an image viewer (TImage) or a text viewer (TMemo). They also modify the settings of the OpenDialog component, to refer to different types of files.

The main form includes some common code. The Close button and the File ® Close command call the Close method of the form. The Help ® About command shows a simple message box. The base form's Load button has only a ShowMessage call displaying an error message. The File ® Load command calls another method:

procedure TViewerForm.Load1Click(Sender: TObject);
begin
  LoadFile;
end;

This method is defined in the TViewerForm class as a virtual abstract method (so that the class of the base form is an abstract class). Because this is an abstract method, you must redefine it (and override it) in the inherited forms. The code for this LoadFile method uses the OpenDialog1 component to ask the user to select an input file and loads it into the image component:

procedure TImageViewerForm.LoadFile;
begin
  if OpenDialog1.Execute then
    Image1.Picture.LoadFromFile (OpenDialog1.Filename);
end;

The other inherited class has similar code, which loads the text into the memo component. The project has one more form, a main form with two buttons, that reloads the files in each of the viewer forms. The main form is the only form created by the project when it starts. The generic viewer form is never created: It is only a generic base class, containing common code and components of the
two subclasses. The forms of the two subclasses are created in the main form's OnCreate event
handler:

procedure TMainForm.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  FormList [1] := TTextViewerForm.Create (Application);
  FormList [2] := TImageViewerForm.Create (Application);
  for I := 1 to 2 do
    FormList[I].Show;
end;

FormList is a polymorphic array of generic TViewerForm objects, declared in the TMainForm class. Note that to make this declaration in the class, you need to add the Viewer unit (but not the specific forms) in the uses clause of the interface portion of the main form. The array of forms is used to load a new file in each viewer form when one of the two buttons is clicked. The handlers of the two buttons' OnClick events use different approaches:

// ReloadButton1Click
for I := 1 to 2 do
  FormList [I].ButtonLoadClick (Self);
   
// ReloadButton2Click
for I := 1 to 2 do
  FormList [I].LoadFile;

The second button calls a virtual method, and it works without any problem. The first button calls an event handler and always reaches the generic TFormView class (displaying the error message of its ButtonLoadClick method). This happens because the method is static, not virtual.

To make this approach work, you can declare the ButtonLoadClick method of the TFormView class as virtual and declare it as overridden in each of the inherited form classes, as you do for any other virtual method:

type
  TViewerForm = class(TForm)
    procedure ButtonLoadClick(Sender: TObject); virtual;
  public
    procedure LoadFile; virtual; abstract;
  end;
   
type
  TImageViewerForm = class(TViewerForm)
    procedure ButtonLoadClick(Sender: TObject); override;
  public
    procedure LoadFile; override;
  end;

This trick really works, although it is never mentioned in the Delphi documentation. This ability to use virtual event handlers is what I mean by visual form polymorphism. In other (more technical) words, you can assign a virtual method to an event property, which will take the address of the method according to the instance available at run time.


 
Previous Section Next Section


 


 


Copyright © 2004-2016 "Delphi Sources". Delphi Programming Guide
     Twitter     Facebook