运行时创建组件 - Delphi

22

如何在运行时创建组件并对其进行操作(更改属性等)?

9个回答

74

这要看它是可视或不可视组件。原则是一样的,但每种组件都有一些额外的考虑因素。

对于非可视组件

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

对于可视化组件:

本质上,可视化组件的创建方式与非可视化组件相同。但是,您需要设置一些额外的属性来使它们可见。

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;

对上面的代码进行一些解释:

  • 通过设置组件的所有者(构造函数参数),该组件在拥有它的表单被销毁时也会被销毁。
  • 设置Parent属性可以使组件可见。如果忘记设置,您的组件将不会显示出来。(这很容易被忽略)

如果你想要多个组件,你可以用同样的方法在一个循环中创建它们:

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;
这将在表单的左边界添加10个按钮。如果您想以后修改这些按钮,可以将它们存储在列表中。(TComponentList最适合,但还应看看本答案评论中的提议)。
如何分配事件处理程序:
您需要创建一个事件处理程序方法并将其分配给事件属性。
procedure TForm1.MyButtonClick(Sender: TObject);
var
  Button: TButton;
begin
  Button := Sender as TButton; 
  ShowMessage(Button.Caption + ' clicked');
end;

B := TButton.Create;
//...
B.OnClick := MyButtonClick;

1
但是如果我不确定要创建多少组件,例如它取决于用户的决定。那么我该如何动态声明组件呢? - Lukáš Neoproud
2
我的意思是我不明白“它取决于它是可视还是非可视组件”的含义。实际上并不是这样的。你的两个代码片段只是在所创建组件的预期生命周期上有所不同。 - mghie
1
并非所有的“组件”都是“控件”。这些组件既没有父属性,也没有left/top/width/height属性之一。但对于可视化组件,设置这些属性是必要的,因为对于非可视化组件,您只能不设置它们。正因为如此,我认为区分是有道理的。 - Daniel Rikowski
@LuckyNeo:你需要另一种引用组件的方式。DR建议使用TComponentList是其中一种方法。事件处理程序的Sender参数也可能很有用。如果它是一组固定的可能控件,你可以声明所有这些控件,但选择性地将Visible属性设置为false。 - Gerry Coll
希望有人能帮我解决这个问题。我正在运行时创建按钮,并使用此代码分配事件。我在运行时创建许多不同的“按钮组”,如何向此过程发送一个值,以便根据我创建的“组”分配另一个过程到事件处理程序?例如:如果组1中的过程是X,则事件中的组2过程为Y。谢谢。 - Edgar Holguin
显示剩余5条评论

27

为了简化运行时组件创建过程,您可以使用GExperts

  1. 通过可视化方式创建一个或多个组件,并设置它们的属性。
  2. 选择一个或多个组件并执行GExperts中的“Components to Code”功能。
  3. 将生成的代码粘贴到您的应用程序中。
  4. 从可视化表单设计器中删除组件。

示例(使用此方法生成的TButton创建代码):

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;

太棒了!这正是我要建议的。GExperts 是与 Delphi 配合使用的绝佳工具。 - Wim ten Brink
...或者你可以在可视化编辑器中设计它,然后查看 .dfm 文件。基本上,文本中有完全相同的内容。 - Earlz
谢谢。我更喜欢自己编写所有的东西(我知道这可能是重复造轮子,但我感觉更有控制力)。无论如何,GExpert工具似乎不会在纯代码中进行更改,这听起来很好。再次感谢您的建议。 - QMaster

4

我想补充一点,当动态添加控件时,建议将它们添加到一个对象列表(TObjectList)中,如@Despatcher在<1>中所建议的那样。

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;

您需要将Unit 'Contnrs'添加到您的Uses列表中。

即System.Contnrs.pas基本容器单元

您可以拥有多个对象列表。

我建议为您使用的每种控件类型使用TObjectList,例如:

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

这样可以让您轻松地操作/管理每个控件,因为您将知道它是哪种类型的控件,例如。
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;

这样你就可以使用该控件的方法和属性了。不要忘记创建TObjectLists,可以在表单创建事件中进行...

checkBoxCntrlsList := TObjectList.Create;
comboboxCntrlsList := TObjectList.Create;

1
但是如果我不确定要创建多少个组件,例如它取决于用户的决定。那么我该如何动态声明组件呢?
答案已经被提出 - 最简单的方法是使用对象(组件)列表。TObjectList是最简单易用的(在contnrs单元中)。列表非常棒!
  In Form1 Public
  MyList: TObjectList;
  procedure AnyButtonClick(Sender: TObject); 

//你可以更加复杂地声明TNotifyevents并将其分配,但让我们保持简单:)

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;

一个对象列表可以包含任何可视或不可视的对象,但这会增加额外的开销,需要排序哪些项目是哪些 - 如果您想在类似面板上拥有多个动态控件,则最好拥有相关列表。

注意:像其他评论者一样,我可能为了简洁而过于简化,但我希望您能理解。您需要一种机制来管理创建后的对象,而列表非常适合处理此类事情。


1
一些组件会覆盖 'Loaded' 方法。如果您在运行时创建一个实例,该方法将不会自动调用。当从表单文件 (DFM) 加载完成时,Delphi 将调用它。
如果该方法包含初始化代码,你的应用程序在运行时创建时可能会显示意外行为。在这种情况下,请检查组件编写者是否使用了此方法。

1
如果您在GroupBox/Page Control等中嵌套Win控件,我认为将父级GroupBox也设置为所有者会很有益。我注意到这样做时窗口关闭时间显著减少,而不是将所有者始终设置为主窗体。

1

在研究“使用基于XML的模板创建Delphi表单”时,我发现了一些有用的东西,指出了RTTI并使用开放式工具API(我认为是ToolsApi.pas)。请查看该单元中的接口。


0

非常简单。调用Create函数。例如:

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

这将在运行时创建一个组件(TButton 是一个组件),并设置可见属性。


对于构造函数:如果您想自己管理内存,请传递nil。如果您希望在另一个组件销毁时也将其销毁,请传递指向另一个组件的指针。

1
需要将元素的所有者指针传递给它。TButton.Create(owner); - Artem Barger
不一定需要。TButton.Create(nil); 是有效的代码。但现在你需要显式地销毁它。使用 nil 所有者创建可视化组件有时是有用的。 - Despatcher

-2

这是一个模拟在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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接