Как я могу узнать, кто инициировал действие в Delphi?

Когда возникает событие TAction, «Отправитель» всегда является самим действием. Обычно это наиболее полезно, но можно ли каким-то образом узнать, кто инициировал событие OnExecute действия?

Пример

Допустим, у вас есть форма со следующим:

  • 2 кнопки, называемые Button1 и Button2
  • 1 сообщение под названием actDoStuff

Обеим кнопкам назначено одно и то же действие. Можно ли показать, какую кнопку я нажал?

Example.dfm

object Form1: TForm1
  object Button1: TButton
    Action = actDoStuff
  end
  object Button2: TButton
    Action = actDoStuff
    Left = 100
  end
  object actDoStuff: TAction
    Caption = 'Do Stuff'
    OnExecute = actDoStuffExecute
  end
end

Example.pas

unit Example;
interface
uses Windows, Classes, Forms, Dialogs, Controls, ActnList, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    actDoStuff: TAction;
    procedure actDoStuffExecute(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation    
{$R *.dfm}

procedure TForm1.actDoStuffExecute(Sender: TObject);
begin
  ShowMessage('Button X was clicked');
end;

end.

Единственное решение, которое я вижу на данный момент, - это не использовать свойство action кнопок, а иметь обработчик событий для каждой кнопки и вызывать оттуда actDoStuffExecute (), но это в первую очередь противоречит цели использования действий.

Я также не хочу иметь отдельное действие для каждого отдельного элемента управления. Приведенный выше пример представляет собой упрощенную версию проблемы, с которой я столкнулся. У меня есть меню с переменным количеством пунктов меню (имен файлов), и каждый пункт меню в основном должен делать то же самое, за исключением загрузки другого файла. Было бы немного глупо иметь действия для каждого пункта меню.


person Wouter van Nifterick    schedule 21.07.2010    source источник
comment
Видите этот параметр Sender: TObject? ... Он предварительно заполнен для вас ... Попробуйте взглянуть на Sender внутри вашей функции.   -  person Fosco    schedule 21.07.2010
comment
Да, но в приведенном выше примере actDoStuff будет отправителем. Я хочу знать, была ли нажата кнопка button1 или button2.   -  person Wouter van Nifterick    schedule 21.07.2010


Ответы (7)


Попробуйте использовать ActionComponent имущество:

Сохраняет клиентский компонент, который вызвал выполнение этого действия.

Используйте ActionComponent, чтобы определить, какой клиентский компонент вызвал выполнение этого действия. Например, проверьте ActionComponent из OnExecute, если вам нужно знать, какое действие пользователя вызвало это действие.

Когда пользователь щелкает клиентский элемент управления, этот клиент устанавливает ActionComponent перед вызовом действия Execute. После выполнения действия оно сбрасывает ActionComponent в ноль.

Например:

  ShowMessage( (Sender as TAction).ActionComponent.Name );

Используя это, я получаю «Button1» и «Button2», когда нажимаю первую и вторую кнопку соответственно.

person Ville Krumlinde    schedule 21.07.2010
comment
Осторожно: что происходит, когда действие запускается сочетанием клавиш? - person Rob Kennedy; 22.07.2010
comment
Отлично, именно то, что я искал! - person Wouter van Nifterick; 26.07.2010

Знание того, какая кнопка инициировала действие, противоречит точке использования действий - действие может быть инициировано нажатием кнопки, щелчком по меню или любым другим действием пользователя. Действия существуют для унификации управления состоянием включения / выключения и обработки щелчков между кнопками и меню.

Если вы хотите знать, какая кнопка активировала действие, потому что вы хотите выполнить несколько иную операцию или по-другому «придать вкусу» операцию, то, возможно, TAction не является правильным решением для того, что вы хотите сделать.

person dthorpe    schedule 21.07.2010
comment
Вы предполагаете, что TAction - неправильное решение, но при этом не предлагаете альтернативы. Глобальное отключение действий - это самый простой способ отключить функции программы, когда они не могут выполняться, и довольно часто вам нужны немного разные функции в разных окнах, поэтому реализация собственного решения не очень практична вместо использования готового решения, такого как TAction. Отключение функции программы путем отключения действия часто требует минимального обслуживания в дальнейшем (она автоматически отключает все соответствующие меню, панели инструментов и т. Д. Во всех окнах, которые ее используют). - person Coder12345; 08.05.2013
comment
Я большой сторонник TAction. Я предполагаю, что дизайн, который стремится изменить поведение события в зависимости от того, какой элемент пользовательского интерфейса использовался для запуска события, является ошибочным. Используйте TActions для единообразной обработки действий независимо от триггера. Если вам действительно нужно, чтобы при нажатии кнопки вместо пункта меню происходило что-то другое, создайте другое действие. - person dthorpe; 08.05.2013

Вместо действий просто используйте событие щелчка. Настройте все кнопки на использование одного и того же обработчика событий. В идеале НЕ называть в честь первой кнопки (ее можно переименовать).

Вот код:

Procedure TMyForm.DestinationButtonClickHandlerThing(Sender: TObject); 
begin
  if Sender = Btn_ViewIt then
  begin
    // View It
  end
  else if Sender = Btn_FaxIt then
  begin
    // Fax It
  end
  else if Sender = Btn_ScrapIt then
  begin
    // Scrap It
  end
  else 
    ....   // error
   ...
end;
person Chris Thornton    schedule 21.07.2010
comment
Использование обработчиков событий OnClick, даже одного обработчика для нескольких кнопок, означает потерю возможности включать / отключать их все в одном месте (обработчике onupdate действия или списка действий). Это если вы не объедините их с действием. Однако назначение действия перезапишет обработчик onclick, поэтому вам придется сбрасывать его для всех задействованных кнопок и, возможно, делать это каждый раз, когда вы что-то меняете в действии. - person Marjan Venema; 22.07.2010
comment
Конечно, это правильный ответ! Вот почему Sender здесь. - person Vassilis; 02.07.2015

Бывают ситуации, когда одно и то же действие должно применяться к аналогичным элементам управления. Проблема с

ShowMessage( (Sender as TAction).ActionComponent.Name );

заключается в том, что когда действие вызывается всплывающим меню say, вы получаете имя всплывающего меню. Вы можете использовать:

procedure TMyForm.actMyActionExecute(Sender: TObject);
var
  LMyControl: TMyControl;
begin
  if Screen.ActiveControl.Name = 'MyControl1' then
    LMyControl = Sender as TMyControl
  else
    Exit;
  // Use the local variable for whatever needed
end;
person user3210861    schedule 31.01.2014
comment
Проблема здесь не в том, чтобы обсуждать плюсы и минусы ShowMessage ((Sender as TAction) .ActionComponent.Name);, а в том, чтобы найти способ определить, кто инициировал TAction. Для этого свойство TAction.ActionComponent является идеальным кандидатом. - person Didier Cabalé; 25.07.2019

У меня есть несколько панелей, и я хочу позволить пользователю щелкнуть правой кнопкой мыши любую из этих панелей и выполнить действие удаления файла. Итак, у меня есть одно всплывающее меню, связанное со всеми этими панелями. Вот как я узнаю, какая панель была нажата правой кнопкой мыши:

(Примечание: я поместил много комментариев, чтобы четко объяснить, как это работает. Но если вам это не нравится, вы можете сжать код до 2 строк (см. Вторую процедуру)).

Итак, если этому всплывающему меню назначены действия:

procedure Tfrm.actDelExecute(Sender: TObject);
VAR
  PopMenu: TPopupMenu;
  MenuItem: TMenuItem;
  PopupComponent: TComponent;
begin
 { Find the menuitem associated to this action }
 MenuItem:= TAction(Sender).ActionComponent as TMenuItem;  { This will crash and burn if we call this from a pop-up menu, not from an action! But we always use actions, so.... }

 { Was this action called by keyboard shortcut? Note: in theory there should be no keyboard shortcuts for this action if the action can be applyed to multiple panels. We can call this action ONLY by selecting (right click) a panel! }
 if MenuItem = NIL then
  begin
   MsgError('This action should not be called by keyboard shortcuts!');
   EXIT;
  end;

 { Find to which pop-up menu this menuitem belongs to }
 PopMenu:= (MenuItem.GetParentMenu as TPopupMenu);

 { Find on which component the user right clicks }
 PopupComponent := PopMenu.PopupComponent;

 { Finally, access that component }
 (PopupComponent as TMonFrame).Delete(FALSE);
end;

Если у вас есть только простое всплывающее меню (действия не назначены):

procedure Tfrm.actDelHddExecute(Sender: TObject);
VAR PopupComponent: TComponent;
begin
 PopupComponent := ((Sender as TMenuItem).GetParentMenu as TPopupMenu).PopupComponent;
 (PopupComponent as TMonFrame).Delete(TRUE);
end;

Вы можете поместить весь этот код в одну функцию, которая возвращает TPanel, и вызвать ее так:

procedure Tfrm.actDelWallExecute(Sender: TObject);
begin
 if GetPanelFromPopUp(Sender) <> NIL
 then GetPanelFromPopUp(Sender).Delete(FALSE);
end;
person Z80    schedule 18.07.2020

Хорошо, а пока я думаю, что нашел работоспособное решение ..

Я могу сделать так, чтобы все элементы управления использовали одно и то же действие; Мне просто нужно переопределить их обработчик событий OnClick, и мне просто нужен один обработчик для всех из них.

Мне все еще интересно узнать, можно ли узнать, какой элемент управления вызвал действие, но для моего текущего приложения я использую решение, подобное приведенному ниже коду:

unit Example;

interface

uses
  Windows, Classes, Forms, Dialogs, Controls, ActnList, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    actDoStuff: TAction;
    procedure actDoStuffExecute(Sender: TObject);
    procedure ButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.actDoStuffExecute(Sender: TObject);
begin
  ShowMessage('Button '+TControl(Sender).Name +' was clicked')
end;

procedure TForm1.ButtonClick(Sender: TObject);
begin
  actDoStuffExecute(Sender)
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.OnClick := ButtonClick;
  Button2.OnClick := ButtonClick
end;

end.
person Wouter van Nifterick    schedule 21.07.2010
comment
Если вы пишете обработчики событий OnClick в кнопках, полностью отключите от действия связь. Шутки в сторону! - person Warren P; 22.07.2010
comment
Ссылка с действием нужна, если вы хотите включить / отключить все кнопки в одном месте (обработчик onupdate действия или списка действий). Вы делаете, однако должны быть осторожны, так как обработчик onclick может быть перезаписан при каждом обновлении действия. - person Marjan Venema; 22.07.2010

установите тег кнопок как 1, 2, ... и т. д., а затем:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.OnClick := ButtonClick;
  Button2.OnClick := ButtonClick;
end;

procedure TForm1.ButtonClick(Sender: TObject);
begin
  if Sender is TButton then
  begin
    Caption := 'Button: ' + IntToStr(TButton(Sender).Tag);
  end;  
end;
person ioan ghip    schedule 21.07.2010
comment
Суть этого вопроса - как узнать, кто инициировал TAction, а не как узнать отправителя TNotifyEvent, что совершенно очевидно. - person Didier Cabalé; 25.07.2019