Author: Marc Hoffmann
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:
1
2 function TWindowManager.CreateForm(const Form: TFormClass;
3 Name: string; Show: Boolean = False): TCustomForm;
4 begin
5 if not Form.InheritsFrom(TCustomForm) then
6 raise Exception.Create('Invalid FormClass - must be a descendant
7 of TCustomForm!'
8 Result := TCustomForm(Form.Create(Application));
9 if Name <> '' then
10 Result.Name := Name;
11 // insert code here, to store the reference
12 if Show then
13 Result.Show;
14 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:
15 type
16 TDynForm1 = class(TForm)
17 ...
18 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:
19
20 procedure TMainForm.ButtonDyn1Click(Sender: TObject);
21 begin
22 // create a new (dynamic) form.
23 WindowManager.CreateForm(TDynForm1, True);
24 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:
25 type
26 { TWindowItem }
27
28 PWindowItem = ^TWindowItem;
29 TWindowItem = packed record
30 Form: Pointer;
31 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:
32
33 function TWindowManager.Add(const Form: TCustomForm): Boolean;
34 var
35 WindowItem: PWindowItem;
36 begin
37 Result := True;
38 try
39 New(WindowItem);
40 WindowItem^.Form := Form;
41 FWindowList.Add(WindowItem);
42 except // wrap up
43 Result := True;
44 end; // try/except
45 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
46 unit WindowMng;
47
48 interface
49
50 uses
51 Classes, Forms, SysUtils, Windows;
52
53 type
54 { TWinNotifyEvent }
55
56 TWinNotifyEvent = procedure(Sender: TObject; Form: TCustomForm) of object;
57
58 { TWindowItem }
59
60 // I used a packed record to be more flexible for futher improvements
61 // which may need to store additional informations.
62
63 PWindowItem = ^TWindowItem;
64 TWindowItem = packed record
65 Form: Pointer;
66 end;
67
68 { TWindowManager }
69
70 TWindowManager = class(TComponent)
71 private
72 { Private declarations }
73 FAutoNotification: Boolean;
74 FLastIndex: Integer;
75 FWindowList: TList;
76 FOnFormAdded: TWinNotifyEvent;
77 FOnFormHandled: TNotifyEvent;
78 FOnFormRemoved: TWinNotifyEvent;
79 protected
80 { Protected declarations }
81 procedure Notification(AComponent: TComponent; Operation: TOperation); override;
82 function GetFormByIndex(Index: Integer): TCustomForm; virtual;
83 function GetWindowItemByIndex(Index: Integer): PWindowItem; virtual;
84 function GetWindowItemByForm(const Form: TCustomForm): PWindowItem; virtual;
85 public
86 { Public declarations }
87 constructor Create(AOwner: TComponent); override;
88 destructor Destroy; override;
89 function Add(const Form: TCustomForm): Boolean; overload;
90 function Count: Integer;
91 function CreateForm(const Form: TFormClass; Name: string; Show: Boolean =
92 False):
93 TCustomForm; overload;
94 function CreateForm(const Form: TFormClass; Show: Boolean = False): TCustomForm;
95 overload;
96 function Exists(const Form: TCustomForm): Boolean;
97 function Remove(const Form: TCustomForm): Boolean;
98 function Restore(const Index: Integer): Boolean; overload;
99 function Restore(const Form: TCustomForm): Boolean; overload;
100 property Forms[Index: Integer]: TCustomForm read GetFormByIndex; default;
101 published
102 { Published declarations }
103 property AutoNotification: Boolean read FAutoNotification write
104 FAutoNotification;
105 property OnFormAdded: TWinNotifyEvent read FOnFormAdded write FOnFormAdded;
106 property OnFormHandled: TNotifyEvent read FOnFormHandled write FOnFormHandled;
107 property OnFormRemoved: TWinNotifyEvent read FOnFormRemoved write
108 FOnFormRemoved;
109 end;
110
111 procedure register;
112
113 implementation
114
115 // -----------------------------------------------------------------------------
116
117 procedure register;
118 begin
119 RegisterComponents('Freeware', [TWindowManager]);
120 end;
121
122 // -----------------------------------------------------------------------------
123
124 { TWindowManager }
125
126 constructor TWindowManager.Create(AOwner: TComponent);
127 begin
128 inherited Create(AOwner);
129 FAutoNotification := False;
130 FLastIndex := -1;
131 FWindowList := TList.Create;
132 end;
133
134 destructor TWindowManager.Destroy;
135 begin
136 FWindowList.Free;
137 inherited Destroy;
138 end;
139
140 procedure TWindowManager.Notification(AComponent: TComponent;
141 Operation: TOperation);
142 begin
143 if (FAutoNotification) and (AComponent <> nil) and (Operation = opRemove)
144 and (AComponent is TCustomForm) and (Exists(TCustomForm(AComponent))) then
145 Remove(TCustomForm(AComponent));
146 inherited Notification(AComponent, Operation);
147 end;
148
149 function TWindowManager.Add(const Form: TCustomForm): Boolean;
150 var
151 WindowItem: PWindowItem;
152 begin
153 Result := False;
154 if not Exists(Form) then
155 try
156 New(WindowItem);
157 WindowItem^.Form := Form;
158 FWindowList.Add(WindowItem);
159 if FAutoNotification then
160 Form.FreeNotification(Self);
161 Result := True;
162 if assigned(FOnFormAdded) then
163 FOnFormAdded(Self, Form);
164 if assigned(FOnFormHandled) then
165 FOnFormHandled(Self);
166 except // wrap up
167 end; // try/except
168 end;
169
170 function TWindowManager.Count: Integer;
171 begin
172 Result := FWindowList.Count;
173 end;
174
175 function TWindowManager.CreateForm(const Form: TFormClass; Name: string; Show:
176 Boolean
177 = False): TCustomForm;
178 begin
179 if not Form.InheritsFrom(TCustomForm) then
180 raise
181 Exception.Create('Invalid FormClass - must be a descendant of TCustomForm!');
182 Result := TCustomForm(Form.Create(Application));
183 if Name <> '' then
184 Result.Name := Name;
185 Add(Result);
186 if Show then
187 Result.Show;
188 end;
189
190 function TWindowManager.CreateForm(const Form: TFormClass; Show: Boolean = False):
191 TCustomForm;
192 begin
193 Result := CreateForm(Form, '', Show);
194 end;
195
196 function TWindowManager.Exists(const Form: TCustomForm): Boolean;
197 begin
198 Result := GetWindowItemByForm(Form) <> nil;
199 end;
200
201 function TWindowManager.GetFormByIndex(Index: Integer): TCustomForm;
202 var
203 WindowItem: PWindowItem;
204 begin
205 Result := nil;
206 WindowItem := GetWindowItemByIndex(Index);
207 if WindowItem <> nil then
208 Result := TCustomForm(WindowItem^.Form);
209 end;
210
211 function TWindowManager.GetWindowItemByIndex(Index: Integer): PWindowItem;
212 begin
213 Result := nil;
214 if Index < Count then
215 Result := PWindowItem(FWindowList[Index]);
216 end;
217
218 function TWindowManager.GetWindowItemByForm(const Form: TCustomForm): PWindowItem;
219 var
220 iIndex: Integer;
221 begin
222 Result := nil;
223 FLastIndex := -1;
224 for iIndex := 0 to FWindowList.Count - 1 do
225 if GetWindowItemByIndex(iIndex)^.Form = Form then
226 begin
227 FLastIndex := iIndex;
228 Result := GetWindowItemByIndex(FLastIndex);
229 Break;
230 end;
231 end;
232
233 function TWindowManager.Remove(const Form: TCustomForm): Boolean;
234 var
235 WindowItem: PWindowItem;
236 begin
237 Result := False;
238 WindowItem := GetWindowItemByForm(Form);
239 if WindowItem <> nil then
240 try
241 FWindowList.Delete(FLastIndex);
242 Dispose(WindowItem);
243 Result := True;
244 if assigned(FOnFormRemoved) then
245 FOnFormRemoved(Self, Form);
246 if assigned(FOnFormHandled) then
247 FOnFormHandled(Self);
248 except // wrap up
249 end; // try/except
250 end;
251
252 function TWindowManager.Restore(const Form: TCustomForm): Boolean;
253 begin
254 Result := False;
255 if (Form <> nil) and (Exists(Form)) then
256 try
257 if IsIconic(Form.Handle) then
258 Form.WindowState := wsNormal;
259 Form.SetFocus;
260 Result := True;
261 except // wrap up
262 end; // try/except
263 end;
264
265 function TWindowManager.Restore(const Index: Integer): Boolean;
266 begin
267 Result := Restore(GetFormByIndex(Index));
268 end;
269
270 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
271 program WMDemo;
272
273 uses
274 Forms,
275 MainFrm in 'MainFrm.pas' {MainForm},
276 WindowMng in 'WindowMng.pas',
277 DynFrm1 in 'DynFrm1.pas' {DynForm1},
278 DynFrm2 in 'DynFrm2.pas' {DynForm2};
279
280 {$R *.res}
281
282 begin
283 Application.Initialize;
284 Application.CreateForm(TMainForm, MainForm);
285 Application.Run;
286 end.
287
288 //The MainForm file
289
290 unit MainFrm;
291
292 interface
293
294 uses
295 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
296 Dialogs, StdCtrls, WindowMng;
297
298 type
299 TMainForm = class(TForm)
300 ButtonDyn1: TButton;
301 GroupBoxForms: TGroupBox;
302 ListBoxForms: TListBox;
303 ButtonHelloWorld: TButton;
304 ButtonDyn2: TButton;
305 procedure FormCreate(Sender: TObject);
306 procedure FormDestroy(Sender: TObject);
307 procedure ButtonDyn1Click(Sender: TObject);
308 procedure ListBoxFormsDblClick(Sender: TObject);
309 procedure ButtonHelloWorldClick(Sender: TObject);
310 procedure ButtonDyn2Click(Sender: TObject);
311 private
312 { Private declarations }
313 WindowManager: TWindowManager;
314 procedure RedrawFormList(Sender: TObject);
315 public
316 { Public declarations }
317 end;
318
319 var
320 MainForm: TMainForm;
321
322 implementation
323
324 uses
325 DynFrm1, DynFrm2;
326
327 {$R *.dfm}
328
329 procedure TMainForm.FormCreate(Sender: TObject);
330 begin
331 // create WindowManager
332 WindowManager := TWindowManager.Create(Self);
333
334 // enable 'AutoNotification'. If this feature is turned on,
335 // WindowManager will receive a notification if a form was closed
336 // by the user, so it can fire events to recorgnize this.
337 // We use the 'OnFormHandled' event to redraw out ListBox.
338 WindowManager.AutoNotification := True;
339
340 // link event handler to update out ListBox.
341 WindowManager.OnFormHandled := RedrawFormList;
342 end;
343
344 procedure TMainForm.FormDestroy(Sender: TObject);
345 begin
346 // destroy WindowManager
347 WindowManager.Free;
348 end;
349
350 procedure TMainForm.RedrawFormList(Sender: TObject);
351 var
352 i: Integer;
353 begin
354 // get all available forms and display them.
355 // we also stores the object reference to enable the 'restore' function
356 // if the user double-clicked on an item.
357 ListBoxForms.Clear;
358 for i := 0 to WindowManager.Count - 1 do
359 ListBoxForms.AddItem(WindowManager.Forms[i].Name, WindowManager.Forms[i]);
360 end;
361
362 procedure TMainForm.ButtonDyn1Click(Sender: TObject);
363 begin
364 // create a new (dynamic) form.
365 WindowManager.CreateForm(TDynForm1, True);
366 end;
367
368 procedure TMainForm.ButtonDyn2Click(Sender: TObject);
369 begin
370 // create a new (dynamic) form.
371 WindowManager.CreateForm(TDynForm2, True);
372 end;
373
374 procedure TMainForm.ListBoxFormsDblClick(Sender: TObject);
375 var
376 ClickForm: TCustomForm;
377 begin
378 // extract the 'clicked' form.
379 with ListBoxForms do
380 ClickForm := TCustomForm(Items.Objects[ItemIndex]);
381
382 // restore the form to the top order.
383 // we used the WindowManager method 'Restore' to be sure
384 // that the form will be restored also if it was iconized
385 // before.
386 WindowManager.Restore(ClickForm);
387 end;
388
389 procedure TMainForm.ButtonHelloWorldClick(Sender: TObject);
390 begin
391 // check, if any registered forms exists.
392 if WindowManager.Count = 0 then
393 begin
394 ShowMessage('No dynamic Forms exists - please create one!');
395 Exit;
396 end;
397
398 // check, if the first available form is 'DynForm1'.
399 // if true, call the HelloWorld method.
400 if WindowManager.Forms[0] is TDynForm1 then
401 TDynForm1(WindowManager.Forms[0]).HelloWorld
402 else
403 ShowMessage('The first Form is not a "Dynamic Form I"!');
404 end;
405
406 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
407 //The DynForm1 file
408
409 unit DynFrm1;
410
411 interface
412
413 uses
414 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
415 Dialogs;
416
417 type
418 TDynForm1 = class(TForm)
419 procedure FormClose(Sender: TObject; var Action: TCloseAction);
420 private
421 { Private declarations }
422 public
423 { Public declarations }
424 procedure HelloWorld;
425 end;
426
427 var
428 DynForm1: TDynForm1;
429
430 implementation
431
432 {$R *.dfm}
433
434 procedure TDynForm1.FormClose(Sender: TObject; var Action: TCloseAction);
435 begin
436 // be sure that our form will be freed.
437 Action := caFree;
438 end;
439
440 procedure TDynForm1.HelloWorld;
441 begin
442 ShowMessage('HelloWorld method was fired!');
443 end;
444
445 end.
446
447 The DynForm2 file
448
449 unit DynFrm2;
450
451 interface
452
453 uses
454 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
455 Dialogs;
456
457 type
458 TDynForm2 = class(TForm)
459 procedure FormClose(Sender: TObject; var Action: TCloseAction);
460 private
461 { Private declarations }
462 public
463 { Public declarations }
464 end;
465
466 var
467 DynForm2: TDynForm2;
468
469 implementation
470
471 {$R *.dfm}
472
473 procedure TDynForm2.FormClose(Sender: TObject; var Action: TCloseAction);
474 begin
475 // be sure that our form will be freed.
476 Action := caFree;
477 end;
478
479 end.
Hope this article helps you to understand how dynamic forms can be created and managed.
|