I'm using TActions and TButtons in a VCL application. Setting the TButtons Action field to an existing action centralizes code.
An actions Execute method looks like this:
void __fastcall MyFrame::MyActionExecute(TObject *Sender)
{
//Some action code
}
Assigning the MyAction to a TButton named MyBtn and looking at the actions ActionComponent:
void __fastcall MyFrame::MyActionExecute(TObject *Sender)
{
if(MyAction->ActionComponent == MyBtn)
{
//.. action code when the MyBtn was clicked..
}
}
seem to work good.
However, calling the MyAction's Execute method programatically, like this:
MyActionExcecute(NULL);
seem to not set the ActionComponent to NULL, but 'still' uses the MyBtn as the ActionCompoent. So the if statement above does evaluate to true, even though the button was not clicked.
Question is, what is the proper way to handle both button clicks and manually calling an actions execute method?
I'm aware that I can check Sender parameter for NULL, and if so, I can assume that it was not the button.
The action's ActionComponent
property is set only when a UI control is firing the action, via an internal TBasicActionLink
object that the control creates internally for itself. The link's Execute()
method has an AComponent
parameter that the control passes its Self
/this
pointer to, to set the action's ActionComponent
before calling the action's Execute()
method.
For example, this is what the VCL does internally:
procedure TControl.SetAction(Value: TBasicAction);
begin
if Value = nil then
begin
ActionLink.Free;
ActionLink := nil;
...
end
else
begin
...
if ActionLink = nil then
ActionLink := GetActionLinkClass.Create(Self);
ActionLink.Action := Value;
...
end;
end;
procedure TControl.Click;
begin
{ Call OnClick if assigned and not equal to associated action's OnExecute.
If associated action's OnExecute assigned then call it, otherwise, call
OnClick. }
if Assigned(FOnClick) and (Action <> nil) and not DelegatesEqual(@FOnClick, @Action.OnExecute) then
FOnClick(Self)
else if not (csDesigning in ComponentState) and (ActionLink <> nil) then
ActionLink.Execute(Self) // <-- HERE
else if Assigned(FOnClick) then
FOnClick(Self);
end;
function TBasicActionLink.Execute(AComponent: TComponent): Boolean;
begin
FAction.ActionComponent := AComponent; // <-- HERE
Result := FAction.Execute;
end;
So, don't call your OnExecute
event handler directly. That will not update the action at all. Call the action's Execute()
method instead. You will just have to set the action's ActionComponent
to NULL yourself beforehand, eg:
MyAction->ActionComponent = NULL;
MyAction->Execute();
The documentation claims:
When the user clicks a client control, that client sets
ActionComponent
before calling the action'sExecute
method. After the action executes, the action resetsActionComponent
to nil (Delphi) or NULL (C++).
However, the ActionComponent
IS NOT reset automatically, only when the next time a UI control decides to execute the action for itself.