Пара баллов:
Элементы управления / Компоненты обычно устанавливаются как принадлежащие форме / фрейму, который управляет их временем жизни, и привязываются к элементу управления, который их показывает. Поэтому, когда вы создаете TEdit для размещения на TPanel в TForm, вы устанавливаете TForm в качестве владельца и TPanel в качестве родителя TEdit. С фреймами вы делаете то же самое: фрейм является владельцем всех элементов управления на нем, элементы управления связаны с любым контейнером на раме (может быть самой фреймом), который удерживается, и, таким образом, отвечает за его показ (рисование).
Когда вы перебираете дочерние элементы элемента управления, перебирая компоненты, используется отношение Владелец; перебор элементов управления использует отношение Parent. Таким образом, ваш код выше уже сделал бы намного меньше, если бы перебрал элементы управления.
Конечно, вы можете ссылаться на другие элементы управления «тупым» способом, но вам нужно будет указать метод доступа. Если вам нужны другие (не только дочерние фреймы), вам по крайней мере придется объявить метод для извлечения этого элемента управления. Много способов сделать это. Один из них заключается в том, чтобы «запрашивать» форму, когда она вам нужна (с помощью метода в форме), или задать для формы свойство для фрейма при его создании ...
Избегайте доступа к опубликованным свойствам формы и фрейма. Они в основном там для потоковой системы Delphi. Когда вы связываете свое приложение, используя описанный выше подход, оно очень скоро может превратиться в один невероятно запутанный беспорядок ...
Пример готовится (когда-нибудь этим вечером), мне понадобится немного времени, чтобы также объяснить, и у меня есть работа ...
Следующий пример касается только игры в пинг-понг между кадрами. Освобождение любой формы или фрейма от одного из его собственных обработчиков событий - не очень хорошая идея. Для этого используйте метод Release
, поскольку он не позволяет форме / фрейму обрабатывать сообщения после освобождения. (Кстати, множество вопросов по этому поводу). Кроме того, когда вы освобождаете фрейм от одной из своих собственных кнопок, вам нужно будет позаботиться о том, чтобы у созданного фрейма был шанс обнулить ссылки, которые он мог удерживать на этом фрейме, в противном случае вы настраиваете себя на некоторые интересные отладить хаос. Изучите «Уведомление» и «NotifyControls» и автоматическое уведомление по формам и фреймам, которое отправляется их владельцу / родителю, чтобы они могли удалить элемент управления из их коллекции компонентов / элементов управления. В вашем примере, если бы вы освободили Frame3 из собственного обработчика события OnClick кнопки «FreeAndNil», вы должны были бы убедиться, что Frame2 отвечает на сообщение об удалении (я думаю) и любые ссылки, которые он содержит на Frame3 (кроме те, которые уже будут очищены автоматически в коллекциях компонентов / элементов управления).
Теперь игра в пинг-понг. Есть несколько способов сделать это.
Метод 1
Первый способ - это то, что вы уже пробовали с циклом над компонентами другого фрейма. Хотя это, безусловно, способ избежать «использования» другого фрейма, он громоздок и не очень лаконичен. Кроме того, когда вы получите больше элементов управления для ваших форм / фреймов, вам нужно будет добавить проверку имени, чтобы знать, что у вас правильный TEdit. И тогда вы можете с таким же успехом использовать имя напрямую, тем более что у одного фрейма уже есть другой фрейм в предложении использования, потому что он его создает.
// 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), вы не можете использовать Ping_fr с использованием Pong_fr в разделе интерфейса и иметь Pong_fr с использованием Ping_fr в разделе интерфейса. В результате Delphi выдаст ошибку из-за циклических ссылок. Эту проблему можно решить, переместив одно из применений в раздел реализации (как это сделано в примере для использования Ping_fr в модуле Pong_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, поскольку он позволяет обоим фреймам менять свои элементы управления, не беспокоясь о «посторонних», ссылающихся на них, но все же имеет недостаток круговой ссылки, которая может быть «решена» только путем перемещения одного «использования». в разделе реализации.
Хорошей практикой является попытка вообще избегать циклических ссылок, вместо того, чтобы исправлять их, перемещая единицы в предложение о применении в разделе реализации. Эмпирическое правило, которое я использую:
Любая форма / фрейм может знать и использовать открытый интерфейс форм / фреймов, которые он создает (хотя следует избегать элементов управления в части «интерфейса по умолчанию» этого интерфейса), но никакая форма / фрейм не должна иметь какие-либо знания о конкретной форме / структуре, которая ее создала.
Метод 3
Один из способов достижения этого (их много) - использовать события, такие как событие OnClick в TButton.
На стороне 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;
Наслаждайтесь!