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

5
我正在使用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将面板设置为可见或不可见?如果可以,请根据上面所示,给我提供逐步进行的方法。

提前致谢...


1
你可以将需要引用的组件添加到 TList|Container 中,然后使用你的列表|容器来访问它们。 - Whiler
4个回答

13

我不确定你的意思是什么:我不能使用字符串变量来引用它 - 例如,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;

还有什么我错过了吗?


2
不,你没有。这应该是显而易见的,但它并不是!非常感谢。 - Capfka
我在一个面板中嵌套了另一个面板,所以我首先使用这种方式找到父面板,然后从父面板调用FindComponent函数,这样就解决了问题。谢谢。 - QMaster
我之前使用了 TPanel.Create(Application),但是它没有起作用。正确的应该是 .Create(Self)。这个改动救了我一命,非常感谢! - Soon Santos

6
你正在混淆组件名称变量名称。对于IDE创建的组件,Delphi IDE会努力保持这两者相同,但它们不一定相等。由于你是动态创建组件,因此没有变量名称,也不知道需要多少变量。但是,你仍然可以控制组件名称:只需分配组件的Name属性,然后就可以像使用任何其他组件名称一样调用FindComponent,确保每个面板实例的名称都是唯一的。
还要记住,当你不知道在编译时需要多少变量时,处理变量的方法是使用数组列表。你可以使用普通的数组,也可以使用更复杂的数据结构,如TComponentListTDictionary
最后,为了更容易地引用你创建的面板上的控件,你可以放弃面板,改用框架。你可以在IDE中可视化设计一个TFrame并为按钮命名,在运行时,你可以实例化该框架类,它将自动为你创建所有的按钮,就像实例化表单或数据模块时一样。你只需要给新框架对象命名,但该对象已经具有指向按钮的命名字段。

1
@Whiler,不需要为每个可能的Delphi版本创建单独的问题和答案。无论版本如何,这都是同一个问题。人们可以在阅读此答案时使用他们所用版本中可用的任何类。此外,我并没有说一定是现代Delphi版本附带的TDictionary类;任何人都可以编写类似字典的类,并使用它来存储对象引用。 - Rob Kennedy
谢谢Rob。非常感谢。我会看一下TFrames,因为我还有另一个应用程序的想法... :-) - Capfka

2

不应该需要创建一个次要的项目列表来在tForm实例上显示你创建的项目。

据我所知,在创建一个新面板并使用表单或其他容器代替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属性,并在上面的示例中使用它。


是的,谢谢,我一直在使用标签将面板“按顺序”保持在一起,根据存储其特征的数据表为它们分配顺序号码。效果非常好。 - Capfka

0

您可以将需要引用的组件添加到 TList|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)


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

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