How to execute an ActionList item from a TListViewItem

3k Views Asked by At

I'm trying to execute an action (TakePhotoFromCameraAction) in a TActionList, when a TListViewItem is selected.

Neither TlistView nor TListViewItem have an Action property, so I've tried calling ActionList[0].Execute in the event, but nothing happens.

Any ideas?

Further: The code is very simple, as it was just a test for this problem. I was focussing on the ActionList as that was what I will use (when I sort it out). Button1 doesn't work (it always fails, even when button 2 doesn't), whereas the (new) Button2 does work OK.

type
  TForm1 = class(TForm)
    ActionList1: TActionList;
    Memo1: TMemo;
    TakePhotoFromCameraAction1: TTakePhotoFromCameraAction;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
begin
     ActionList1[0].Execute;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
     if TakePhotoFromCameraAction1.Execute
     then
         Memo1.Lines.add('Photo OK')
     else
         Memo1.Lines.add('Photo Fail');
end;
2

There are 2 best solutions below

6
On

You can use good old tag property of TListViewItem to store pointer to TAction you want to use with this item. Of course, you can't set it in object inspector, but can do it programmaticaly in TForm.onCreate event or some other convenient place. It has type NativeInt which has the same size as pointer be it 32-bit or 64-bit architecture, so it should work properly.

Something like this:

//in formCreate or other place to initialize actions:
TakePhotoItem.Tag:=NativeInt(TakePhotoFromCameraAction);
SavePhotoItem.Tag:=NativeInt(SavePhotoAction);
//...

//onitemchange event handler
if AItem.Tag<>0 then
  TAction(AItem.Tag).Execute;

Maybe it's better to introduce your own descendant of TListViewItem which has Action property, that way you'll have to populate your listview in code only, adding not basic TListViewItem, but TActionListViewItem (name of your class), that has more work to do but will yield more understandable code.

1
On

There is no difference (except being ugly) to call ActionList1[0].Execute; versus Action1.Execute;.

You didn't show the the .fmx file so I can't know what linkage you may have setup between the components, but, it seems you haven't assigned anything to the actions OnExecute event, and therefore do not get the expected response to the Execute call.

The FMX Version of the documentation is not very clear, but the VCL version is (IMO) better (In a brief test I don't see any difference in actual functionality): From documentation :

Responds when a client control "fires".

Execute is called automatically when a client control "fires" (for example, when the user clicks a button or selects a menu item). It returns True if an event handler is found to handle the action, False if there was no event handler or if the action was not enabled.

but you can ofcourse also call Execute directly as you tried. And further

Execute first ensures that the action is updated. Then, if the Enabled property is True, it attempts to handle the action by generating an OnExecute event on the action list that contains this action (if the action belongs to an action list). If the action list's OnExecute event handler does not handle the action, Execute generates an OnActionExecute event on the application itself. If neither the action list nor the application handles the action in response to these events, Execute generates an OnExecute event on itself. If this action has no OnExecute event handler, Execute instructs the application to locate the current target control and call the ExecuteTarget method, which is the mechanism by which predefined action classes perform their function.

Note that you can handle the actions in TActionList.OnExecute or in the TAction.OnExecute