Delphi - 引用在运行时创建的组件

Posted

技术标签:

【中文标题】Delphi - 引用在运行时创建的组件【英文标题】:Delphi - Referencing Components created at Runtime 【发布时间】:2012-11-23 10:47:46 【问题描述】:

我使用的是 Delphi 5,我在运行时创建了许多面板,然后在面板上创建按钮,显然在运行时再次创建。我需要这样做,因为将来我可能需要动态创建更多面板/按钮组合。

我可以做到所有这些,但我不知道如何引用我创建的面板,因为我找不到访问面板组件名称的方法。在互联网上搜索我发现我可以使用 FindComponent 按名称查找面板组件,但我仍然不知道如何使用该名称,因为我不能使用字符串变量来引用它 - 例如StringVar := Panel.Name。我得到一个类型不匹配,TComponentName 与 String。

我在创建面板时为每个面板创建了按钮。简化后如下所示:

   With TypeQuery do begin // Create Panels
   First;
   While (not eof) do begin        // create the actual panel
      panelno := FieldByName('Product_type_id').AsInteger;
      pnl := Tpanel.Create(Self);
      pnl.name := FieldByName('PanelName').AsString;
      pnl.color := clInactiveCaption;
      pnl.parent := MainForm;
      pnl.width := 365;
      pnl.Height := 551;
      pnl.left := 434
      pnl.top := 122;
      pnl.caption := '';
      With ButtonQuery do begin
         Close;
         Parameters.parambyname('PanelID').Value := PanelNo;
         Open;
         First;
         While (not eof) and (FieldByName('Product_type_id').AsInteger = PanelNo) do begin    //put the buttons on it.
            btnName := FieldByName('ButtonName').AsString;
            BtnText := FieldByName('ButtonText').AsString;
            BtnGroup := FieldByName('Product_Group_ID').AsString;
            GrpColour := FieldByName('ButtonColour').AsString;
            btn := TColorButton.Create(Self);
            btn.Parent := pnl;
            btn.Name := BtnName;
            Btn.backcolor := HexToTColor(GrpColour);
            btn.Font.Name := 'Arial Narrow';
            btn.Font.Style := [fsBold];
            btn.Font.Size := 10;
            . . .
        end;
        . . .
     end; 
  end;

我在几个论坛(包括这个论坛)上读到,无法直接按名称引用面板。我尝试过使用组件数组,但遇到了同样的问题 - 我需要通过分配的组件名称来引用组件。

好吧,我不是枪程序员——多年来我一直使用 Delphi 来创建简单的程序,但这个程序要复杂得多。我以前从未使用过运行时组件创建。

我可以使用 FindComponent 使面板可见或不可见吗?如果是这样,鉴于我在上面向您展示的内容,您能告诉我我应该采取的小步骤吗?

提前致谢...

【问题讨论】:

您可以将需要引用的组件添加到 TList|Container...,然后使用您的 list|container 访问它们... 【参考方案1】:

我不确定你的意思是什么: “我不能使用字符串变量来引用它 - 例如 StringVar := Panel.Name。

试试这个:

procedure TForm1.FormCreate(Sender: TObject);
var
  p: TPanel;
begin
  p := TPanel.Create(Self); // create a TPanel at run-time
  p.Name := 'MyPanel'; // set a unique name
  p.Parent := Self;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  p: TPanel;
  StringVar: string;
begin
  p := FindComponent('MyPanel') as TPanel;
  if Assigned(p) then // p has reference to MyPanel
  begin
    // use that reference
    p.Caption := 'Foo';
    StringVar := p.Name;
    ShowMessage(StringVar);
  end;
end;

还是我错过了什么?

【讨论】:

不,你没有。这应该很明显,但事实并非如此!非常感谢。 我在其他面板中有一个面板,所以我首先使用这种方式找到父面板,然后从父面板调用 FindComponent 函数,这很有效。谢谢。 我正在使用 TPanel.Create(Application) 但它不起作用。它应该是 .Create(Self)。救了我的命,谢谢!【参考方案2】:

应该不需要创建您创建的项目的辅助列表以显示在 tForm 实例上。

AFAIK,每当您创建一个使用表单或其他容器代替 self 的新面板时

  pnl := Tpanel.Create(Self);

你不需要处理销毁新的 pnl,因为它是由包含“self”组件处理的。

这意味着应该有任何构造来保存父组件的子组件。

我希望您会在父对象中找到 ComponentCount 或 Components 列表或 FindComponent 方法。 假设“Self”引用的对象是一个 Tform。

 for i := 0 to tForm(self).ComponentCount -1 do 
   if tForm(self).Components[i] is tPanel then 
      tPanel(tForm(self).Components[i]).Caption := intToStr(i) ;

将更改应用程序中 tPanel 的所有标题。 为了区分由代码和设计器创建的面板,您可以使用每个创建的 tPanel 的 TAG 属性并在上面的示例中使用它。

【讨论】:

是的,谢谢,我一直在使用标签来保持面板“有序”,方法是根据存储面板特征的数据表为面板分配序列号。这是一种享受。跨度> 【参考方案3】:

您可以将需要引用的组件添加到 TList|Container...,然后使用您的 list|container 访问它们

var
  slPanels: TStringList;
...


 With TypeQuery do begin // Create Panels
   First;
   While (not eof) do begin        // create the actual panel
      panelno := FieldByName('Product_type_id').AsInteger;
      pnl := Tpanel.Create(Self);
      pnl.name := FieldByName('PanelName').AsString;
      slPanels.AddObject(FieldByName('PanelName').AsString, pnl);

当你需要它时:

TPanel(slPanels.Objects[slPanels.IndexOf(FieldByName('PanelName').AsString)]) ...

我不喜欢上面的代码(有更好的容器......但这应该可以完成工作:o))

【讨论】:

为什么每个人都坚持创建一个单独的列表?表单已经有一个(在 Components 数组中),可能有两个(在 Controls 数组中),父级有一个或两个(在 Components 和 Controls 数组中)。你不需要另一个。如果您需要一种对它们进行分组的方法,请使用命名约定或为 Tag 属性设置一个值。 也许是因为我可以用字符串而不是整数来访问它? ;o) 当然...您的方式,节省内存...(您是否影响 delphi R&D 使用 FMX 实现 TagString 属性?;o))) @Ken,一个专门用于存储一种控件的容器可以让事情变得更容易。例如,项目的数字索引不会因为您重新设计表单而改变。字符串索引不一定限于 Delphi 认为的有效标识符,并且它没有相同的唯一性约束。您甚至可以使用其他索引类型,例如数据库键。专用容器还可以提供更快的访问,因为它排除了所有不感兴趣的对象。此外,一个单独的列表允许将不相关的算法的父级或所有者排除在外。 @Rob:我的评论是基于最近对每篇此类帖子的回答是“创建另一个列表”。我同意它有时可以使事情变得更容易,但这并不是如何处理在运行时创建的控件的问题的自动答案。在这种情况下,这样做的好处是零。 FindControl(FieldValue) 也一样好,它避免了维护另一个列表。当存在多个现有列表时,必须有一个可行的理由来创建另一个列表。 :-)【参考方案4】:

您将 组件名称变量名称 混为一谈。对于 IDE 创建的组件,Delphi IDE 努力保持这两者相同,但它们不一定相等。您没有变量名,因为您正在动态创建组件,而且您不知道需要多少变量。但是,您仍然可以控制组件名称:只需分配组件的 Name 属性,然后您就可以像使用任何其他组件名称一样使用它,方法是调用 FindComponent。只需确保每个面板实例的名称都是唯一的。

还请记住,当您在编译时不知道需要多少个变量时,处理变量的方法是使用 arrayslists。您可以使用普通的旧数组,也可以使用更复杂的数据结构,例如 TComponentListTDictionary

最后,为了更方便地引用您正在创建的面板上的控件,您可以放弃面板而使用框架。您可以在IDE中直观地设计一个TFrame并为按钮命名,并且在运行时可以实例化框架类,它会自动为您创建所有按钮,就像您实例化表单或数据时一样模块。您只需为新框架对象命名,但该对象已经具有引用按钮的命名字段。

【讨论】:

@Whiler,不,但我们真的不需要为每个可能的 Delphi 版本提供单独的问题和答案。无论版本如何,都是同一个问题。人们可以在他们阅读此答案时碰巧使用的版本中使用他们可用的任何类。此外,我并没有说它一定是现代 Delphi 版本附带的 TDictionary 类;任何人都可以编写类似字典的类并使用它来存储对象引用。 谢谢罗伯。非常感激。我会看看我想到的另一个应用程序的 TFrames ... :-)

以上是关于Delphi - 引用在运行时创建的组件的主要内容,如果未能解决你的问题,请参考以下文章

在单独的设计时包中覆盖组件创建构造函数

delphi中,怎样设置新窗体打开时,就运行指定的SQL语句

用于包/组件开发的 Delphi 环境设置

创建安装程序时如何确保从设计时包中正确“需要”定义 Delphi 运行时包

Delphi XE2 运行时不考虑组件属性

光标位置的运行时组件