delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback



Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi








Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (4)


Create and Manage dynamic Forms at Runtime using Class ReferencesFormat this article printer-friendly!Bookmark function is only available for registered users!
Product:
Delphi 3.x (or higher)
Category:
GUI
Skill Level:
Scoring:
Last Update:
12/06/2001
Search Keys:
delphi delphi3000 article borland vcl code-snippet Forms,Class,Reference,FormClass,Dynamic
Times Scored:
6
Visits:
15352
Uploader: Marc Hoffmann
Company:
Reference: N/A
 
Question/Problem/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.


Sincerely,

M. Hoffmann





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
TWindowManager/TObjectBroker
    Raymond Barlow (Dec 13 2001 12:43AM)

Hi Mark,

I too like this style of creating and managing forms. I have, in the past, written an object broker which basically does the same as you describe here, except that the bas level class is TObject. This way, you can create and manage any type of object at run time. I then added another layer to this that did form specific management, The internals were based around dynamic arrays, reusing elements not used.

When my application would first start up, it would register all the form classes with the form broker. Each time a form was created (via the form broker), it would be given an Id, usually the primary key for that record. This way, you need not pass form class references around, only an id which referred to that form class reference. Almost every did not refer to any other form units in the program. Most functions where available through the form broker.

If anyone is interested in this code, drop us a line, and I can send you the source code too!

Happy Delphi programming......

Cheers,
Raymond Barlow
Respond

Sorry!
    Marc Hoffmann (Dec 7 2001 5:42PM)

Excuse this bad format - I'll update this article next monday with a fixed style!
Respond

RE: Sorry!
Laurent PIERRE (Dec 7 2001 11:43PM)

Hello Marc,

I like your programming object approach for this dynamic form managment. I will be very curious to see how you have resolved the button toolbars/menu items (enabled/visible properties )interaction for each dynamic form with your main form ? Do you have made another class or have you add some new procedure in your actual TWindowsManagaer ?

Thank you again.
I will work on your sample...
Respond

THNX!!
Anders Jensen (Dec 12 2001 6:26PM)

in the past year i have been wondering how to manage dynamic forms, since I`m no wizard in writing code, I have not been able to do it the way I wanted to, but, now I can!! thanx to this article, at last my project can move on, and I have learned exactly what I needed. Thank you a lot.
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


   


  Community Ad of
Hans Gulö
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)