Create and Manage dynamic Forms at Runtime using Class References (Views: 2147)
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. |