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

Base Forms and Interfaces

You have seen that when you need two similar forms in an application, you can use visual form inheritance to inherit one from the other or both of them from a common ancestor. The advantage of visual form inheritance is that you can use it to inherit the visual definition: the DFM. However, this is not always required.

At times, you might want several forms to exhibit a common behavior or respond to the same commands without having any shared component or user interface elements. Using visual form inheritance with a base form that has no extra components makes little sense to me. I rather prefer to define my own custom form class, inherited from TForm, and then manually edit the form class declarations to inherit from this custom base form class instead of the standard one. If you only need to define shared methods or override TForm virtual methods in a consistent way, defining custom form classes can be a good idea.

Using a Base Form Class

A simple demonstration of this technique is available in the FormIntf demo; it also showcases the use of interfaces for forms. In a new unit called SaveStatusForm, I've defined the following form class (with no related DFM file—instead of using the New Form command, create a new unit and type the code in it):

type
  TSaveStatusForm = class (TForm)
  protected
    procedure DoCreate; override;
    procedure DoDestroy; override;
  end;

The two overridden methods are called at the same time as the event handler so you can attach extra code (allowing the event handler to be defined as usual). Inside the two methods, you load or save the form position in an INI file of the application, in a section marked with the form caption. Here is the code for the two methods:

procedure TSaveStatusForm.DoCreate;
var
  Ini: TIniFile;
begin
  inherited;
  Ini := TIniFile.Create (ExtractFileName (Application.ExeName));
  Left := Ini.ReadInteger(Caption, 'Left', Left);
  Top := Ini.ReadInteger(Caption, 'Top', Top);
  Width := Ini.ReadInteger(Caption, 'Width', Width);
  Height := Ini.ReadInteger(Caption, 'Height', Height);
  Ini.Free;
end;
   
procedure TSaveStatusForm.DoDestroy;
var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create (ExtractFileName (Application.ExeName));
  Ini.WriteInteger(Caption, 'Left', Left);
  Ini.WriteInteger(Caption, 'Top', Top);
  Ini.WriteInteger(Caption, 'Width', Width);
  Ini.WriteInteger(Caption, 'Height', Height);
  Ini.Free;
  inherited;
end;

Again, this is a simple common behavior for your forms, but you can define a complex class here. To use this as a base class for the forms you create, let Delphi create the forms as usual (with no inheritance) and then update the form declaration to something like the following:

type
  TFormBitmap = class(TSaveStatusForm)
    Image1: TImage;
    OpenPictureDialog1: TOpenPictureDialog;
    ...

Simple as it seems, this technique is very powerful, because all you need to do is change the definition of your application's forms to refer to this base class. If even this step is too tedious, because you might want to change this base class in your program at some point, you can use an extra trick: "interposer" classes.

An Extra Trick: Interposer Classes

In contrast with Delphi VCL components, which must have unique names, Delphi classes in general must be unique only within their unit. Thus you can have two different units defining a class with the same name. This technique looks weird at first sight, but can be useful. For example, Borland uses this approach to provide compatibility between VCL and VisualCLX classes. Both have a TForm class, one defined in the Forms unit and the other in the QForms unit.

Note 

This technique is much older than CLX/VCL. For example, the service and control panel applet units define their own TApplication object, which is not related to the TApplication used by VCL visual GUI applications and defined in the Forms unit.

I've seen a technique called "interposer classes" mentioned in an old issue of The Delphi Magazine. It suggested replacing standard Delphi class names with your own versions that have the same class name. This way, you can use Delphi's form designer and refer to Delphi standard components at design time, but use your own classes at run time.

The idea is simple. In the SaveStatusForm unit, you can define the new form class as follows:

type
  TForm = class (Forms.TForm)
  protected
    procedure DoCreate; override;
    procedure DoDestroy; override;
  end;

This class is called TForm, and it inherits from TForm of the Forms unit (this last reference is compulsory to avoid a kind of recursive definition). In the rest of the program, you don't need to change the class definition for your form, but simply add the unit defining the interposer class (the SaveStatusForm unit in this case) in the uses statement after the unit defining the Delphi class. The order of the unit in the uses statement is important, and is the reason some people criticize this technique, because it is difficult to know what is going on. I have to agree: I find interposer classes handy at times (more for components than for forms), but their use makes programs less readable and at times harder to debug.

Using Interfaces

Another technique, which is slightly more complex but even more powerful than the definition of a common base form class, is to create forms that implement specific interfaces. You can have forms that implement one or more of these interfaces, query each form for the interfaces it implements, and call the supported methods.

As an example (available in the same FormIntf program I began discussing in the last section), I've defined a simple interface for loading and storing:

type
  IFormOperations = interface
    ['{DACFDB76-0703-4A40-A951-10D140B4A2A0}']
    procedure Load;
    procedure Save;
  end;

Each form can optionally implement this interface, as in the following TFormBitmap class:

type
  TFormBitmap = class(TForm, IFormOperations)
    Image1: TImage;
    OpenPictureDialog1: TOpenPictureDialog;
    SavePictureDialog1: TSavePictureDialog;
  public
    procedure Load;
    procedure Save;
  end;

The example code includes the Load and Save methods, which use the standard dialog boxes to load or save the image. (In the example's code, the form also inherits from the TSaveStatusForm class.)

When an application has one or more forms implementing interfaces, you can apply a given interface method to all the forms supporting it, with code like this (extracted from the main form of the FormIntf example):

procedure TFormMain.btnLoadClick(Sender: TObject);
var
  i: Integer;
  iFormOp: IFormOperations;
begin
  for i := 0 to Screen.FormCount - 1 do
    if Supports (Screen.Forms [i], IFormOperations, iFormOp) then
      iFormOp.Load;
end;

Consider a business application in which you can synchronize all the forms to the data of a specific company or a specific business event. Also consider that, unlike inheritance, you can have several forms that implement multiple interfaces, with unlimited combinations. This is why using such an architecture can improve a complex Delphi application a great deal, making it much more flexible and easier to adapt to implementation changes.


 
Previous Section Next Section


 


 


Copyright © 2004-2020 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
   Facebook     Twitter