2011. március 30., szerda
Create and Manage dynamic Forms at Runtime using Class References
Problem/Question/Abstract:
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 < Count 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.
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése