доступ к компонентам frame1 с frame2, с frame2 на frame1, в delphi кадры создаются динамически - PullRequest
3 голосов
/ 29 декабря 2011

Изначально вопрос должен был заключаться в том, как получить доступ к компонентам, но мне как-то удалось это выяснить.Я только учу Delphi, поэтому я склонен к тупым и очевидным вопросам.Я также на стадии, когда я на самом деле не пишу ничего полезного, а просто балуюсь случайными вещами, чтобы посмотреть, как это работает, и, возможно, чему-то научиться.

Стена входящего текста, я хочу объяснить, что яисследуя на данный момент ...

В основном у меня есть форма 1 с кнопкой 1, нажатие на нее создает рамку 2, на этой рамке 2 есть кнопка 2, нажатие кнопки 2 создает рамку 3 в рамке 2 (это родитель и владелец рамки 3).Каждый кадр имеет еще одну кнопку freeandnil.После нажатия каждой кнопки 1/2/3 она отключается, чтобы предотвратить создание нескольких экземпляров.Моя первоначальная проблема заключалась в том, что после использования кнопки freeandnil я не мог получить доступ к кнопке в предыдущем кадре (она отлично работала для форм, form1.button1.enabled:=true отлично работает из кадра2), который был отключен для его повторного включения (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, на самом деле не выглядит правильным или достаточно эффективным для меня.

MyНа данный момент возникают следующие вопросы: можно ли сделать это лучше?и кто-то может объяснить, почему я не могу получить доступ к компонентам «родительский фрейм» из «дочернего фрейма» тупым способом (то есть: frame2.button1.enabled:=true изнутри frame3)?

1 Ответ

4 голосов
/ 29 декабря 2011

Пара баллов:

  • Элементы управления / Компоненты обычно устанавливаются как принадлежащие форме / фрейму, который управляет их временем жизни, и привязываются к элементу управления, который их показывает. Поэтому, когда вы создаете 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;

Наслаждайтесь!

...