Tutorial - Creating Controls at Run Time

'

Whilst browsing the newsgroups this question seems to be asked very often:How do I create controls at runtime?
as well asWhy can I not see the control I just created?
and How do I assign event handlers to my controls?
Here I intend to answer all of the above questions and provide sample code to show just how easy it is to do.

Getting Started - What the program will do

What we will do is write a very simple program which will comprise of a form with 2 buttons (TButton) on it. One will allow us to add new buttons to the form and the other will delete the last added button. All new buttons that are added will be placed next to each other on the form. When ever one of the new buttons is clicked a dialog box will be displayed showing which button was pressed.

Designing the form in the IDE

First we need to start a new application, (I called mine RunTime.dpr with the only other unit being main.pas). As it is a very simple program that we are writing all we need to add to the form is 2 buttons, so by selecting the Button from the Standard page of the component palette add 2 new buttons to your form. Place the 2 buttons next to each other at the top of the form calling one AddNewButton and the other DeleteLastButton. Then by using the Object Inspector create the function stubs for the OnClick event of the two buttons. We now have the skeleton of our program but do not run it yet as Delphi will remove our OnClick program stubs as they are not doing anything.

Writing the actual code

All we need to do now is fill in the two procedures (Delphi created for us) for the OnClick events of our two buttons, and write another procedure that will be called when one of our buttons is clicked.

The first procedure to write then is the one for adding the NewButton

procedure TForm1.AddNewButtonClick(Sender: TObject);
var
(* Pointer to the new button that we are going to create *)
NewButton : TButton;
begin
(*
This creates (in memory) the new button with the owner of it
being the form (self) so that the NewButton will be
destroyed automatically when the form is destroyed
*)
NewButton := TButton.create(self);
(*
By using the with statement on the new button we do not need to
to keep referencing its properties with NewButton. all the time
*)
with NewButton do
begin
(*
Set Top so that it appears underneath our two fixed buttons
*)
Top := 30;
(*
Make the width large enough to hold the caption
*)
Width := 60;
(*
This line takes a little more explanation. Every WinControl
has a ControlCount property which holds the number of
controls that are parented by it. So self.ControlCount will
return the number of controls on our form. We know of two of
these controls (our fixed buttons so by taking 2 off this we
have the number of NewButtons that we have created and
multiplying this by the width we have the left position of
the NewButton
*)
Left := Width * (self.ControlCount-2);
(*
This is the line that is most often forgotten, the parent
property should be set to the WinControl the button (or
any other component) is to be displayed on. In our case
this is self which will be the main form, if it is not
set your button will not be displayed
*)
Parent := self;
(*
This assigns the procedure CustomButtonClick (which will be
written later) to the OnClick event of the NewButton
*)
OnClick := CustomButtonClick;
(*
We calculate the button number as early, and add this to
the caption so that all of our new buttons will
have different captions
*)
Caption := 'Button '+ inttostr (self.ControlCount-2);
end; //With
end;

Next we need to complete the procedure for when the 'Delete Last Button' button is clicked

procedure TForm1.DeleteLastButtonClick(Sender: TObject);
begin
(* Make sure there are some new buttons on the form *)
if Self.ControlCount>2 then
(* Delete the last added button *)
TButton (Controls[ControlCount-1]).destroy;
end;

Finally we need to write a new procedure to deal with the OnClick event for all our new buttons. First define it in the private section of the unit:

Notice that the procedure has the same layout as the other button clicks, so if we wish to respond to another event just see what the syntax is using one of the Buttons at design time (i.e. double clicking the event in the Object Inspector). Copy it, changing the procedure name to something different. And assign the event to that procedure (e.g. OnDragDrop := MyNewDragDop). Then write the procedure using the required syntax.

Here is the procedure for our OnClick event:

procedure TForm1.CustomButtonClick(Sender: TObject);
begin
(* Display the caption of the pressed button on a Message *)
(* Box to indicate which button was pressed *)
(* The pressed button is got from the sender parameter of the procedure *)
(* which needs casting to TButton *)
ShowMessage(TButton(Sender).caption + ' Pressed');
end;

Conclusion

I hope this has been a useful starter tutorial for creating controls at run time. When writing your own application you may find it is better to store pointers to them in an array or TList so that it is easier to access them. I used the ControlCount on the form as this was the simplest way in the circumstances, but if you have other controls on the form this will probably not be the case. I will be writing a sample application in the future that allows the user to create buttons at runtime assigning them to executables on their machine which will be launched when the button is clicked. A custom toolbar and launch pad (like that seen in Microsoft Office).

You can also save the positions of these dynamically created controls by using the information from this other tutorial