Jump to content
Sign in to follow this  
Qasim Shahzad

How to get ThisFormClass (TForm1) from Form1

Recommended Posts

Greetings Dear Delphians,

 

Say we have 20 Forms in an App. We use these 6 lines to create 20 Forms at different places.

  Form1 := TForm1.Create;
  try
    Form1.ShowModal;
  finally
    Form1.Free;
  end;

 

When refactoring we can see this is a lot of code duplication and we should encapsulate form creation in a procedure like this.
 

procedure ShowForm ( ThisForm: TCustomForm ) ;
begin
  ThisForm := TCustomForm(ThisFormClass.Create);//?? How to Get Form Class
  try
    ThisForm.ShowModal;
  finally
    ThisForm.Free;
  end;
end;

I only have problem in first line. how to get ThisForm parent class to call it.

  1. Is it even possible?
  2. If yes then how?
  3. What about FMX?

Any help or pointer is appreciated.
Thanks and stay blessed

Share this post


Link to post

For VCL:

TWhateverForm.Create(whoever).Show[Modal]; and in OnClose event Action := caFree; and remove every form reference like "Form1: TForm1" except the main form's which is needed.

If you need to pass arguments or need results then you can add a method to the Form class like public function ShowForm(myarguments): resulttype etc..

 

FMX, I don't know, I'd be surprised if it would be much different.

  • Like 1

Share this post


Link to post

I think this is a bad idea, however here is a code for VCL:

 

procedure ShowForm ( ThisForm: TFormClass ) ;
var
  lForm: TForm;
begin
  lForm := ThisForm.Create(nil);
  try
    lForm.ShowModal;
  finally
    lForm.Free;
  end;
end;

{Example to use}
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowForm(Tform1);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowForm(Tform2);
end;

The difference between VCL and Fmx is that in Fmx you've to define TFormClass:

type
  TFormClass = class of TForm;

 

  • Thanks 1

Share this post


Link to post

I have it this way (only for VCL). In the manner of Attila Kovacs.
I only hide the main form by placing it off the screen.
 

procedure NewForm(ATypeForms: TTypeForm);
const
  Position = 200;
var
  BasalFormClass: TBasalFormClass;
  LeftMainForm, TopMainForm: Integer;
  BasFrm: TBasalForm;
begin
  ServeMainForm(LeftMainForm, TopMainForm);
  BasalFormClass := GetForm(ATypeForms);

  try
    BasFrm := BasalFormClass.CreateBasalForm(nil);
    BasFrm.Position := poDesigned;
    SupObjJson.ReadForm(BasFrm);
    BasFrm.ShowModal;
  finally
    if (oGlobVar.ActualForm = Application.MainForm) then
    begin
      if (LeftMainForm < 0) then
      begin
        LeftMainForm := Position;
        TopMainForm := Position;
        Application.MainForm.Left := LeftMainForm;
        Application.MainForm.Top := TopMainForm;
      end;
    end;
  end;
end;

 

Edited by Stano
  • Like 1

Share this post


Link to post
unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Unit1,
  Unit2, Unit3;

type
  TfrmMain = class ( TForm )
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    BitBtn3: TBitBtn;
    procedure BitBtn1Click ( Sender: TObject ) ;
    procedure BitBtn2Click ( Sender: TObject ) ;
    procedure BitBtn3Click ( Sender: TObject ) ;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure ShowForm ( ThisForm: TFormClass ) ;
var
  lForm: TForm;
begin
  lForm := ThisForm.Create ( nil ) ;
  try
    lForm.ShowModal;
  finally
    lForm.Free;
  end;
end;

procedure TfrmMain.BitBtn1Click ( Sender: TObject ) ;
begin
  ShowForm ( TForm1 ) ;
end;

procedure TfrmMain.BitBtn2Click ( Sender: TObject ) ;
begin
  ShowForm ( TForm2 ) ;
end;

procedure TfrmMain.BitBtn3Click ( Sender: TObject ) ;
begin
  ShowForm ( TForm3 ) ;
end;

end.

Thank you so much. That was very clever. VCL version is working perfectly fine. I will check on FMX also.

 

Share this post


Link to post
16 minutes ago, Lajos Juhász said:

I think this is a bad idea, however here is a code for VCL:

 


Now Please guide why this is a bad idea. It reduces a lot of code duplication and we can add Form Creation procedure in a common library.

Share this post


Link to post
6 minutes ago, Qasim Shahzad said:

we can add Form Creation procedure in a common library

well, in this case you could derive TMyForm from TForm which has a .Show[Modal]: returntype; method and do many more things for every form in one place, and no need for a global "utils.pas". Just edit the class declaration at the top of the file from class(TForm) to class(TMyForm) (after you implemented TMyForm of course)

Edited by Attila Kovacs
  • Like 1

Share this post


Link to post

Nearly all forms I show modal return a result.
At the most basic level all these forms are inherited from one base form which has this code:

 

class function TBaseDialogForm.ShowDialog(AOwner: TComponent): Boolean;
begin
  with Create(AOwner) do
    try
      Result := ShowModal = mrOK;
    finally
      Free;
    end;
end;
    class function ShowDialog(AOwner: TComponent): Boolean; virtual;

 

  • Like 1

Share this post


Link to post
19 minutes ago, Qasim Shahzad said:


Now Please guide why this is a bad idea. It reduces a lot of code duplication and we can add Form Creation procedure in a common library.

It will work fine so long as you use the virtual constructor and all your classes override that correctly. 

 

If you passed in an anon method that creates and returns a newly minted form, that would allow other constructors to be used. 

 

To me this is pointless though. It's a really common pattern, it's only a handful of lines. I don't really see the benefit.

 

I mean, if you were trying to centralise some logging or other aspect, then it would make sense. Otherwise I don't really see the benefit. 

 

Also the name sucks. Should include the fact that the form is shown modally. 

  • Thanks 1

Share this post


Link to post
7 hours ago, Qasim Shahzad said:

Now Please guide why this is a bad idea. It reduces a lot of code duplication and we can add Form Creation procedure in a common library.

When you're displaying the forms this way they are useless there are no input values to the form and the form doesn't produce any result. You're destroying the instance before the calling code could read the results back.

 

I am aware that you can initialize the form in the formcreate and write the results back in formdestroy but that is a bad design.

  • Like 1

Share this post


Link to post
1 hour ago, Lajos Juhász said:

When you're displaying the forms this way they are useless there are no input values to the form and the form doesn't produce any result. You're destroying the instance before the calling code could read the results back.

 

I am aware that you can initialize the form in the formcreate and write the results back in formdestroy but that is a bad design.

I have plenty of similar constructions in my DB app to edit some tables. So init is fully contained in form c-tor/design props/onShow, changes are applied internally and result is not important.

  • Like 1

Share this post


Link to post
9 hours ago, David Heffernan said:

It will work fine so long as you use the virtual constructor and all your classes override that correctly. 

 

If you passed in an anon method that creates and returns a newly minted form, that would allow other constructors to be used. 

 

To me this is pointless though. It's a really common pattern, it's only a handful of lines. I don't really see the benefit.

 

I mean, if you were trying to centralise some logging or other aspect, then it would make sense. Otherwise I don't really see the benefit. 

 

Also the name sucks. Should include the fact that the form is shown modally. 

That's great. These are valid concerns. To me the real benefit was readability and clean code. But centralizing logging is another benefit.

Procedure name suggestion is also very thoughtful. It should be ShowModalForm Thank you so much.

Share this post


Link to post
2 hours ago, Lajos Juhász said:

When you're displaying the forms this way they are useless there are no input values to the form and the form doesn't produce any result. You're destroying the instance before the calling code could read the results back.

 

I am aware that you can initialize the form in the formcreate and write the results back in formdestroy but that is a bad design.

Yes these are valid concerns. But in my usage, these forms are like different parts of the application and mostly have no need to pass values or get result. Even if something is changed by a form like SettingsForm, It is persisted in file, DB or registry so next Form or Calling routine can/should load it from there. So in this scenario it is OK. But if it gets complex as you pointed out then we can create that form directly instead of calling a procedure.

Nevertheless, your solution was a mental massage😊. This little problem/challenge was annoying me very much. Thank you so much. Stay Blessed.

Share this post


Link to post
39 minutes ago, Fr0sT.Brutal said:

I have plenty of similar constructions in my DB app to edit some tables. So init is fully contained in form c-tor/design props/onShow, changes are applied internally and result is not important.

Exactly, My usage is also like this. For any other scenario (In/Out Values) this might not be good solution. But for simple creation, display and destroy usage it is neat and time saving.

Share this post


Link to post
9 hours ago, FredS said:

Nearly all forms I show modal return a result.
At the most basic level all these forms are inherited from one base form which has this code:

 


class function TBaseDialogForm.ShowDialog(AOwner: TComponent): Boolean;
begin
  with Create(AOwner) do
    try
      Result := ShowModal = mrOK;
    finally
      Free;
    end;
end;

    class function ShowDialog(AOwner: TComponent): Boolean; virtual;

 

That's good idea and we can get ModalResults with this method. Thanks for sharing.

Share this post


Link to post
// Return child form of a given class. Create new form if not in Components list yet
function TfrmMDIMain.GetChildForm(ChildFormClass: TFormClass): TForm;
var i: Integer;
begin
  // Seach by class
  // Not via MDIChildren because there are non-MDI as well (frmReport)
  for i := 0 to ComponentCount - 1 do
    if Components[i] is ChildFormClass then
      begin Result := TForm(Components[i]); Exit; end;
  // create new if not found. Owner is set so child form is destroyed automatically on close
  Result := ChildFormClass.Create(Self);
end;

// Sample usage - specific init
procedure TfrmMDIMain.NPersonsIndexClick(Sender: TObject);
begin
  with TfrmPersons(GetChildForm(TfrmPersons)) do
  begin
    SetPersonType(TPersonType(-1));
    BringToFront;
  end;
end;

// Sample usage - no explicit init
procedure TfrmMDIMain.NSpecsIndexClick(Sender: TObject);
begin
  GetChildForm(TfrmSpecs).BringToFront;
end;

Here's how I do it in my MDI app (DB client with several UIs for table editing). Note that there could be only one form of a particular class. Also closed forms remain in memory thus consuming resources - not an issue in my case but a subject to note.

As for modal windows, I don't bother excess lines and just use

procedure TfrmMDIMain.NImportClick(Sender: TObject);
begin
  with TfrmImport.Create(nil) do
  try
    ShowModal;
  finally
    Free;
  end;
end;

 

I also extend TForm class with some customizations and derive child forms from it without the need to install this as component by magic type override trick:

  // Form with common actions on create and close: save/restore size, ctx help, zoom etc
  TCustomMDIChildForm = class(TForm)
    procedure DoCreate; override;
    procedure DoClose(var Action: TCloseAction); override;
  end;

and FormSmth.pas:


  // magic!
  TForm = TCustomMDIChildForm;

  TfrmSmth= class(TForm)
  ...
  end;

 

Edited by Fr0sT.Brutal
  • Like 1

Share this post


Link to post
Guest

maybe some like this... :>(

 

unit uMyClassToForms;

interface

uses
  System.SysUtils,
  FMX.Forms;

type
  TFormClass = class of TForm; // only for FMX usage!  to VCL just comment this line and use the VCL units on "uses" clause.

  TMyClassForms = class
  private
    class var FMyListForm: TArray<TForm>;
  public
    class destructor Destroy;
    class function MyFormReturn(AClassOf: TFormClass): TForm;
    class function MyClassFromFormReturn(AForm: TForm): TClass; overload;
    class function MyClassFromFormReturn(ATForm: TFormClass): TClass; overload;
  end;

implementation

{ TMyClassForms }

class destructor TMyClassForms.Destroy;
begin
  for var MyItem in FMyListForm do
    MyItem.Free;
  //
  FMyListForm := [];
end;

class function TMyClassForms.MyClassFromFormReturn(AForm: TForm): TClass;
begin
  if (AForm = nil) then
    raise Exception.Create('NIL noooooooooooo!');
  //
  result := AForm.ClassType;
end;

class function TMyClassForms.MyClassFromFormReturn(ATForm: TFormClass): TClass;  // im silly ><)
begin
  if (ATForm = nil) then
    raise Exception.Create('NIL noooooooooooo!');
  //
  result := ATForm;
end;

class function TMyClassForms.MyFormReturn(AClassOf: TFormClass): TForm;
begin
  if (AClassOf = nil) then
    raise Exception.Create('NIL noooooooooooo!');
  //
  result := AClassOf.Create(nil);
  //
  FMyListForm := FMyListForm + [result];
end;

initialization

ReportMemoryLeaksOnShutdown := true;

finalization

end.

 

unit Unit1;

... FORM main

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  uMyClassToForms,
  Unit2,
  Unit3;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(TMyClassForms.MyClassFromFormReturn(TForm2).ClassName);
  //
  // ShowMessage(TMyClassForms.MyClassFromFormReturn(TForm3.Create(nil)).ClassName); // ...Create... needs free it!!!
  //
  TMyClassForms.MyFormReturn(TForm2).ShowModal;
  TMyClassForms.MyFormReturn(TForm3).ShowModal;
  //
  // TMyClassForms.MyFormReturn(nil).ShowModal; // an "AV"...
end;

end.

 

unit Unit2;

... SECOND FORM ... chain called

var
  Form2: TForm2;

implementation

{$R *.fmx}

uses
  uMyClassToForms,
  Unit3;

procedure TForm2.Button1Click(Sender: TObject);
begin
  ShowMessage(TMyClassForms.MyClassFromFormReturn(TForm2).ClassName);
  //
  // ShowMessage(TMyClassForms.MyClassFromFormReturn(TForm3.Create(nil)).ClassName); // ...Create... needs free it!!!
  //
  // TMyClassForms.MyFormReturn(TForm2).ShowModal; myself ... nooooooo!
  TMyClassForms.MyFormReturn(TForm3).ShowModal;
  //
  // TMyClassForms.MyFormReturn(nil).ShowModal; // an "AV"...
end;

end.

 

Edited by Guest

Share this post


Link to post
On 12/23/2021 at 1:03 PM, Attila Kovacs said:

For VCL:

TWhateverForm.Create(whoever).Show[Modal]; and in OnClose event Action := caFree; and remove every form reference like "Form1: TForm1" except the main form's which is needed.

If you need to pass arguments or need results then you can add a method to the Form class like public function ShowForm(myarguments): resulttype etc..

The problem with above is that you will be scratching your head as to why you have AV's everywhere when you mix patterns, not to mention the fact that it will not be disposed of if there is an AV in the modal form.  But we never have those do we.  🙂  

 

The simplest solution is what Lajos Juhász suggests above.  You could easily make this a standardized procedure or class method.  I just wouldn't be afraid of the standard pattern, its simple and easy to maintain.

Share this post


Link to post
8 hours ago, Steven Kamradt said:

The problem with above is

there is no problem with that pattern and there is no simpler pattern and it introduces no unnecessary dependencies

Share this post


Link to post
9 hours ago, Attila Kovacs said:

there is no problem with that pattern

First, lets agree to disagree.

 

Yes, there is no problem with that pattern as long as it is used consistently for those forms.  However, if you start to mix patterns with those forms, where sometimes you use the method you pointed out, and other times use the try/finally block with a variable you will likely run into AV when the destructor gets called twice in the later pattern (once on form close, then again on the explicit free). This is not something that is not always easy to find the cause of.  Sure, if you have a small team or project it won't be that large of a deal, but if your project becomes more complex or has a larger team working on it the deviation from the norm can have an unexpected cost. 

Share this post


Link to post
8 minutes ago, Steven Kamradt said:

the deviation from the norm can have an unexpected cost

This is pretty true for everything in life. This is all made up, there is no need to mix anything, there should be rules to follow and that's it. I'd also find the problem in a very short time.

Also, where would you store all the opened non-modal forms and why would you do that in first place?

Let me guess, are those "big, complex projects" which are full of cross-form  references? Form2.Edit.Text := 'lala'?

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×