views:

1735

answers:

7

Hi,

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

A: 

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.

Tobias Langner
There is a need to pass pointer to the owner of element. TButton.Create( owner);
Artem Barger
This code doesn't compile
DR
> need for ownerNot 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.
Despatcher
+27  A: 

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;
DR
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?
LuckyNeo
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.
mghie
s/visible/visual/g
mghie
Of course you are right, but in my example I delete it explicitly so it is not really necessary.
DR
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.
mghie
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.
DR
hi DR , this would be more complete if you provide exmaples for how to assigne procedures to just created comopents ;)
avar
Done. Good idea!
DR
@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 false
Gerry
Setting the name of each dynamically created component, and later use TComponent.FindComponent() is another way of referencing them at runtime.
mghie
+13  A: 

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;
gabr
Great tip! It's exactly what I would have suggested. GExperts is a great tool to use with Delphi.
Workshop Alex
+1  A: 

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.

Despatcher
A: 

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.

mjustin
A: 

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.

Peter Turner
A: 

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.