Search code examples
delphilazarusdynamic-forms

dynamically create forms using a STRING


I have 5 forms created at design time. I need to dynamically create an instance of each form and put on a tab.

My question: If the form names are in an array of strings and I call my procedure like this:

ShowForm(FormName[3]);// To show the 3rd form on a tab page.

How can I define and create the new instance for each form?

This is what I have for now:

procedure TForm1.ShowFormOnTab(pProcName:String);
var
  NewForm: TfrmSetupItemCategories;//***HERE IS MY PROBLEM***

  NewTab: TTabSheet;
  FormName: String;

begin
  NewTab := TTabSheet.Create(PageControl1);
  NewTab.PageControl:= PageControl1;
  NewTab.Caption:='hi';
  PageControl1.ActivePage :=  NewTab;

  if pProcName='ProcfrmSetupItemCategories' Then
     begin
       NewForm:=TfrmSetupItemCategories.Create(NewTab);
       NewTab.Caption := NewForm.Caption;
     end;
  if pProcName='ProcfrmZones' Then
     begin
       NewForm:=TfrmZones.Create(NewTab);
       NewTab.Caption := NewForm.Caption;
     end;
.
.
.
end;

the line that reads "HERE IS MY PROBLEM" is where I need help. I can't reuse NewForm as a variable with a second form in this way...

Note: My problem is NOT the tab. Rather it's creating a new instance of the form using the same variable name.


Solution

  • Declare the NewForm variable as TForm:

    var
      NewForm: TForm;
    begin
      NewForm := TMyForm.Create(Tab1); //compiles OK
      NewForm := TMyOtherForm.Create(Tab2); //also compiles OK
    end;
    

    I'm assuming TMyForm and TMyOtherForm both are derivatives of TForm.

    DRY

    You can also reduce your repeating code using a class reference variable, like this:

    procedure TForm1.ShowFormOnTab(pProcName:String);
    var
      NewForm: TForm;
      ClassToUse: TFormClass;
      NewTab: TTabSheet;
      FormName: String;
    
    begin
      NewTab := TTabSheet.Create(PageControl1);
      NewTab.PageControl:= PageControl1;
      NewTab.Caption:='hi';
      PageControl1.ActivePage :=  NewTab;
    
      if pProcName='ProcfrmSetupItemCategories' then
        ClassToUse := TfrmSetupItemCategories
      else if pProcName='ProcfrmZones' then
        ClassToUse := TfrmZones
      else
        ClassToUse := nil;
      if Assigned(ClassToUse) then
      begin
        NewForm := ClassTouse.Create(NewTab);
        NewTab.Caption := NewForm.Caption;
        //if you access custom properties or methods, this is the way:
        if NewForm is TfrmZones then
          TfrmZones(NewForm).ZoneInfo := 'MyInfo';
      end;
    end;
    

    Register your classes and then create the forms from a string

    As Sir Rufo points in his comment, you can even go further registering your classes (I'm not sure if this can be done in Lazarus, that exercise is up to you).

    First, register the form classes you want to instantiate from the class name, previous to any call to your ShowFormOnTab method, for example:

    procedure TMainForm.FormCreate(Sender: TObject);
    begin
      RegisterClass(TfrmSetupItemCategories);
      RegisterClass(TfrmZones);
      //and other classes
    end;
    

    Then, you can change the code to get the class reference from the class name string:

    procedure TForm1.ShowFormOnTab(pProcName:String);
    var
      NewForm: TForm;
      ClassToUse: TFormClass;
      ClassNameToUse: string;
      NewTab: TTabSheet;
      FormName: String;
    
    begin
      NewTab := TTabSheet.Create(PageControl1);
      NewTab.PageControl:= PageControl1;
      NewTab.Caption:='hi';
      PageControl1.ActivePage :=  NewTab;
      //get rid of 'Proc' and add the T
      //or even better, pass directly the class name
      ClassNameToUse := 'T' + Copy(pProcName, 5, MaxInt);
      ClassToUse := TFormClass(FindClass(ClassNameToUse));
    
      if Assigned(ClassToUse) then
      begin
        NewForm := ClassTouse.Create(NewTab);
        NewTab.Caption := NewForm.Caption;
        //if you access custom properties or methods, this is the way:
        if NewForm is TfrmZones then
          TfrmZones(NewForm).ZoneInfo := 'MyInfo';
      end;
    end;
    

    That way, the code remains the same for any number of classes.

    For more info about this, take a look at Creating a Delphi form from a string in delphi.about.com.