CiucaS
CiucaS

Reputation: 2128

Delphi rebuild modal form at runtime

For example I created a small project as for you to understand what I'm trying to achieve.

I've got a ModalForm that has some buttons on it created at runtime, but when the user presses a " special button" I want all the buttons from the form to be deteled as other buttons will be created on runtime. Here is an example code

interface

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

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

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
    button : TButton;
begin
    button := TButton.Create(Self);
    button.Parent := Form2;
    button.Caption := 'New Button';
    button.Top := 50;
    button.Left := 200;
    Form2.ShowModal;
end;

end.


unit Unit2;

interface

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

type
  TForm2 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
var

button : TButton;
begin
    Form2.CloseModal;
    button := TButton.Create(Self);
    button.Parent := Form2;
    button.Caption := 'New Button';
    button.Top := 60;
    button.Left := 200;
// Form2.CloseModal;
    Form2.ShowModal;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
      ShowModal;
end;

end.

Now in this example I have 3 buttons on the form, when I click on button 1 I want a 4th button to appear. But if I run my code I get the error

"Cannot make a visible window modal"

I read that has something to do with the fact that I don't properly close my Form2. If I close Form2 and click on the button from Form1 ( same code as the button from Form2) it works and I get my 4th button the Form2.

My question is now, how can I achieve this result I get from clicking on the button from Form1 with clicking on the button from Form2.

Upvotes: 0

Views: 1610

Answers (2)

David Heffernan
David Heffernan

Reputation: 612784

There are a couple of obvious things wrong with this code.

Call to ShowModal from the constructor

The form's OnCreate event is fired during construction. At that point it is too early to show the form modally. So you must not call ShowModal there. I believe that you should simply remove TForm2.FormCreate. The form is shown modally from TForm1.Button1Click, and that suffices.

Spurious calls to CloseModal and ShowModal from TForm2.Button1Click

There's no need to call CloseModal and ShowModal in that method. You can perfectly well create a new button from that method, and there is no need to interfere with the modal loop. That's the cause of your error message. Remove those calls.

Use of global variable Form2 rather than Self

Although your form is clearly a single instance form, you should still avoid using the Form2 global variable from inside TForm2 methods. At some point in the future you might want to have two instance of the form. Or remove the global variable altogether. Remember that instance methods can use Self to refer to the instance. So, for instance, in TForm2 methods replace

button.Parent := Form2;

with

button.Parent := Self;

Likewise, in TForm2 methods, any time you feel compelled to write Form2.Foo, instead write Foo.

Enumerating controls inside another container

Use the ControlCount and Controls[] property of TWinControl to find all the controls in a container. For instance, if you know the parent of the buttons is the form, use ControlCount and Controls[] on the form to find its children. Then you can delete any buttons by calling Free on those buttons. Test for the control being buttons using Controls[Index] is TButton.

for Index := ControlCount-1 downto 0 do
  if Controls[Index] is TButton then
    Controls[Index].Free;

Note that we enumerate the controls in reverse index order. This trick avoids indexing problems that arise from us modifying the list whilst iterating over it.

Upvotes: 3

ain
ain

Reputation: 22749

You do not need to close and (re)show a form in order to dynamically add controls to it. In your TForm2.Button1Click method get rid of Form2.CloseModal and Form2.ShowModal calls, ie

procedure TForm2.Button1Click(Sender: TObject);
var button : TButton;
begin
    button := TButton.Create(Self);
    button.Parent := Self;
    button.Caption := 'New Button';
    button.Top := 50;
    button.Left := 200;
end;

should work.

BTW adding an button to Form2 in TForm1.Button1Click() is just bad design, don't do that (one form shouldn't change other like that). Rather have a method in Form2 which creates the button and then other forms can call that function. Or override the constructor of the Form2 so that it takes an extra parameter which indicates should the special button be visible or not.

To remove buttons you just call Free on them. Ie to delete all the buttons on the form

for x := ControlCount - 1 downto 0 do begin
   if(Controls[x] is TButton)then Controls[x].Free;
end;

But if you have predefined number of buttons it might be better to create them all at design time and then just change the Visible property as needed.

Upvotes: 1

Related Questions