1

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.

2
  • 1
    Why are you using hardcoded text strings? You aren't coding in PHP here! Use an enumerated type, or a class type, or a factory. Commented Dec 11, 2012 at 8:29
  • @DavidHeffernan First, as an old timer with a long history of Pascal, C, dbase, FoxPro and later on VB6, I guess my thinking is a little different (and may not necessarily be relevant to the platform). But more importantly, I think I just need a superb community with people like you to direct me and expose me to these techniques you have presented... And on a more technical note, the procedures and forms to be called are loaded from a database and assigne to a menu at runtime. Since my Lazarus code is meant to run on Linux/Windows, I'm avoiding APIs and the MDI. So there's a lot of ground work Commented Dec 11, 2012 at 13:15

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

11 Comments

its a little bit ugly how you determine the FormClass, although you could have register this classes to get the class by string ;o)
@Sir You're right for Delphi, I'm not sure about the Lazarus part, thus, I'm sticking on what's apparently working for the OP.
@Sir, I've added that to the answer.
That could well be true. In which case there should be a bespoke method that converts from the text to the class reference. It's bad form to mix it all up in one big blob. But I'm not holding you responsible for that!
+1 for going the extra mile with the "determine class from string".
|
3

Declare your variable as an ancestor type:

var
  NewForm: TForm;

or

var
  NewForm: TCustomForm;

Drawback: you'll need to cast the variable to the specific class if you want to call any methods of your form that you have declared yourself.

Use a 'soft' cast if you want to have the compiler check that NewForm is actually a TMyForm at runtime:

(NewForm as TMyForm).MyMethod;

When you are absolutely sure that NewForm is a TMyForm (like when you just created it), you can also use a 'hard' cast:

TMyForm(NewForm).MyMethod;

2 Comments

@marjan_venema Thanks for this input and +2 for your thought on casting. Honestly, I don't really get it. What is this casting about?
@itsols: oooh dear... Please check out Casting using traditional and as casting
0

With registered classes, in the initialization of the used forms, you could shorten it to

Function CreateAndDock(pc:TPageControl;const FormName:String):Boolean;
begin
  Result := false;
  if Assigned(GetClass(FormName)) and GetClass(FormName).InheritsFrom(TCustomForm)   then
  With TFormClass( GetClass(FormName)).Create(pc.Owner) do
    begin
     ManualDock(pc);
     Show;
     Result := true;
    end;
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
   ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TDockForm'))));
   ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TNotExists'))));
end;

2 Comments

Did you try this on Lazarus or Delphi? I seem to be bumping into erros trying to use TFormClass(GetClass(FormName)).Create... on Lazarus
Works's with Delphi. I've got no experiences with Lazarus, sorry.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.