How can I see who triggered an action in Delphi?

Viewed 12224

When a TAction event fires, the "Sender" is always the action itself. Usually that's the most useful, but is it somehow possible to find out who triggered the action's OnExecute event?

Example

Let's say you have a form with the following:

  • 2 buttons, called Button1 and Button2
  • 1 TAction called actDoStuff

The same action is assigned to both buttons. Is it possible to show which button I clicked?

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.

The only solution I see at the moment is to not use the action property of buttons, but having an eventhandler for each button, and calling actDoStuffExecute() from there, but that sort of defies the whole purpose of using actions in the first place.

I don't want to have a dedicated action for each separate control either. The example above is a simplified version of the problem that I'm facing. I have a menu with a variable number of menu items (file names), and each menu item basically has to do the same thing, except for loading another file. Having actions for each menu item would be a bit silly.

7 Answers

I have a bunch of panels and I want to let the user right click any of those panels and perform a "delete file" action. So, I have a single pop-up menu associated with all those panels. This is how I find out which panel was right clicked:

(Note: I put lots of comments to clearly explain how it works. But if you don't like it, you can compactify the code to 2 lines (see the second procedure)).

So, if you have actions assigned to that pop-up menu:

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;

If you only have a simple pop-up menu (no actions assigned):

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

You can put all that code in a single function that returns a TPanel and call it like this:

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