How to execute an ActionList item from a TListViewItem - delphi

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;

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.

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

Related

Error on opening an MDI ChildForm from another MDI ChildForm's TButton via TMenuItem on MDI ParentForm

I have 2 MDIChild Forms and an MDIParent Form with a TMainMenu. In the TMainMenu, I have the following TMenuItem.OnClick procedure to open ChildForm2:
procedure TfrmMain.miOpenChildForm2Click(Sender: TObject);
begin
TfrmChildForm2.Create(self).Show;
end;
Now, I want to access the above procedure from ChildForm1 by a TButton.OnClick procedure below:
procedure TfrmChildForm1.btnOpenChildForm2Click(Sender: TObject);
begin
frmMain.miOpenChildForm2Click; // Error Here: E2035 Not enough actual parameters
End;
I am getting an error on the above second procedure:
E2035 Not enough actual parameters
I don't know exactly where to correct it. I tried putting '()' at the end of the procedure call, but no avail.
The procedure definition
procedure TfrmMain.miOpenChildForm2Click(Sender: TObject);
tells you that the procedure expects to receive a parameter Sender of type TObject. Calling it with frmMain.miOpenChildForm2Click; does not pass that parameter. The parameter is not optional.
Sender is intended to tell you what triggered the event, for use in cases where it matters, such as when you're using one event handler for multiple controls. It allows you to differentiate where the call to the event originated.
You can use the button or menu item that was clicked in the call as the parameter
frmMain.miOpenChildForm2Click(btnOpenChildForm2);
If it doesn't matter where the call came from, you can pass nil instead
frmMain.miOpenChildForm2Click(nil);
As a note: MDI has been deprecated for at least a decade, and Windows has not had much support for it for at least that long. Modern applications do not use MDI, and new development most likely shouldn't include it either.

Extend the event OnClick of all MenuItems in the screen to execute another block of code

I want to set an event OnClick to all TMenuItems on the screen to do what the event currently does, and another few lines of code. I am currently using Delphi 5
For example, say that I have a TMenuItem with the code:
procedure TdesktopForm.MenuFoo1Click(Sender: TObject);
begin
ShowMessage(TComponent(Sender).Name)
end;
and I also have the following procedure:
procedure TdesktopForm.bar;
begin
ShowMessage('extra')
end;
And I want to everytime I click the TMenuItem the program show the TMenuItem's name and also the 'extra' message.
The example shown is just a demonstration of my problem, as in the real software I have over 300 menu items, I want to do this generically, so I won't have to add extra lines of code to all current menu clicks, nor add them when I add new menu items. The order of execution (between the menu click and the extra block of code) doesn't matter.
I tried using TActionList but I couldn't retrieve the object triggering the action, hence, I can't print it's name. I tried using ActiveControl but it always return the focused currently focused object, not the actual menu that I clicked. And also, the TAction execute event overwrites my TMainMenu.OnClick event
As long as all your event handlers are assigned at some point (either at design time or at run time) and don't change afterwards, you can do something like this:
Enumerate all menu items in the menu
For each create an object like the one described below
type
TEventInterceptor = class(TComponent)
private
FOrigEvent: TNotifyEvent;
FAdditionalEvent: TNotifyEvent;
procedure HandleOnClick(_Sender: TObject);
public
constructor Create(_MenuItem: TMenuItem; _AdditionalEvent: TNotifyEvent);
end;
constructor TEventInterceptor.Create(_MenuItem: TMenuItem; _AdditionalEvent: TNotifyEvent);
begin
inherited Create(_MenuItem);
FOrigEvent := _MenuItem.OnClick;
FAdditionalEvent := _AdditionalEvent;
_MenuItem.OnClick := HandleOnClick;
end;
procedure TEventInterceptor.HandleOnClick(_Sender: TObject);
begin
FOrigEvent(_Sender);
FAdditinalEvent(_Sender);
end;
Note that this code is completely untested and may not even compile.
I'm also not sure whether this works with Delphi 5. It does with Delphi 6 though, so chances are good.
Edit:
Some additional notes (thanks for the comments):
Inheriting this class from TComponent makes the form free it automatically when it is being destroyed.
HandleOnClick should possibly check if FOrigEvent is assigned before calling it.

Is safe to set a private/protected/public method as event handler?

While assigning event handlers, I've noticed that the Object Inspector allows to choose only between methods who have not an explicit access modifier.
Taking the following class as example:
TMyForm = class(TForm)
MyButton: TButton;
procedure MyButtonClick(Sender: TObject);
private
procedure MyButtonPrivateClick(Sender: TObject);
protected
procedure MyButtonProtectedClick(Sender: TObject);
public
procedure MyButtonPublicClick(Sender: TObject);
end;
In the dropdown list, the Object Inspector shows only MyButtonClick:
Due to this reason, I'm wondering if it's safe to set a private/protected/public method to an event handler by code or if there could be some problems deriving from this practice.
MyButton.OnClick := MyButtonPrivateClick;
//...
This is perfectly safe without having to worry about any hidden issues. This is especially common when writing a custom component (as opposed to a form) which has a sub-component inside of it, for example. You can put it virtually anywhere you want, but I suggest to keep it under private.

Implementing Button Click in Firemonkey

I am missing TButton.Click Method in FireMonkey TButton.
Is there any way to fire click event in the code in fire monkey? If there is no such event, just use the click method to fire the action assigned to the button?
Well, you can simply write:
Button1.OnClick(Button1);
If there is an action attached to the button you can invoke it with
Button1.Action.Execute;
But that is not really to be recommended. The right way to do this is to create a method to do the work. Then call that method from either your OnClick event handler, or the other location in your code that wants to invoke this action. Like this:
procedure TForm1.DoSomething;
begin
// do whatever it is
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomething;
end;
Then anywhere in your code you can just call DoSomething. It's best to leave GUI event handlers just for handling GUI events.
You can use this method :
first declare this new type to access TButton Click procedure :
type
TButtonHack = type TButton;
Then caste your button to TButtonHack class and call Click procedure :
TButtonHack(Button1).Click;

Prevent methods with empty bodies from deletion on save

It's quite a contradictory habit of mine to press Ctrl+S permanently. The negative side is that delphi deletes empty functions/procedures on save.
Is there a way to prevent IDE from deleting functions/procedures with empty bodies on save?
Converted from the comment as per OP request. My comment is too tiny for an answer, so I'm going to add few details maybe already obvious to an OP.
This happens with event handlers only¹. Write them without delay or
comment them with todo²
¹ That is, event handlers are methods of design class and they are created, listed and deleted (if caught empty when saving or compiling) by the form designer (this include data module designer and any of other custom designers installed). Confer to delegates you probably familiar with from C# background. Any other methods are subject to "manual" management.
² TODO items (Ctrl+Shift+T in default keybinding) are definitely better than just blank comments:
procedure TForm1.MagicButton1Click(Sender: TObject);
begin
{ TODO -ctomorrow : I'm going to write the code, I promise! }
end;
Possible special case
TAction with AutoCheck set must (see the comment from Sir Rufo below for another possibility at run time) have its OnExecute assigned in order to be Enabled. In this case it is inevitably to have such blank event handlers within design class. Example:
procedure TMonitor.AutoCheckActionExecute(Sender: TObject);
begin
// dummy stub
{ DONE -crefactor : merge with other stub(s) }
end;
Just add an empty comment like //
begin
//
end;
an other way would by moving the declaration to the published part
type
TForm5 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject); // will be removed if empty
private
{ Private-Deklarationen }
public
published
procedure Button2Click(Sender: TObject); // will not be removed if empty
{ Public-Deklarationen }
end;
Is there a way to prevent IDE from deleting functions/
procedures with empty bodies on save?
There is no option in the IDE to disable this behaviour.

Resources