Let's say that we want to dynamically create a button that will be able to close the parent form. To create this button, we have to declare it in the private or public sections of the form.
type TForm1 = class(TForm) private { Private declarations } public { Public declarations } DynamicButton: TButton; end;
This code cannot be compiled because none of the units included by default in the uses list contain the declaration of the TButton class. The TButton class is declared in the StdCtrls unit, so add it to the uses list. When you add a TButton component to the Designer Surface, the IDE automatically adds the StdCtrls unit to the uses list. Since we're creating the button without the help of the Designer Surface, we have to add the reference to the StdCtrls unit manually.
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
The best place to create this button is in the OnCreate event of the main form. To create the button, we have to call the TButton constructor.
Figure 12-12: Calling the TButton constructor
As you can see in Figure 12-12, component constructors accept an AOwner parameter that is used to define the component's owner. Since we want to add the button to the main form, we can pass Self as the AOwner parameter. Since we're writing the code for the main form, Self references the main form.
DynamicButton := TButton.Create(Self);
The constructor only creates the button and defines the button's owner. If you run the application now, you won't be able to see the button because the button's parent component hasn't been defined. To display the button, you have to define the button's parent component.
{ Set form as the parent } DynamicButton.Parent := Self;
Now that we've defined the button's parent, it will be displayed on the parent's surface. But it's hard to identify this control since it's situated in the top-left corner of the form and has no caption.
Figure 12-13: A dynamically created button
To completely define the button that will enable the user to close the form, set the Caption to "Close".
Listing 12-8: Dynamically creating a button
procedure TForm1.FormCreate(Sender: TObject); begin DynamicButton := TButton.Create(Self); { Set form as the parent } DynamicButton.Parent := Self; DynamicButton.Caption := 'Close'; DynamicButton.Top := 8; DynamicButton.Left := 8; end;
Currently, the DynamicButton component is only a graphical decoration on the form because none of its events have an event handler assigned. If you want to execute a piece of code when the user clicks the DynamicButton component, you have to assign an event handler to the OnClick event of the button. You can only assign an event handler with a compatible event list. If you take a look at the help file, you will notice that the OnClick event is declared like this:
[Delphi] property OnClick: TNotifyEvent;
TNotifyEvent is a custom type and defines a procedure that accepts a single TObject parameter.
type TNotifyEvent = procedure (Sender: TObject) of object;
So, a procedure that is compatible with the OnClick event has to accept a single TObject parameter.
Listing 12-9: A method compatible with the OnClick event
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } DynamicButton: TButton; procedure DynamicClick(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.DynamicClick(Sender: TObject); begin ShowMessage('The form will now close.'); Close; end;
Finally, since the OnClick event and the DynamicClick method are compatible, to connect the DynamicClick method with the OnClick event you only have to write this:
DynamicButton.OnClick := DynamicClick;
Figure 12-14: A dynamic component calling a dynamically assigned event handler
Dynamic component creation in Delphi and C++ is pretty much the same. The only difference between Delphi and C++ is that in C++ you have to use the new operator to create the component.
The first thing you have to do is declare an object variable in either the private or public section of the class. The component must be declared as a pointer because all VCL components must be heap allocated:
class TForm1 : public TForm { __published: private: TButton* DynamicButton; public: __fastcall TForm1(TComponent* Owner); };
In Delphi, after we declared a TButton variable, we had to add the StdCtrls unit to the uses list in order to compile the code. In C++, we don't have to include the StdCtrls unit because it is included in all VCL Forms projects by default.
Now that you've declared the necessary variable, you can switch to the source code file and create the object dynamically in the form's constructor. To create the component, you have to use the following syntax:
ObjectVariable = new Class(Constructor_Parameters);
Here's the code that creates the DynamicButton button:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { // pass this in the constructor to make the form the Owner DynamicButton = new TButton(this); // set form as the Parent to make the button show // itself on the form DynamicButton->Parent = this; DynamicButton->Caption = "Close"; DynamicButton->Top = 8; DynamicButton->Left = 8; }
You don't have to worry about destroying the button because we passed this in the button's constructor, which means the form will destroy it when the application terminates.
To assign an event handler to a component's event, you need to create a method with a compatible parameter list. Since we're going to add an event handler to the button's OnClick event, you need to create a method that is compatible with the TNotifyEvent type. In C++, the TNotifyEvent type looks like this:
typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);
As you can see from the above declaration, the TNotifyEvent type is a procedure that has only one parameter, an object pointer named Sender. The (__closure *TNotifyEvent) part of the declaration is the equivalent of Delphi's "of object" syntax, which is used to define a method pointer.
The __fastcall directive at the beginning of the declaration is a calling directive that makes sure the parameters are passed through CPU registers and in the appropriate order. To make your methods compatible with the VCL, you have to mark them with the __fastcall directive.
So, to add an event handler to the DynamicButton's OnClick event, first add the method's header to either the public or private section of the class:
class TForm1 : public TForm { __published: private: TButton* DynamicButton; void __fastcall DynamicClick(TObject* Sender); public: __fastcall TForm1(TComponent* Owner); };
When you've added the method header to the class interface, write the method's implementation:
void __fastcall TForm1::DynamicClick(TObject* Sender) { ShowMessage("The form will now close!"); Close(); }
Finally, to have the button call the DynamicClick method in response to its OnClick event, assign the method to the button's OnClick event:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { DynamicButton = new TButton(this); DynamicButton->Parent = this; DynamicButton->Caption = "Close"; DynamicButton->Top = 8; DynamicButton->Left = 8; DynamicButton->OnClick = DynamicClick; }