Reputation: 2128
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
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
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