使用frame2访问frame1的组件,frame2在frame1上,在delphi中,动态创建帧

时间:2011-12-28 23:32:18

标签: delphi components communication frames

最初的问题必须是如何首先访问组件,但是我设法弄明白了。我刚刚学习德尔福,所以我很容易出现愚蠢和明显的问题。我也处在一个阶段,当时我实际上并没有写任何有用的东西,只是搞乱了随机的东西,看看它是如何工作的,也许是学到了什么。

文字墙传入,我想解释我现在正在探索的内容......

基本上我有一个带有button1的form1,按下它会创建一个frame2,frame2有一个button2,按下button2会在frame2中创建一个frame3(它是frame3的父级和所有者)。每个框架都有另一个freeandnil按钮。按下每个按钮1/2/3后,它将被禁用以防止创建多个实例。我最初的问题是,在使用freeandnil按钮之后,我无法访问前一帧上的按钮(它适用于表单,form1.button1.enabled:=true可以在frame2中正常工作)被禁用以便重新启用它({我认为,在frame3中{1}}会创建一个访问冲突。

假设我将来要写一些需要这种沟通的东西?所以我在每个框架上添加了一个编辑框,另一个按钮用于更改编辑框文本,这是我当前的工作解决方案:

frame2.button1.enabled:=true

procedure TFrame2.Button3Click(Sender: TObject);
var i,z:integer;
begin
for i := 0 to ComponentCount - 1 do
  if components[i] is tframe3 then
    for z := 0 to (components[i] as tframe3).ComponentCount - 1 do
      if (components[i] as tframe3).Components[z] is TEdit then
         ((components[i] as tframe3).Components[z] as TEdit).Text:='ping';
end;

如果我有一堆编辑框或任何地狱,我想我可以使用Tag属性来识别它们,但是,这个计算和传递procedure TFrame3.Button3Click(Sender: TObject); var i:integer; begin for i := 0 to parent.ComponentCount-1 do if parent.components[i] is TEdit then (parent.Components[i] as TEdit).Text:='pong'; end; 的组件对我来说看起来并不合适或有效

我现在的问题是:能以更好的方式完成吗?并且有人可以提供为什么我无法以愚蠢的方式从“子框架”访问“父框架”组件的原因(即:框架3内的something AS something)?

1 个答案:

答案 0 :(得分:4)

有几点:

  • 控件/组件通常设置为控制其生命周期的表单/框架所拥有的,并且是显示它们的控件的父级。因此,当您创建TEdit以坐在TForm上的TPanel上时,您可以将TForm设置为所有者,将TPanel设置为TEdit的父级。对于框架,你也是这样做的:框架是它上面所有控件的所有者,控件是框架上任何容器的父级(可以是框架本身),并因此可以显示(绘制)它。

  • 当您遍历控件的子项时,迭代组件使用所有者关系;迭代控件使用父关系。因此,如果要对控件进行迭代,那么上面的代码已经做得少了很多。

  • 当然,你可以在" dumb"中引用其他控件。方式,但你必须提供访问方法。如果你想要其他人(不仅仅是子框架),你至少必须声明一个方法来检索该控件。许多方法可以做到这一点。一个是"问"您需要时的表单(使用表单上的方法),或者在创建表单时让表单在框架上设置属性...

  • 避免访问表单和框架的已发布属性。它们主要用于Delphi的流媒体系统。当您使用上述方法将应用程序绑定在一起时,它很快就会成为一个令人难以置信的混乱......

示例即将到来(今晚某个时候),我需要一些时间来解释,我还有工作要做......


以下示例仅处理帧之间的乒乓比赛。从其自己的事件处理程序中释放任何形式或框架并不是一个好主意。使用Release方法,因为它可以防止表单/框架在释放后处理消息。 (顺便说一句关于这个问题的很多问题)。此外,当你从一个自己的按钮释放一个框架时,你需要注意创建它的框架有可能使它可能保持在那个框架上的引用为零,否则你会为自己设置一些有趣的东西。调试混乱。查看"通知"和#34; NotifyControls"以及发送给其所有者/父级的表单和框架的自动通知,以便这些可以从其组件/控件集合中删除控件。在您的示例中,如果您要从自己的#34; FreeAndNil"中释放Frame3。按钮的OnClick事件处理程序,你必须确保Frame2响应删除(我认为)通知消息,并且没有任何对Frame3的引用(除了那些已经自动清除的引用)在组件/控件集合中。)

现在,乒乓球比赛。有几种方法可以解决这个问题。

方法1

第一种方法是你已经尝试过对另一帧组件的循环。虽然它肯定是一种避免不得不使用"另一个框架,它很麻烦而且不够简洁。此外,当您在表单/框架上获得更多控件时,您必须添加对名称的检查以了解您具有正确的TEdit。然后你也可以直接使用这个名字,特别是当一个帧已经在其uses子句中有另一个帧时,因为它正在创建它。

// PingFrame (your Frame2)
uses
  ...
  Pong_fr;

type
  TPingFrame = class(TFrame)
    ...
    procedure CreateChildBtnClick(Sender: TObject);
    procedure PingPongBtnClick(Sender: TObject);
  private
    FPong: TPongFrame; // This is the "extra" reference you need to nil when
                       // freeing the TPongFrame from one of its own event handlers.
    ...
  end;

procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
  CreateChildBtn.Enabled := False;
  FPong := TPongFrame.Create(Self);
  FPong.Parent := ContainerPnl;
  FPong.Align := alClient;
end;

procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
  if Assigned(FPong) then
    FPong.Edit1.Text := 'Ping';
end;

另一方面:

// PongFrame (your Frame3)
type
  TPongFrame = class(TFrame)
    ...
    procedure PingPongBtnClick(Sender: TObject);
  end;

implementation

uses
  Ping_fr;

procedure TPongFrame.PingPong1BtnClick(Sender: TObject);
begin
  (Owner as TPingFrame).Edit1.Text := 'Pong called';
end;

这种方法看起来很漂亮,但它有缺点:

  • 您需要使用包含TPingFrame的单位(本例中为Ping_fr)。我猜也不错,但是......因为Ping_fr已经使用Pong_fr(持有TPongFrame的单位),你不能在接口部分使用Pong_fr使用Ping_fr,并且在接口部分使用Ping_fr也可以使用Pong_fr。这样做会导致Delphi因循环引用而抛出错误。这可以通过将其中一个用途移动到实现部分来解决(如在Pong_fr单元中使用Ping_fr的示例中所做的那样。
  • 更大的缺点是现在两个框架之间存在非常紧密的耦合。您不能将TEdit更改为任何一种控件(除非碰巧具有Text属性),而不必更改其他单元中的代码。这种紧密耦合是引起严重头痛的一个原因,尝试避免它是一种好习惯。

方法2

减少帧之间耦合并允许每个帧在其认为合适的情况下更改控件的一种方法是不直接使用另一个窗体/框架的控件。为此,您可以在另一个可以调用的每个帧上声明一个方法。每种方法都会更新自己的控件。从OnClick事件处理程序,您不再直接访问其他框架的控件,但您调用该方法

type
  TPingFrame = class(TFrame)
    ...
  public
    procedure Ping;
  end;

implementation

procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
  if Assigned(FPong) then
    FPong.Ping;
end;

procedure TPingFrame.Ping;
begin
  Edit1.Text := 'Someone pinged me';
end;

另一方面:

type
  TPongFrame = class(TFrame)
    ...
  public
    procedure Ping;
  end;

implementation

procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
  (Owner as TPingFrame).Ping;
end;

procedure TPongFrame.Ping;
begin
  Edit1.Text := 'Someone pinged me';
end;

这比方法1更好,因为它允许两个帧都改变他们的控制而不必担心"局外人"引用它们,但仍然有循环引用的缺点,只能解决"#34;通过移动一个"使用"到实施部分。

一个好的做法是尝试完全避免循环引用,而不是通过将单元移动到实现部分的使用子句来修补它们。我使用的经验法则是:

任何表单/框架都可以知道并使用它实例化的表单/框架的公共接口(尽管它应该避免"默认可见性"该接口的一部分中的控件),但是没有表单/框架不应该知道创建它的特定表单/框架。

方法3

实现这一目标(有很多)的一种方法是使用事件,就像TButton的OnClick事件一样。

在TPongFrame方面你可以删除Ping_fr的使用。你不再需要它了。

然后你需要声明一个属性及它引用的字段,并声明一个方法来触发事件。

type
  TPongFrame = class(TFrame)
  private
    FOnPingPongClicked: TNotifyEvent;
  protected
    procedure DoPingPongClicked;
  public
    property OnPingPongClicked: TNotifyEvent 
      read FOnPingPongClicked write FOnPingPongClicked;
  end;

Ping方法保持不变并且其实现不变。 PingPongBtnClick事件处理程序也会保留,但它的实现现在变为:

procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
  DoPingPongClicked;
end;

procedure TPongFrame.DoPingPongClicked;
begin
  if Assigned(FOnPingPongClicked) then
    FOnPingPongClicked(Self);
end;

您可以直接在此处输入DoPingPongClicked的代码,但在单独的方法中触发事件是一种很好的做法。如果您有一个可以从代码的多个部分触发的事件,它可以避免重复完全相同的代码。它还允许后代(当你得到那些)覆盖"射击"方法(你必须在祖先中将其标记为虚拟)并在事件被触发时执行特定的操作。

在TPingFrame方面,您需要为新事件编写处理程序:

type
  TPingFrame = class(TFrame)
    ...
  protected
    procedure HandleOnPingPongClicked(Sender: TObject);

请注意,此处理程序的签名必须是TNotifyEvent指定的签名。当然,您需要确保在TPongFrame中触发事件时调用此事件处理程序:

procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
  CreateChildBtn.Enabled := False;
  FPong := TPongFrame.Create(Self);
  FPong.Parent := ContainerPnl;
  FPong.Align := alClient;
  // Listen to event fired by PongFrame whenever it's PingPingBtn is clicked
  FPong.OnPingPongClicked := HandleOnPingPongClicked;
end;

在处理程序代码中,您可以执行您需要执行的操作。它可以是通用的:

procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
begin
  Edit1.Text := 'OnPingPongClicked event was fired';
end;

当然,您也可以使用事件传递对触发事件的实例的引用这一事实:

procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
var
  Pong: TPongFrame;
begin
  // This checks that Sender actually is a TPongFrame and throws an exception if not
  Pong := Sender as TPongFrame; 

  // Use properties from the Pong instance that was passed in
  Edit1.Text := 'OnPingPongClicked event was fired by ' + Pong.Name;
end;

享受!