【问题标题】:Creating components at runtime - Delphi在运行时创建组件 - Delphi
【发布时间】:2010-11-03 13:43:30
【问题描述】:

如何在运行时创建组件然后使用它(更改属性等)?

【问题讨论】:

    标签: delphi runtime components


    【解决方案1】:

    这是如何在 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.
    

    【讨论】:

      【解决方案2】:

      我只想在动态添加控件时添加它... 按照@Despatcher 在 中的建议,将它们添加到对象列表(TObjectList)中是个好主意。

      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;
      

      【讨论】:

        【解决方案3】:

        这取决于它是可视组件还是非可视组件。原理是一样的,但是每种组件都有一些额外的注意事项。

        对于非视觉组件

        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 最适合,但也可以看看 cmets 对这个答案的建议)

        如何分配事件处理程序:

        您必须创建一个事件处理程序方法并将其分配给事件属性。

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

        【讨论】:

        • 但是如果我不确定要创建多少个组件,例如如果这取决于用户的决定。那么如何动态声明组件呢?
        • 作为所有者传递nil还是其他组件的区别与组件是否可见无关,仅与对象的生命周期有关。可以像在第二个 sn-p 中一样创建未在同一方法中释放的不可见组件,并由所有者自动释放。
        • 当然你是对的,但在我的例子中,我明确删除了它,所以没有必要。
        • 我的意思是我不明白“它是如何取决于它是视觉组件还是非视觉组件”。它没有。您的两个 sn-ps 仅在所创建组件的预期生命周期上有所不同。
        • 并非所有“组件”都是“控件”。这些组件既没有 parent 属性,也没有 left/top/width/height 属性之一。但是对于可视组件,有必要将这些属性设置为非可视组件,而你不能。因此,我认为这种区别是合理的。
        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          如果您在 Group Boxes/Page Controls/Etc... 中嵌套 win 控件,我认为让父组框也成为所有者是有益的。我注意到这样做时窗口关闭时间急剧减少,而不是让所有者始终是主要窗体。

          【讨论】:

            【解决方案6】:

            某些组件会覆盖“已加载”方法。如果您在运行时创建实例,则不会自动调用此方法。当从表单文件 (DFM) 加载完成时,它将被 Delphi 调用。

            如果方法包含初始化代码,您的应用程序在运行时创建时可能会出现意外行为。在这种情况下,请检查组件编写器是否使用了此方法。

            【讨论】:

              【解决方案7】:

              非常轻松。调用创建。示例:

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

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


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

              【讨论】:

              • 需要将指针传递给元素的所有者。 TButton.Create(所有者);
              • > 需要所有者 不一定。 TButton.Create(nil);是有效代码。但是你现在需要明确地销毁它。使用 nil 所有者创建可视化组件有时很有用。
              【解决方案8】:

              但如果我不确定要创建多少个组件,例如如果这取决于用户的决定。那么如何动态声明组件呢?

              已经提出了答案 - 最简单的方法是对象(组件)列表。 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;
              

              对象列表可以包含或不包含任何视觉对象,但这会给您带来额外的开销来分类哪些项目是哪些 - 例如,如果您想要在类似面板上使用多个动态控件,则最好有相关列表。

              注意:和其他评论者一样,为了简洁起见,我可能过于简化了,但我希望你能理解这个想法。您需要一种机制来管理创建对象后的对象,而列表非常适合这些东西。

              【讨论】:

                【解决方案9】:

                为了简化运行时组件的创建过程,您可以使用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 一起使用的绝佳工具。
                • ...或者您可以在可视化编辑器中设计它,然后在 .dfm 文件中取一个峰值。文本中的内容基本上完全相同
                • 谢谢。我更喜欢自己写所有的东西(我知道这可能是重新发明轮子,但我觉得对它有更多的控制权)无论如何,GExpert 工具似乎不会在纯代码中改变,这听起来不错。再次感谢您的建议。
                猜你喜欢
                • 2013-02-21
                • 2012-11-23
                • 2013-01-11
                • 1970-01-01
                • 2018-12-28
                • 2010-12-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多