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:
Button1
and Button2
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.
Try using the ActionComponent property:
Stores the client component that caused this action to execute.
Use ActionComponent to discern which client component caused this action to execute. For example, examine ActionComponent from an OnExecute event handler if you need to know what user action triggered this action.
When the user clicks a client control, that client sets ActionComponent before calling the action's Execute method. After the action executes, the action resets ActionComponent to nil.
For example:
ShowMessage( (Sender as TAction).ActionComponent.Name );
Using this I get "Button1" and "Button2" when I click the first and second button respectively.
Knowing what button triggered the action sort of goes against the point of using actions - an action may be triggered by a button click, or a menu click, or any number of other user activities. Actions exist to unify the state management of enable/disabled and click handling between buttons and menus.
If you want to know which button fired the action because you want to perform a slightly different operation, or "flavor" the operation differently, then perhaps TAction isn't the right solution for what you want to do.
Instead of actions, just use a click event. Set all buttons to use the same event handler. Ideally, NOT named after the first button (you can rename it).
Here's the code:
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;
There are situations where the same action should apply to similar controls. The problem with
ShowMessage( (Sender as TAction).ActionComponent.Name );
is that when the action is invoked by a say popup menu, you get the popup menu's name. You could use:
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;
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With