Forms Delphi

Title: Create and Manage dynamic Forms at Runtime using Class References
Question: How to dynamicaly create and manage different Forms at runtime in a global manner?
Answer:
If you need to create dynamic forms at runtime and you want to manage them in
a global manner, you may have the problem that you don't know how to administrate
different form classes. For this case, Delphi comes with special class types of all
common objects. But before I go into details, let me create a scenario in which this
article may helps you.
"I'll create an application for my customer to let him administrate serveral kinds of data
in a local database. Each data category (such as employees, articles, ...) have to been
implemented in a specified form with individual edit fields and tools. I don't like to
create a MDI bases application (for some reasons) but the customers should have the
possibilty to create more than one form for each category (e.g. opens up to 10 forms
with customer informations and 3 forms with article informations). He should refer to
each form after a while, so all forms have to been non-modular the customer can hide
or minimize each form. In normal MDI application, Delphi helps you to manage the MDI childs form via
the 'ActiveMDIChild' property for example, but in non MDI applications you had to
manage all child forms by yourself."
To find a workable solution we had to abstract the layer in which we could manage
several kinds of forms. Each Delphi form inherites from TCustomForm so our first
solution is to create a method who we pass a form reference to memorize - but how to
keep such references? By the way, it's also possible to create a form manually and then
pass the handle direct to the management component, but we'll create a method which
automatically creates each kind of form. At the end of this article we've created a VCL
component called TWindowManager which makes all of the discussed stuff, but now -
let's start:
function TWindowManager.CreateForm(const Form: TFormClass;
Name: string; Show: Boolean = False): TCustomForm;
begin
if not Form.InheritsFrom(TCustomForm) then
raise Exception.Create('Invalid FormClass - must be a descendant
of TCustomForm!');
Result := TCustomForm(Form.Create(Application));
if Name '' then
Result.Name := Name;
// insert code here, to store the reference
if Show then
Result.Show;
end;
Okay, but how to use it? First, we've created a normal Delphi application and added a new form
called DynForm1 for example. Delphi automatically creates the following entry in the pas unit:
type
TDynForm1 = class(TForm)
...
end;
For the next step we had to refer to the new unit by included the corresponding unit
name to the uses clause. To dynamically create the new form at runtime, you can call
the method in a way like:
procedure TMainForm.ButtonDyn1Click(Sender: TObject);
begin
// create a new (dynamic) form.
WindowManager.CreateForm(TDynForm1, True);
end;
Don't marvel about the name WindowManager or TWindowManager in the source
examples, I've pasted it direct from the component source I've explained earlier.
Do you notice that we have passed the formclass to the method instead of the name or
anythink else? It's possible, because the parameter type of the method is TFormClass
which is implemented as TFormClass = class of TForm in Delphi's Forms unit.
Now we need a solution to store the form reference:
type
{ TWindowItem }
PWindowItem = ^TWindowItem;
TWindowItem = packed record
Form: Pointer;
end;
Note:
It's also possible to use a TStringList for example and create items which holds
the form handles (or references direct) but it's not a good solutions if you want to
search for already existing form (names). Since Version 3 (I'm not sure exactly) Delphi
comes with a special container class which gives you some more specific descendants from
the TList class. You can use the TObjectList class, derive from it and overwritte
the maintenance methods. In this article I use a normal record to store all informations - it's less
code to write and you can easily add improved custom informations to store.
The sourcecode of the TWindowManager comes from a Delphi3 implementation I've wrote - if I've some
spare time, I'll update it to the newer technology!
Our WindowManager also published a method to directly add already existing form references,
so you don't need to create your forms using the CreateForm method:
function TWindowManager.Add(const Form: TCustomForm): Boolean;
var
WindowItem: PWindowItem;
begin
Result := True;
try
New(WindowItem);
WindowItem^.Form := Form;
FWindowList.Add(WindowItem);
except // wrap up
Result := True;
end; // try/except
end;
FWindowList is declared as FWindowList: TList to hold a list of reference
records. Followed you'll see to complete sourcode of the TWindowManager - try to understand
the individual methods - they are simple. The main trick is the use off class references I've
mentioned earlier.
The main component
unit WindowMng;
interface
uses
Classes, Forms, SysUtils, Windows;
type
{ TWinNotifyEvent }
TWinNotifyEvent = procedure(Sender: TObject; Form: TCustomForm) of object;
{ TWindowItem }
// I used a packed record to be more flexible for futher improvements
// which may need to store additional informations.
PWindowItem = ^TWindowItem;
TWindowItem = packed record
Form: Pointer;
end;
{ TWindowManager }
TWindowManager = class(TComponent)
private
{ Private declarations }
FAutoNotification: Boolean;
FLastIndex: Integer;
FWindowList: TList;
FOnFormAdded: TWinNotifyEvent;
FOnFormHandled: TNotifyEvent;
FOnFormRemoved: TWinNotifyEvent;
protected
{ Protected declarations }
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
function GetFormByIndex(Index: Integer): TCustomForm; virtual;
function GetWindowItemByIndex(Index: Integer): PWindowItem; virtual;
function GetWindowItemByForm(const Form: TCustomForm): PWindowItem; virtual;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Add(const Form: TCustomForm): Boolean; overload;
function Count: Integer;
function CreateForm(const Form: TFormClass; Name: string; Show: Boolean = False): TCustomForm; overload;
function CreateForm(const Form: TFormClass; Show: Boolean = False): TCustomForm; overload;
function Exists(const Form: TCustomForm): Boolean;
function Remove(const Form: TCustomForm): Boolean;
function Restore(const Index: Integer): Boolean; overload;
function Restore(const Form: TCustomForm): Boolean; overload;
property Forms[Index: Integer]: TCustomForm read GetFormByIndex; default;
published
{ Published declarations }
property AutoNotification: Boolean read FAutoNotification write FAutoNotification;
property OnFormAdded: TWinNotifyEvent read FOnFormAdded write FOnFormAdded;
property OnFormHandled: TNotifyEvent read FOnFormHandled write FOnFormHandled;
property OnFormRemoved: TWinNotifyEvent read FOnFormRemoved write FOnFormRemoved;
end;
procedure Register;
implementation
// -----------------------------------------------------------------------------
procedure Register;
begin
RegisterComponents('Freeware', [TWindowManager]);
end;
// -----------------------------------------------------------------------------
{ TWindowManager }
constructor TWindowManager.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FAutoNotification := False;
FLastIndex := -1;
FWindowList := TList.Create;
end;
destructor TWindowManager.Destroy;
begin
FWindowList.Free;
inherited Destroy;
end;
procedure TWindowManager.Notification(AComponent: TComponent;
Operation: TOperation);
begin
if (FAutoNotification) and (AComponent nil) and (Operation = opRemove)
and (AComponent is TCustomForm) and (Exists(TCustomForm(AComponent))) then
Remove(TCustomForm(AComponent));
inherited Notification(AComponent, Operation);
end;
function TWindowManager.Add(const Form: TCustomForm): Boolean;
var
WindowItem: PWindowItem;
begin
Result := False;
if not Exists(Form) then
try
New(WindowItem);
WindowItem^.Form := Form;
FWindowList.Add(WindowItem);
if FAutoNotification then
Form.FreeNotification(Self);
Result := True;
if assigned(FOnFormAdded) then
FOnFormAdded(Self, Form);
if assigned(FOnFormHandled) then
FOnFormHandled(Self);
except // wrap up
end; // try/except
end;
function TWindowManager.Count: Integer;
begin
Result := FWindowList.Count;
end;
function TWindowManager.CreateForm(const Form: TFormClass; Name: string; Show: Boolean = False): TCustomForm;
begin
if not Form.InheritsFrom(TCustomForm) then
raise Exception.Create('Invalid FormClass - must be a descendant of TCustomForm!');
Result := TCustomForm(Form.Create(Application));
if Name '' then
Result.Name := Name;
Add(Result);
if Show then
Result.Show;
end;
function TWindowManager.CreateForm(const Form: TFormClass; Show: Boolean = False): TCustomForm;
begin
Result := CreateForm(Form, '', Show);
end;
function TWindowManager.Exists(const Form: TCustomForm): Boolean;
begin
Result := GetWindowItemByForm(Form) nil;
end;
function TWindowManager.GetFormByIndex(Index: Integer): TCustomForm;
var
WindowItem: PWindowItem;
begin
Result := nil;
WindowItem := GetWindowItemByIndex(Index);
if WindowItem nil then
Result := TCustomForm(WindowItem^.Form);
end;
function TWindowManager.GetWindowItemByIndex(Index: Integer): PWindowItem;
begin
Result := nil;
if Index then
Result := PWindowItem(FWindowList[Index]);
end;
function TWindowManager.GetWindowItemByForm(const Form: TCustomForm): PWindowItem;
var
iIndex: Integer;
begin
Result := nil;
FLastIndex := -1;
for iIndex := 0 to FWindowList.Count - 1 do
if GetWindowItemByIndex(iIndex)^.Form = Form then
begin
FLastIndex := iIndex;
Result := GetWindowItemByIndex(FLastIndex);
Break;
end;
end;
function TWindowManager.Remove(const Form: TCustomForm): Boolean;
var
WindowItem: PWindowItem;
begin
Result := False;
WindowItem := GetWindowItemByForm(Form);
if WindowItem nil then
try
FWindowList.Delete(FLastIndex);
Dispose(WindowItem);
Result := True;
if assigned(FOnFormRemoved) then
FOnFormRemoved(Self, Form);
if assigned(FOnFormHandled) then
FOnFormHandled(Self);
except // wrap up
end; // try/except
end;
function TWindowManager.Restore(const Form: TCustomForm): Boolean;
begin
Result := False;
if (Form nil) and (Exists(Form)) then
try
if IsIconic(Form.Handle) then
Form.WindowState := wsNormal;
Form.SetFocus;
Result := True;
except // wrap up
end; // try/except
end;
function TWindowManager.Restore(const Index: Integer): Boolean;
begin
Result := Restore(GetFormByIndex(Index));
end;
end.
To show you the in more detail how to work with this component, followed you'll find a
demo application with two additional forms. You don't need to install the component to
a package, I'll create it at runtime:
The project file
program WMDemo;
uses
Forms,
MainFrm in 'MainFrm.pas' {MainForm},
WindowMng in 'WindowMng.pas',
DynFrm1 in 'DynFrm1.pas' {DynForm1},
DynFrm2 in 'DynFrm2.pas' {DynForm2};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
The MainForm file
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, WindowMng;
type
TMainForm = class(TForm)
ButtonDyn1: TButton;
GroupBoxForms: TGroupBox;
ListBoxForms: TListBox;
ButtonHelloWorld: TButton;
ButtonDyn2: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ButtonDyn1Click(Sender: TObject);
procedure ListBoxFormsDblClick(Sender: TObject);
procedure ButtonHelloWorldClick(Sender: TObject);
procedure ButtonDyn2Click(Sender: TObject);
private
{ Private declarations }
WindowManager: TWindowManager;
procedure RedrawFormList(Sender: TObject);
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
uses
DynFrm1, DynFrm2;
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
// create WindowManager
WindowManager := TWindowManager.Create(Self);
// enable 'AutoNotification'. If this feature is turned on,
// WindowManager will receive a notification if a form was closed
// by the user, so it can fire events to recorgnize this.
// We use the 'OnFormHandled' event to redraw out ListBox.
WindowManager.AutoNotification := True;
// link event handler to update out ListBox.
WindowManager.OnFormHandled := RedrawFormList;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// destroy WindowManager
WindowManager.Free;
end;
procedure TMainForm.RedrawFormList(Sender: TObject);
var
i: Integer;
begin
// get all available forms and display them.
// we also stores the object reference to enable the 'restore' function
// if the user double-clicked on an item.
ListBoxForms.Clear;
for i := 0 to WindowManager.Count - 1 do
ListBoxForms.AddItem(WindowManager.Forms[i].Name, WindowManager.Forms[i]);
end;
procedure TMainForm.ButtonDyn1Click(Sender: TObject);
begin
// create a new (dynamic) form.
WindowManager.CreateForm(TDynForm1, True);
end;
procedure TMainForm.ButtonDyn2Click(Sender: TObject);
begin
// create a new (dynamic) form.
WindowManager.CreateForm(TDynForm2, True);
end;
procedure TMainForm.ListBoxFormsDblClick(Sender: TObject);
var
ClickForm: TCustomForm;
begin
// extract the 'clicked' form.
with ListBoxForms do
ClickForm := TCustomForm(Items.Objects[ItemIndex]);
// restore the form to the top order.
// we used the WindowManager method 'Restore' to be sure
// that the form will be restored also if it was iconized
// before.
WindowManager.Restore(ClickForm);
end;
procedure TMainForm.ButtonHelloWorldClick(Sender: TObject);
begin
// check, if any registered forms exists.
if WindowManager.Count = 0 then
begin
ShowMessage('No dynamic Forms exists - please create one!');
Exit;
end;
// check, if the first available form is 'DynForm1'.
// if true, call the HelloWorld method.
if WindowManager.Forms[0] is TDynForm1 then
TDynForm1(WindowManager.Forms[0]).HelloWorld
else
ShowMessage('The first Form is not a "Dynamic Form I"!');
end;
end.
The MainForm resource file
object MainForm: TMainForm
Left = 290
Top = 255
BorderStyle = bsSingle
Caption = 'MainForm'
ClientHeight = 229
ClientWidth = 510
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
OnCreate = FormCreate
OnDestroy = FormDestroy
DesignSize = (
510
229)
PixelsPerInch = 96
TextHeight = 13
object ButtonDyn1: TButton
Left = 16
Top = 16
Width = 121
Height = 25
Caption = 'Create Dynamic Form I'
TabOrder = 0
OnClick = ButtonDyn1Click
end
object GroupBoxForms: TGroupBox
Left = 16
Top = 56
Width = 481
Height = 169
Anchors = [akLeft, akTop, akRight, akBottom]
Caption = 'Available Forms (Double-Click to restore)'
TabOrder = 1
object ListBoxForms: TListBox
Left = 2
Top = 15
Width = 477
Height = 152
Align = alClient
BorderStyle = bsNone
ItemHeight = 13
ParentColor = True
TabOrder = 0
OnDblClick = ListBoxFormsDblClick
end
end
object ButtonHelloWorld: TButton
Left = 344
Top = 16
Width = 153
Height = 25
Caption = 'Fire ''HelloWorld'' on DynForm1'
TabOrder = 2
OnClick = ButtonHelloWorldClick
end
object ButtonDyn2: TButton
Left = 144
Top = 16
Width = 121
Height = 25
Caption = 'Create Dynamic Form II'
TabOrder = 3
OnClick = ButtonDyn2Click
end
end
The DynForm1 file
unit DynFrm1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TDynForm1 = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
procedure HelloWorld;
end;
var
DynForm1: TDynForm1;
implementation
{$R *.dfm}
procedure TDynForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// be sure that our form will be freed.
Action := caFree;
end;
procedure TDynForm1.HelloWorld;
begin
ShowMessage('HelloWorld method was fired!');
end;
end.
The DynForm2 file
unit DynFrm2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TDynForm2 = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DynForm2: TDynForm2;
implementation
{$R *.dfm}
procedure TDynForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// be sure that our form will be freed.
Action := caFree;
end;
end.
Hope this article helps you to understand how dynamic forms can be created and
managed.
Sincerely,
M. Hoffmann