Creating components at runtime - Delphi
Asked Answered
K

9

22

How can I create a component at runtime and then work with it (changing properties, etc.)?

Kandacekandahar answered 17/6, 2009 at 6:12 Comment(0)
D
74

It depends if it is a visual or non-visual component. The principle is the same, but there are some additional considerations for each kind of component.

For non-visual components

var
  C: TMyComponent;
begin
  C := TMyComponent.Create(nil);
  try
    C.MyProperty := MyValue;
    //...
  finally
    C.Free;
  end;
end;

For visual components:

In essence visual components are created in the the same way as non-visual components. But you have to set some additional properties to make them visible.

var
  C: TMyVisualComponent;
begin
  C := TMyVisualComponent.Create(Self);
  C.Left := 100;
  C.Top := 100;
  C.Width := 400;
  C.Height := 300;
  C.Visible := True;
  C.Parent := Self; //Any container: form, panel, ...

  C.MyProperty := MyValue,
  //...
end;

A few explanations to the code above:

  • By setting the owner of the component (the parameter of the constructor) the component gets destroyed when the owning form gets destroyed.
  • Setting the Parent property makes the component visible. If you forget it your component will not be displayed. (It's easy to miss that one :) )

If you want many components you can do the same as above but in a loop:

var
  B: TButton;
  i: Integer;
begin
  for i := 0 to 9 do
  begin
    B := TButton.Create(Self);
    B.Caption := Format('Button %d', [i]);
    B.Parent := Self;
    B.Height := 23;
    B.Width := 100;
    B.Left := 10;
    B.Top := 10 + i * 25;
  end;
end;

This will add 10 buttons at the left border of the form. If you want to modify the buttons later, you can store them in a list. (TComponentList ist best suited, but also take a look at the proposals from the comments to this answer)

How to assign event handlers:

You have to create an event handler method and assign it to the event property.

procedure TForm1.MyButtonClick(Sender: TObject);
var
  Button: TButton;
begin
  Button := Sender as TButton; 
  ShowMessage(Button.Caption + ' clicked');
end;

B := TButton.Create;
//...
B.OnClick := MyButtonClick;
Devilmaycare answered 17/6, 2009 at 6:12 Comment(10)
But if I don't surely know how many components I want to create, e.g. if it depends on user's decision. So how can I declare components dynamically?Upstanding
The distinction whether to pass nil or another component as the owner has nothing to do with the component being visible or not, only with the lifetime of the object. An invisible component that is not freed in the same method could be created just like in your second snippet, and be freed automatically by the owner.Contradiction
Of course you are right, but in my example I delete it explicitly so it is not really necessary.Devilmaycare
What I mean is that I don't see how "it depends if it is a visual or non-visual component". It doesn't. Your two snippets differ only in the intended lifetime of the created component.Contradiction
Not all "Components" are "Controls". Those components neither have the parent property nor one of the left/top/width/height properties. But for visual components it's necessary to set those properties as for non-visual components you just can not. Because of that I think the distinction is justified.Devilmaycare
hi DR , this would be more complete if you provide exmaples for how to assigne procedures to just created comopents ;)Rive
@LuckyNeo : You will need another way of referencing the components. DR's suggestion of using a TComponentList is one way of doing this. The Sender parameter of event handlers can be useful as well. f it is a fixed set of possible controls, you could declare all of them, but selectively set the Visible property to falseAbort
Setting the name of each dynamically created component, and later use TComponent.FindComponent() is another way of referencing them at runtime.Contradiction
I hope someone can help me with this. Im creating the buttons at runtime, and Im assigning the events with this code. I create many different "Group of buttons" at runtime, how can I send a value to this procedure so it would assign another procedure to the event handler depending on the "Group" im creating? Example: IF group 1 procedure in the event is X, IF group 2 Procedure in the event is Y. Thank You.Foch
setting C.Parent := Self should be the first thing to do!Boehike
S
27

To simplify the runtime component creation process, you can use GExperts.

  1. Create a component (or more components) visually and set its properties.
  2. Select one or more components and execute GExperts, Components to Code.
  3. Paste the generated code into your application.
  4. Remove component(s) from the visual form designer.

Example (TButton-creation code generated in this way):

var
  btnTest: TButton;

btnTest := TButton.Create(Self);
with btnTest do
begin
  Name := 'btnTest';
  Parent := Self;
  Left := 272;
  Top := 120;
  Width := 161;
  Height := 41;
  Caption := 'Component creation test';
  Default := True;
  ParentFont := False;
  TabOrder := 0;
end;
Supinate answered 17/6, 2009 at 7:22 Comment(3)
Great tip! It's exactly what I would have suggested. GExperts is a great tool to use with Delphi.Briton
...or you could design it in the visual editor and then take a peak into the .dfm file. Basically the exact same thing is there in the textBiophysics
Gracias. I prefer to write all things by myself (I know that is maybe reinvent the wheel but I feel more control on it) anyway it seems the GExpert tool don't change in pure code and that sounds is good. Thanks again for advise.Frerichs
S
4

I would just like to add that when dynamically adding controls... it as a good idea to add them to an object list (TObjectList) as suggested in <1> by @Despatcher.

procedure Tform1.AnyButtonClick(Sender: TObject);
begin
  If Sender is TButton then
  begin
    Case Tbutton(Sender).Tag of 
    .
    .
    .
// Or You can use the index in the list or some other property 
// you have to decide what to do      
// Or similar :)
  end;
end;

procedure TForm1.BtnAddComponent(Sender: TObJect)
var
  AButton: TButton;
begin
  AButton := TButton.Create(self);
  Abutton. Parent := [Self], [Panel1] [AnOther Visual Control];
  AButton.OnClick := AnyButtonClick;
// Set Height and width and caption ect.
  .
  .
  . 
  AButton.Tag := MyList.Add(AButton);
end;

You need to add the Unit 'Contnrs' to your Uses list. I.e System.Contnrs.pas the base Containers Unit And you can have many object lists. I suggest using a TObjectList for each type of control that you use e.g.

Interface
 Uses Contnrs;
Type
 TMyForm = class(TForm)
private
   { Private declarations }
public
   { Public declarations }
end;
 Var
  MyForm: TMyForm;
  checkBoxCntrlsList: TObjectList; //a list for the checkBoxes I will createin a TPanel
  comboboxCntrlsList: TObjectList; //a list of comboBoxes that I will create in some Form Container

this allows you to easily manipulate/manage each control as you will know what type of control it is e.g.

Var comboBox: TComboBox;
I: Integer;

begin
 For I = 0 to comboboxCntrlsList.Count -1 do // or however you like to identify the control you are accessing such as using the tag property as @Despatcher said
   Begin
    comboBox := comboboxCntrlsList.Items[I] as TComboBox;
    ...... your code here
   End;
end;

This allows you to then use the methods and properties of that control Don't forget to create the TObjectLists, perhaps in the form create event...

checkBoxCntrlsList := TObjectList.Create;
comboboxCntrlsList := TObjectList.Create;
Serviette answered 14/10, 2012 at 6:56 Comment(0)
C
1

But if I don't surely know how many components I want to create, e.g. if it depends on user's decision. So how can I declare components dynamically?

The answer has been suggested - the easiest way is a List of Objects(components). TObjectList is the simplest to use (in unit contnrs). Lists are great!

  In Form1 Public
  MyList: TObjectList;
  procedure AnyButtonClick(Sender: TObject); 

// You can get more sophisticated and declare //TNotifyevents and assign them but lets keep it simple :) . . .

procedure Tform1.AnyButtonClick(Sender: TObject);
begin
  If Sender is TButton then
  begin
    Case Tbutton(Sender).Tag of 
    .
    .
    .
// Or You can use the index in the list or some other property 
// you have to decide what to do      
// Or similar :)
  end;
end;

procedure TForm1.BtnAddComponent(Sender: TObJect)
var
  AButton: TButton;
begin
  AButton := TButton.Create(self);
  Abutton. Parent := [Self], [Panel1] [AnOther Visual Control];
  AButton.OnClick := AnyButtonClick;
// Set Height and width and caption ect.
  .
  .
  . 
  AButton.Tag := MyList.Add(AButton);
end;

An Object list can contain any object visual or not but that gives you an added overhead of sorting out which items are which - better to have related lists if you want multiple dynamic controls on similar panels for instance.

Note: like other commenters I may have over-simplified for brevity but I hope you ge the idea. You need a mechanism to manage the objects once they are created and lists are excellent for this stuff.

Cellarer answered 17/6, 2009 at 11:20 Comment(0)
H
1

Some components override the 'Loaded' method. This method will not be called automatically if you create an instance at runtime. It will be called by Delphi when loading from the form file (DFM) is complete.

If the method contains initialization code, your application might show unexpected behaviour when created at runtime. In this case, check if the component writer has used this method.

Hillell answered 17/6, 2009 at 14:7 Comment(0)
S
1

If you nest win controls in Group Boxes/Page Controls/Etc..., I think it is beneficial to have the parent group box also be the owner. I've noticed a sharp decrease in window close times when doing this, as opposed to having the owner always be the main form.

Somatic answered 17/6, 2009 at 15:32 Comment(0)
H
1

During a research on "creating a delphi form using xml based template", I find something useful pointing out RTTI and using open tools api (ToolsApi.pas I think). Have a look at the interfaces in the unit.

Hadleigh answered 19/6, 2009 at 13:32 Comment(0)
R
0

Very ease. Call Create. Example:

procedure test
var
  b : TButton;
begin
  b:=TButton.Create(nil);
  b.visible:=false;
end;

This creates a component (TButton is a component) at runtime and sets the property visible.


For the constructor: pass nil if you want to manage the memory yourself. Pass a pointer another component if you want to have it destroyed when the other component is destroyed.

Readymade answered 17/6, 2009 at 6:14 Comment(2)
There is a need to pass pointer to the owner of element. TButton.Create( owner);Riflery
> need for owner Not necessarily. TButton.Create(nil); is valid code. but you now need to explicitly destroy it. Creating visual components with a nil owner is sometime useful.Cellarer
R
-2

This is example how to emulate button tag on Evernote

unit Unit7;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, CHButton, Vcl.ExtCtrls, RzPanel, CHPanel, RzCommon,RzBmpBtn, Vcl.StdCtrls;

type
  // This is panel Button
  TButtonClose = class (TRzPanel)
   CloseButton : TRzBmpButton;
   procedure CloseButtonClick(Sender: TObject);
   procedure CloseButtonMouseEnter(Sender: TObject);
   procedure MouseDown(Sender: TObject; Button: TMouseButton;
             Shift: TShiftState; X, Y: Integer);
   procedure MouseUp(Sender: TObject; Button: TMouseButton;
             Shift: TShiftState; X, Y: Integer);
public
   constructor Create(AOwner: TComponent); override;
   destructor Destroy; override;
end;

TForm7 = class(TForm)
   CHButton1: TCHButton;
   RzPanel1: TRzPanel;
   RzBmpButton1: TRzBmpButton;
   procedure CHButton1Click(Sender: TObject);
   procedure RzBmpButton1Click(Sender: TObject);
   procedure RzPanel1MouseDown(Sender: TObject; Button: TMouseButton;
     Shift: TShiftState; X, Y: Integer);
   procedure RzPanel1MouseUp(Sender: TObject; Button: TMouseButton;
     Shift: TShiftState; X, Y: Integer);
   procedure RzPanel1MouseEnter(Sender: TObject);
   procedure RzBmpButton1MouseEnter(Sender: TObject);
   procedure FormMouseEnter(Sender: TObject);
   procedure FormCreate(Sender: TObject);
private
  { Private declarations }
public
  { Public declarations }
end;

var
  Form7: TForm7;
  MyCloseButton : TButtonClose;

implementation

{$R *.dfm}

// constructor for on the fly component created
constructor TButtonClose.Create(AOwner: TComponent);
begin
   inherited Create(AOwner);

   // Set Events for the component
   Self.OnMouseEnter := Self.CloseButtonMouseEnter;
   Self.OnMouseDown := Self.MouseDown;
   Self.OnMouseUp := Self.MouseUp;
   Self.Height := 25;

   // Close button on top panel Button
   // Inherited from Raize Bitmap Button
   CloseButton := TRzBmpButton.Create(self);
   // Set On Click Event for Close Button
   CloseButton.OnClick := Self.CloseButtonClick;
   // Place Close Button on Panel Button
   CloseButton.Parent := self;
   CloseButton.Left := 10;
   CloseButton.Top := 5;
   CloseButton.Visible := False;
   // Setting the image for the button
   CloseButton.Bitmaps.Up.LoadFromFile(ExtractFilePath(Application.ExeName)+'\close.bmp');
end;

procedure TButtonClose.CloseButtonClick(Sender: TObject);
begin
   // Free the parent (Panel Button)
   TControl(Sender).Parent.Free;
end;

procedure TButtonClose.CloseButtonMouseEnter(Sender: TObject);
begin
   // Show the Close button
   CloseButton.Visible := True;
end;

procedure TButtonClose.MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
   // Emulate Button down state, since it is panel
   TRzPanel(Sender).BorderOuter := fsLowered;
end;

procedure TButtonClose.MouseUp(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
begin
   // Emulate Button up state, since it is panel
   TRzPanel(Sender).BorderOuter := fsRaised;
end;

destructor TButtonClose.Destroy;
begin
   inherited Destroy;
end;

procedure TForm7.FormCreate(Sender: TObject);
begin
   // Create Panel Button on the fly
   MyCloseButton := TButtonClose.Create(self);
   MyCloseButton.Caption := 'My Button';
   MyCloseButton.Left := 10;
   MyCloseButton.Top := 10;
   // Don't forget to place component on the form
   MyCloseButton.Parent := self;
end;

procedure TForm7.FormMouseEnter(Sender: TObject);
begin
   if Assigned(RzBmpButton1) then
      RzBmpButton1.Visible := False;

   // Hide when mouse leave the button
   // Check first if myCloseButton Assigned or not before set visible property
   if Assigned(MyCloseButton.CloseButton) then
      MyCloseButton.CloseButton.Visible := False;
end;

procedure TForm7.RzBmpButton1Click(Sender: TObject);
begin
   TControl(Sender).Parent.Free;
end;

procedure TForm7.RzBmpButton1MouseEnter(Sender: TObject);
begin
   RzBmpButton1.Visible := True;
end;

procedure TForm7.RzPanel1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  TRzPanel(Sender).BorderOuter := fsLowered;
end;

procedure TForm7.RzPanel1MouseEnter(Sender: TObject);
begin
   RzBmpButton1.Visible := True;
end;

procedure TForm7.RzPanel1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
   TRzPanel(Sender).BorderOuter := fsRaised;
end;

procedure TForm7.CHButton1Click(Sender: TObject);
begin
   FreeAndNil(Sender);
end;

end.
Ragamuffin answered 3/4, 2013 at 7:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.