I have an app, which has multiple forms. All these forms have a PopupMenu. I build the menu items programatically, all under a common root menu item. I want ALL the menu
You can wrap your procedures into a class. This class might look like this in a separate unit:
unit CommonUnit;
interface
uses
Dialogs;
type
TMenuActions = class
public
class procedure BrowseCategoriesClick(Sender: TObject);
end;
implementation
{ TMenuActions }
class procedure TMenuActions.BrowseCategoriesClick(Sender: TObject);
begin
ShowMessage('BrowseCategoriesClick');
end;
end.
And to assign the action to a menu item in a different unit is enough to use this:
uses
CommonUnit;
procedure TForm1.FormCreate(Sender: TObject);
begin
PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;
Update:
Updated to use class procedures (instead of object methods) by David's suggestion. For those who want to use the object methods with the need of object instance, follow this version of the post.
You could choose one of these:
procedure MyClick(Self, Sender: TObject);
begin
//...
end;
var
M: TMethod;
begin
M.Data := nil;
M.Code := @MyClick;
MyMenuItem.OnClick := TNotifyEvent(M);
end;
This is the difference between a "procedure" and a "procedure of object"
The OnClick
is defined as a TNotifyEvent
:
type TNotifyEvent = procedure(Sender: TObject) of object;
You cannot assign a procedure to the OnClick
as it is the wrong type. It needs to be a procedure of object.
One solution is to place the OnClick method into a TDatamodule.
A little background...
Delphi has 3 procedural types:
Standalone or unit-scoped function/procedure pointers declared like so:
var Func: function(arg1:string):string;
var Proc: procedure(arg1:string);
Method pointers declared like so:
var Func: function(arg1:string):string of object;
var Proc: procedure(arg1:string) of object;
And, since Delphi 2009, anonymous(see below) function/method pointers declared like so:
var Func: reference to function(arg1:string):string;
var Proc: reference to procedure(arg1:string);
Standalone pointers and method pointers are not interchangeable. The reason for this is the implicit Self
parameter that is accessible in methods. Delphi's event model relies on method pointers, which is why you can't assign a standalone function to an object's event property.
So your event handlers will have to be defined as part of some class definition, any class definition to appease the compiler.
As TOndrej suggested you can hack around the compiler but if these event handlers are in the same unit then they should already be related anyway so you may as well go ahead and wrap them into a class.
One additional suggestion I have not seen yet is to backtrack a little. Let each form implement its own event handler but have that handler delegate responsibility to a function declared in your new unit.
TForm1.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
TForm2.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
unit CommonUnit
interface
procedure BrowseCategories;
begin
//
end;
This has the added benefit of separating the response to the user's action from the control that triggered the action. You could easily have the event handlers for a toolbar button and a popup menu item delegate to the same function.
Which direction you choose is ultimately up to you but I'd caution you to focus on which option will make maintainability easier in the future rather than which is the most expedient in the present.
Anonymous methods
Anonymous methods are a different beast all together. An anonymous method pointer can point to a standalone function, a method or a unnamed function declared inline. This last function type is where they get the name anonymous from. Anonymous functions/methods have the unique ability to capture variables declared outside of their scope
function DoFunc(Func:TFunc<string>):string
begin
Result := Func('Foo');
end;
// elsewhere
procedure CallDoFunc;
var
MyString: string;
begin
MyString := 'Bar';
DoFunc(function(Arg1:string):string
begin
Result := Arg1 + MyString;
end);
end;
This makes them the most flexible of the procedural pointer types but they also have potentially more overhead. Variable capture consumes additional resources as does inline declarations. The compiler uses a hidden reference counted interface for inline declarations which adds some minor overhead.