用frame2访问frame1的组件,当frame2在frame1上时,在delphi中,帧是动态创建的.< /



最初的问题必须是如何访问组件,但是我设法弄清楚了。我刚刚学习Delphi,所以我很容易提出愚蠢和明显的问题。我也处于这样一个阶段,我实际上并没有写任何有用的东西,只是随意摆弄一些东西,看看它是如何工作的,也许能学到一些东西。

文本墙传入,我想解释我正在探索的时刻…

基本上我有一个带有button1的form1,按下它创建一个frame2,那个frame2有一个button2,按下button2在frame2中创建一个frame3(它是frame3的父节点和所有者)。每一帧都有另一个自由和nil按钮。在按下1/2/3按钮时,它将被禁用,以防止创建多个实例。我原来的问题是,在使用freeandnil-button后,我无法访问前一帧上的按钮(它对表单很好,form1.button1.enabled:=true从frame2内工作得很好),为了重新启用它而被禁用(我认为,从frame3内创建访问违反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;

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;

如果我有一堆编辑框或任何地狱,我想我可以使用标签属性来识别它们,然而,这么多的组件计数和传递something AS something对我来说并不真正正确或有效。

我现在的问题是:能有更好的方法吗?有人能提供为什么我不能从"子框架"以一种愚蠢的方式访问"父框架"组件的原因(即:frame2.button1.enabled:=true从frame3内)?

几点说明:

  • 控件/组件通常被设置为由控制其生命周期的表单/框架拥有,并且父级为显示它们的控件。因此,当您在TForm的TPanel上创建一个TEdit时,您将TForm设置为所有者,TPanel设置为TEdit的父节点。对于框架,您可以做同样的事情:框架是其上所有控件的所有者,控件的父级是框架上的任何容器(可以是框架本身)所持有的,并且因此负责显示(绘制)它。

  • 当你遍历控件的子元素时,遍历组件使用Owner关系;在控件上迭代使用Parent关系。因此,如果要在控件上迭代,上面的代码所做的工作就会少得多。

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

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

即将到来的例子(今晚某个时候),它将花费我一点时间来解释,我有工作要做…


下面的例子只处理两帧之间的乒乓球比赛。从它自己的事件处理程序中释放任何表单或框架都不是一个好主意。使用Release方法,因为它可以防止表单/框架在被释放后处理消息。(顺便说一下,关于这个有很多问题)。此外,当您确实从它自己的一个按钮释放一个帧时,您需要注意创建它的帧有机会将它可能持有的对该帧的引用归零,否则您将为自己设置一些有趣的调试混乱。查看"Notification"one_answers"NotifyControls"以及发送给其所有者/父类的表单和框架的自动通知,以便这些控件可以从其组件/控件集合中删除控件。在你的例子中,如果你要从自己的"FreeAndNil"按钮的OnClick事件处理程序中释放Frame3,你必须确保Frame2响应删除(我认为)通知消息和nil的任何引用,它持有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(保存tongframe的单元),所以不能让Ping_fr在接口部分使用Pong_fr,而让Pong_fr在接口部分使用Ping_fr。这样做会使Delphi因为循环引用而抛出错误。这可以通过将其中一个用法移到实现部分来解决(就像在Ping_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好,因为它允许两个帧改变它们的控件,而不必担心"外部"引用它们,但仍然有循环引用的缺点,只能通过将一个"使用"移动到实现部分来"解决"。

这是一个很好的做法,尽量避免循环引用,而不是通过将单元移动到实现节的uses子句来修补它们。我使用的经验法则是:

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

3

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

在tongframe方面,您可以删除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中的代码直接放在这里,但是在单独的方法中触发事件是一种很好的做法。如果您有一个可以从代码的多个部分触发的事件,它可以避免重复完全相同的代码。它还允许后代(当你得到那些)覆盖"firing"方法(你必须在祖先中将其标记为virtual),并在事件被触发的所有时间做一些特定的事情。

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

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

注意这个处理程序的签名需要是TNotifyEvent指定的。当然,你需要确保这个事件处理程序在tongframe:

中触发事件时被调用
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;

享受吧!

最新更新