How to create a form programmatically with a couple components on it in Delphi

Mikhail picture Mikhail · Apr 19, 2013 · Viewed 32.3k times · Source

I'm working with Delphi 7 and I'm trying to create a form programmatically. Here's my form class stub:

unit clsTStudentInfoForm;

interface

    uses Forms;

    type
        TStudentInfoForm = class (TForm)

        end;

implementation


end.

I also have a button on my main form (that's just a regular form that's supposed to create and show the form above at run-time) and when clicked it creates and shows the student form as a modal window. It does show the form but there's nothing on it. The only thing you can do is click the close button at the upper-right corner of the window to close it.

procedure TLibraryForm.btnShowStudentIfoFormClick(Sender: TObject);
var
    f : TStudentInfoForm;
begin
    f := TStudentInfoForm.CreateNew(Self);
    f.ShowModal;
    f.Free;
    f := nil;
end;

enter image description here

I have no idea how to add components to a programmatically-created form (not at run-time, but to the source code). Can you help me write some code that adds an Okay button to the student form as well as sets the caption and the form's height and width (the code has to be written in the student form file)?

Any suggestions and examples will be highly appreciated. Thank you.

Answer

NGLN picture NGLN · Apr 19, 2013

By default (that is: with all default IDE configuration settings), newly designed forms are automatically created. Only the main form will be shown, and secondary forms can be shown with:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Show;
  Form3.ShowModal;
end;

It is good common practice to disable this auto creation option. Go to: Tools > (Environment) Options > (VCL) Designer > Module creation options, and disable/uncheck the Auto create forms & data modules option.

Instead, create an (already designed) form only when it is needed:

procedure TForm1.Button1Click(Sender: TObject);
var
  Form: TForm2;
begin
  Form := TForm2.Create(Self);
  Form.Show;
end;

This illustrates as well that the global variables for the secondary forms are not needed, and it is good common practice to delete them as soon as possible to prevent wrong usage:

type
  TForm2 = class(TForm)
  end;

//var
//  Form2: TForm2;  << Always delete these global variable

implementation

If you do not want to set up such secondary form with the form designer, then you need to create all controls in code at runtime. As follows:

unit Unit2;

interface

uses
  Classes, Forms, StdCtrls;

type
  TForm2 = class(TForm)
  private
    FButton: TButton;
  public
    constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  end;

implementation

{ TForm2 }

constructor TForm2.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
begin
  inherited CreateNew(AOwner);
  FButton := TButton.Create(Self);
  FButton.SetBounds(10, 10, 60, 24);
  FButton.Caption := 'OK';
  FButton.Parent := Self;
end;

end.

As you see, I used the CreateNew constructor. This is necessary for T(Custom)Form derivatives:

Use CreateNew instead of Create to create a form without using the associated .DFM file to initialize it. Always use CreateNew if the TCustomForm descendant is not a TForm object or a descendant of TForm.

For all other container controls (such as TPanel, TFrame, etc.) you can override the default constructor Create.

Call this form as follows:

procedure TForm1.Button1Click(Sender: TObject);
var
  Form: TForm2;
begin
  Form := TForm2.Create(nil);
  try
    Form.ShowModal;
  finally
    Form.Free;
  end;
end;

Or:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FForm := TForm2.CreateNew(Application);
  FForm.Show;
end;

In this last case, the form is not freed but hidden when it is closed, so you need to store its reference in a private field (FForm) and free it later. Or you can do it automatically:

unit Unit2;

interface

uses
  Classes, Forms, StdCtrls;

type
  TForm2 = class(TForm)
  private
    FButton: TButton;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  public
    constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  end;

implementation

{ TForm2 }

constructor TForm2.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
begin
  inherited CreateNew(AOwner);
  OnClose := FormClose;
  FButton := TButton.Create(Self);
  FButton.SetBounds(10, 10, 60, 24);
  FButton.Caption := 'OK';
  FButton.Parent := Self;
end;

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

end.

Now, you could call it without storing a reference:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TForm2.CreateNew(Self).Show;
end;

Whether you pass, Self, Application or nil as owner for the new form depends on when you want to get it automatically destroyed in case you do not free it manually or via the OnClose event. Using

  • Self: will destroy the new form when the calling form is destroyed. This is particularly usefull when the calling form isn't the main form.
  • Application: will destroy the new form when the application ends. This would be my preferred choice.
  • nil: will not destroy the new form and results in a memory leak at application's finish. Though, memory will ultimately be released when Windows kills the process.