Link to home
Start Free TrialLog in
Avatar of bernani
bernaniFlag for Belgium

asked on

Dynamically creating forms ....

Hi,

Your opinion woud be greatly appreciated.

I've got this procedure - in a separate unit - wich dynamically creates a form (named Form2 or Form3 ... FormN) when I call it in the main form:

Implementation of the procedure:

Procedure LoadTheForm(Name: String; T:TFormClass; Panel:TPanel);
var
  f : TFormClass;
begin
RegisterClass(T);
  try
    f := TFormClass(FindClass(Name));
    with f.create(Application) do
    begin
    Parent:= Panel;
    Align:= alClient;
    Show;
    end;
  except
    raise;
  end;
end;

I click on a button in MainForm (filled with a PanelControl with TabSheets. One of those TabSheet contains a panel (Panel1) which is intended to be the parent (container) for my Form2. Calls are done in this way:

LoadTheForm('TForm2',TForm2, Panel1);
LoadTheForm('TForm3',TForm3, Panel11);
....

Form2 contains only:

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
action:= caFree;
form2:= nil;
end;

My question: all seems correct and is perfectly working. I'd like to know if this could be achieved in another way - in a better way ?  

I need to use the statement in the procedure:

with f.create(Application) do

and can't use the statement

with f.create(self) do

Could you tell me if this last statment is possible ?

Thanks.

Avatar of 2266180
2266180
Flag of United States of America image

yes it is, but you will have to include the procedure in the TForm1 declaration because "self" identifies the current class, and the procedure is not part of any class at the moment (that is why you received an error). Something like this:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure LoadTheForm(Name: String; T:TFormClass; Panel:TPanel);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses unit2;

Procedure TForm1.LoadTheForm(Name: String; T:TFormClass; Panel:TPanel);
var
  f : TFormClass;
begin
RegisterClass(T);
  try
    f := TFormClass(FindClass(Name));
    with f.create(self) do
    begin
    Parent:= Panel;
    Align:= alClient;
    Show;
    end;
  except
    raise;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  LoadTheForm('TForm2',TForm2,panel1);
end;

end.
Avatar of Kyle Foster
does the form have to be a child form.

Just use f.Create(nil) in your proc  this will create the form but you will have to make sure it is free'd yourself, because it doesn't have a parent.
Avatar of bernani

ASKER

Hi Ciuly ,

Thanks for the explanation. I know that I could'nt use the self reference and it the reason why I used in my implementation the assignement  with
f.create(Application) do
instead of
with f.create(self) do

I want to avoid to insert the procedure in the mainform or in any form of my prog. I've moved this procedure to a generic unit containing only procedures and functions, reusable in any other prog without modifications.

The goal I try to achieve is to have a genric function or procedure reusable anywhere in any application by only inserting the unit name in a use clause of any form.

============================================================================

Hi kfoster11,

does the form have to be a child form ---> the form can be or a main form or a child form, but it's already dynamically created at runtime.

Just use f.Create(nil) in your proc  this will create the form : --> I've test you suggestion and it effectively seems to work correctly.

but you will have to make sure it is free'd yourself : --> Isn't sufficient to free the dynamiccaly created form created by the procedure like done in the code above:
Form2 contains only:

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
action:= caFree;  // --> to free the dynamically form created when calling LoadTheForm
form2:= nil;        // --> assign nil to the reference after closing this form
end;



because it doesn't have a parent --> assigning the form to a panel like this:
 
// with f.create(Application) do
// new version will be:
    with f.create(nil) do
    begin
    Parent:= Panel;  //--->  (I use a with statement for clarity:  f.parent := Panel);
    ...

Has the dynamically created form called by the procedure no parent (ins't it panel1 in the sample above ?)
The parent of the dynamiccaly created form by the procedure will always exist and it will always be a panel.
It MUST be supplied in the declaration of the procedure:  Procedure LoadTheForm(Name: String; T:TFormClass; Panel:TPanel); i.e Panel:TPanel.

If I call the Procedure (click on a button, item in a menu), each time I create a new form ot type  f := TFormClass(FindClass(Name)); //i.e form2, form3, ...

I usually test if a form is already created and exist like this:  
if not assigned(Form2) then Form2 := Tform2.Create(Application); // one instance of a form at a time
...

but in this case, I can't achieve it. Have you an idea ? The goal is easy: if the form doesn't exist, create it. If the form already exist (Form2 <> nil) show it to avoid duplicating the same form when you dont need a second instance. I could disable the item menu or button when creating the form and enable them when closing the form, but I try to check the state of existence/non existence of the dynamically created form.

Thanks .


Avatar of bernani

ASKER

OOps ...

should be:

does the form have to be a child form ---> the form can be or a main form or a child form, but it's ALWAYS dynamically created at runtime.

I increase the points for my complementary question.

in order to achieve that, you must have a container class. you can use a TComponentList for that; something like this:

unit TheUnitWithAllTheGoodies;// :)

interface

  some functions
 
Procedure TForm1.LoadTheForm(Name: String; T:TFormClass; Panel:TPanel);

implementation

var _list:TComponentList;

  some functions

Procedure TForm1.LoadTheForm(Name: String; T:TFormClass; Panel:TPanel);
var
  f : TFormClass;
begin
  // write code to find the form (by name or by class) in _list.
  if present in list then // if found, do nothing
    exit;
RegisterClass(T);
  try
    f := TFormClass(FindClass(Name));
    with f.create(_list) do
    begin
    Parent:= Panel;
    Align:= alClient;
    Show;
    end;
  except
    raise;
  end;
end;

initialization
  _list:=TComponentList.Create;

finalization
  _list.free; // will also free any chlid component from the list (in this case the forms)

end.

I wrote the above from memory, that is why I didn't gave full implementation. but the implementation should be straitforward. If you didn't succeed to make it until tomorrow, I'll do it for you (I was just about to close the PC when you posted this and though I could throw in this ;) )

cheers
indeed.. the copy-paste issue :D

just remove the "TForm1." from the function prototype/definition. sorry for that :D
2:21 am here. should probably go to sleep before posting again something bad :)
Avatar of bernani

ASKER

Hi ciuly ,

1.52 am here ...

I would be very glad to see a complete implementation of your prototype/function: until now, when I try to access a component on the form2, form3 ... calling it from another unit (Mainform/ Form1), I get an AV using this code:

in unit 1 is declared:

Uses .... Form2, Form3 ....;

implementation
Uses
fnForms; //the name of the generic unit containing all the proc/func relating to forms and their creation

.....

procedure TForm1.Button63Click(Sender: TObject);    
begin
LoadTheForm('TForm2',TForm2, panel1);
with Form2 do
    begin
    Button1.caption:= 'Test';
    ListBox1.Items.Add('Test');
    ....
    end;
end;


In Unit2 I've added:

......


Initialization
RegisterClass(TButton);
RegisterClass(TListBox);

I misunderstand sth, but I don't know what. Your comments and help are really welcome.

Thanks.
well, for starters:

LoadTheForm('TForm2',TForm2, panel1);
with Form2 do

if what you said with dynamically creating the forms is true, then that is incorrect because form2 is never initialized. if you look closely, form2 is a variable in unit2.
also, you should look at the project source files (or in the project -> options: forms) and see if the other forms are autocreated or not. If they are you should remove them from there since you did say that you are creating them dynamically.

I made this and it seems to work ok (form2 is NOT autocreated (see above)):

-------start
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses unit2, TheUnitWithAllTheGoodies;

procedure TForm1.Button1Click(Sender: TObject);
var f:TForm2;
begin
  f:=TForm2(LoadTheForm('TForm2',TForm2,panel1));
  with f do
  begin
    button1.Caption:='test';
  end;
end;

end.
--------end

-------start
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

end.
-----------end

--------start
unit TheUnitWithAllTheGoodies;// :)

interface

uses Forms, ExtCtrls;

  Function LoadTheForm(Name: String; T:TFormClass; Panel:TPanel):TCustomForm;

implementation
uses Contnrs, Classes, Controls;

var _list:TComponentList;

Function LoadTheForm(Name: String; T:TFormClass; Panel:TPanel):TCustomForm;
var
  f : TFormClass;
  ff : TCustomForm;
begin
  if _list.FindInstanceOf(t)>-1 then // if found, do nothing
    exit;
  RegisterClass(T);
  try
    f := TFormClass(FindClass(Name));
    ff := f.create(nil);
    _list.Add(ff);
    with ff do
    begin
      Parent:= Panel;
      Align:= alClient;
      Show;
    end;
    result:=ff;
  except
    raise;
  end;
end;

initialization
  _list:=TComponentList.Create;

finalization
  _list.free; // will also free any chlid component from the list (in this case the forms)

end.
----------end

hope this makes it clear now :)

cheers
In my opinion ciuly just hit the bullseye.  Sry i didnt respond yesterday, out of the house all day.
Avatar of bernani

ASKER

Hi ciuly ,

Thanks for this code.

... see if the other forms are autocreated  ...

No, they aren't: here is the way I create the prog

...

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Your code works great when i click on the TForm1.Button1Click and the caption of the Button1 in Form2 contains really the correct caption (Test) BUT if I click a second time on the TForm1.Button1 to check

if _list.FindInstanceOf(t)>-1 then // if found, do nothing
exit;

then I get an AV (EAccessViolation).

I've commented all other code in my unit to only keep the code you provide and the unit 2 contains only the same code as yours. After checking and re-checking and re-checking the code, I still get this AV and don't understand why.

So I've tested your code in a new project using your code (same unit names and code by copy and paste of your reply).
If you close the Form2 (not the prog) after Form2 has been created and if you try to re-create (click on TForm1.Button1Click), the Form2 won't show again.

After testing and re-testing and re-checking , for testing purpose, I've added a Showmessage in the function (and addes Dialogs in the Use clause)
  if _list.FindInstanceOf(t)>-1 then
  begin
  showmessage('Already existing'); // if found, do nothing
  exit;
  end;

Click on Form1.Button1 : create Form2 with correct Button1.caption
Close the Form2 (we are back to the main program's window) showing panel1
Click on Form1.Button1 : display the message 'Already existing' but Form2 isn't visible.

Any idea ?

Thanks
using the _list is an issue if you free the form in the onclose with your cafree.  That will not remove it from the list.  You will have to add another function that you call before the cafree to remove the instance from the list yourself.

Instead of cafree, if you just hide the form then everything should work correctly and the _list should clean up the memory in the finalization section


procedure removeformfromlistbeforefreeingit(f:TForm);
var
    ndx : Integer;
begin
   ndx :=  _list.IndexOf(f);  //

   if ndx<0 then
      exit;

   _list.Delete(ndx);
end;
  .  

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    removeformfromlistbeforefreeingit(Self);

    action:= caFree;  // --> to free the dynamically form created when calling LoadTheForm
    form2:= nil;        // --> assign nil to the reference after closing this form
end;

---------------

I didnt test this but it should work fine.  You may get a type mismatch and have to typecast the form to a tcomponent but I don't think that will happen.
Avatar of bernani

ASKER

Hi,

I'm still working on this function and have just tested it using some test shomessage after commenting the exit statement and modifiying the function like this:

Function LoadTheForm(Name: String; T:TFormClass; Panel:TPanel):TCustomForm;
var
  f : TFormClass;
  ff : TCustomForm;
begin
  if _list.FindInstanceOf(t) > -1  then
  begin
      Showmessage(_list.Items[_list.FindInstanceOf(t)].Name);
    // if found, do nothing
    //exit;
    ff.Show;
    end;
  RegisterClass(T);
  try
    f := TFormClass(FindClass(Name));
    ff := f.create(nil);
    _list.Add(ff);
    Showmessage(_list.Items[_list.FindInstanceOf(t)].Name);
    with ff do
    begin
      Parent:= Panel;
      Align:= alClient;
      Show;
    end;
    result:=ff;
  except
    raise;
  end;
end;

On each click, the value of Form2 becomes Form2, Form2_1, Form2_3 and so on ...

a) if I don't close the Form2:
 procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
action:= caFree;
form2:= nil;
end;

b) if I try to create another Form2 when another is open.

all what I want is to be able
- to create a form1, assign it to panel1
- do the same with Form3
- do the same with Form4
switch from Form1 to Form4, to Form3 ..... and if they are still open, show them again and if they are closed, create a new instance.

Each Form2, Form3 ... FormN MUST be created only ONCE and along the prof or showed again (if not closed) or created (in not open).

Thanks



 
Avatar of bernani

ASKER

Oops,

a typo

all what I want is to be able
- to create a form1, assign it to panel1
- do the same with Form3
 should be

all what I want is to be able
- to create a FORM2, assign it to panel1
- do the same with Form3
ASKER CERTIFIED SOLUTION
Avatar of 2266180
2266180
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of bernani

ASKER

Hi Ciuly

I've tested your final code function in my own program and it' working now really fine and ... in the way I wanted to achieve.

I believe, it an easy way to dynamically create and destroy forms, manage them along the prog, avoiding the risk to forget to free the dynamically resources allocated by them.

Many thanks for your help.



The only issue is that you are taking up the memory for the forms once they are created until the application is terminated.   If you have a lot of forms then you are wasting a lot of memory.

 If you are going to DYNAMICALLY manage the memory by creating them then why not free them when the user is finished with them.

The issue you were having was that you had a pointer to a form in your objectlist that was no longer valid.  All you needed to do was remove the pointer from the list prior to freeing the form.

If you are interesting in doing it this way then I will provide you with the code.
Avatar of bernani

ASKER

Hi kfoster11,

Thanks. Your code is welcome.

I've post another question relating this one: I give the code I use in a test program with some comments. Indeed after trying some functionnalites, I have noticed that the reference to the form seemed not to be correctedly released.

It's the reason of my new question:

https://www.experts-exchange.com/questions/21743801/Dynamically-creating-forms-Part-II-Memory.html

The given code work perfectly and I believe that all resources are completely freed after destruction of the form.

May I ask you, to propose your code in the new thread as this first part is finalized and alreday accepted.