gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 15:53:00 25 4
gpt4 key购买 nike

最初的问题必须是如何首先访问组件,但是我设法设法弄清楚了。我只是在学习Delphi,所以我很容易出现愚蠢和明显的问题。我还处在一个阶段,实际上我并没有在写任何有用的东西,而只是在弄乱随机的东西以了解它是如何工作的,甚至可以学习一些东西。

文字墙传来,我想解释一下我现在正在探索的东西...

基本上我有一个带有button1的form1,按下它会创建一个frame2,那个frame2有一个button2,按下button2会在frame2内创建一个frame3(它是frame3的父级和所有者)。每个框架都有另一个freeandnil按钮。按下每个按钮1/2/3时,它将被禁用以防止创建多个实例。我的原始问题是,在使用freeandnil-button之后,我无法访问上一帧的按钮(对于窗体,它工作正常,form1.button1.enabled:=true在frame2内工作正常),该按钮被禁用以便重新启用它(frame2.button1.enabled:=true我认为从frame3内部创建访问冲突。

假设我将来写一些需要这种交流的东西吗?因此,我在每个框架上添加了一个编辑框,在另一个框架上添加了一个按钮来更改编辑框文本,这是我目前的工作解决方案:

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;


如果我有一堆编辑框或其他工具,我想我可以使用Tag属性来识别它们,但是,对我来说,计数和传递 something AS something这么多组件并不真正正确或有效。

我目前的问题是:能否以更好的方式完成?有人可以提供为什么我不能以愚蠢的方式(例如:frame3中的 frame2.button1.enabled:=true)从“子框架”访问“父框架”组件的原因吗?

最佳答案

要点:


控件/组件通常设置为由控制其生命周期的窗体/框架拥有,并以显示它们的控件为父。因此,当您创建一个TEdit放在TForm的TPanel上时,您将TForm设置为所有者,并将TPanel设置为TEdit的父级。对于框架,您可以执行相同的操作:框架是框架上所有控件的所有者,控件是框架上所容纳的任何容器(可以是框架本身)的父级,因此负责显示(绘画)它。
当您迭代控件的子代时,遍历Components时会使用Owner关系。遍历Controls使用Parent关系。因此,如果要遍历控件,则上面的代码已经做的工作​​要少得多。
当然,您可以“笨拙”地引用其他控件,但必须提供访问方法。如果需要其他控件(而不仅仅是子框架),则至少必须声明一个方法来检索该控件。有很多方法可以做到这一点。一种是在需要时“询问”表单(使用表单上的方法),或者在表单创建时在框架上设置属性...
避免访问表单和框架的已发布属性。它们主要用于Delphi的流系统。当您使用上述方法将应用程序捆绑在一起时,它很快就会变成一团糟。


示例出现(今天晚上的某个时候),这将花费我一些时间来解释,我还有工作要做...



以下示例仅处理框架之间的乒乓游戏。从自己的事件处理程序之一中释放任何形式或框架并不是一个好主意。为此使用Release方法,因为它可防止表单/框架在释放后处理消息。 (顺便说一下,关于SO的很多问题)。另外,当您从其自己的按钮之一释放框架时,需要注意创建该框架的框架有机会将其持有的对该框架的引用无效,否则您将需要进行一些有趣的设置调试混乱。查看“通知”和“ NotifyControls”,以及通过表单和框架自动发送给其所有者/父母的通知,以便它们可以从其组件/控件集合中删除该控件。在您的示例中,如果要从其自己的“ FreeAndNil”按钮的OnClick事件处理程序中释放Frame3,则必须确保Frame2对删除(我认为)通知消息作出响应,并且对框架3持有的所有引用都为nil(除此以外)那些将在组件/控件集合中自动清除的文件)。

现在,乒乓球比赛。有两种方法可以解决此问题。

方法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属性),而不必同时更改另一单元中的代码。这种紧密的耦合是导致严重头痛的原因,因此,尝试避免这种情况是一种很好的做法。


方法二

减少框架之间的耦合并允许每个框架更改其控件的一种方法是不直接使用其他表单/框架的控件。为此,您在每个框架可以声明的方法上声明另一个方法。每种方法都会更新自己的控件。从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事件一样。

在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;


请享用!

关于delphi - 使用frame2访问frame1的组件,其中frame2在frame1上,在delphi中,动态创建帧,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8662472/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com