Method pointer and regular procedure incompatible

user1009073 picture user1009073 · Jul 3, 2012 · Viewed 32.7k times · Source

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 items to call the same procedure, and the menu item itself is basically acting as an argument....

I had this working when I just had one form doing this functionality. I now have multiple forms needing to do this. I am moving all my code to a common unit.

Example.
Form A has PopupMenu 1.  When clicked, call code in Unit CommonUnit.
Form B has PopupMenu 2.  When clicked, call code in unit CommonUnit.

When I need to call my popup from each form, I call my top level procedure (which is in unit CommonUnit), passing the name of the top menu item from each form to the top level procedure in the common unit.

I am adding items to my PopupMenu with with code.

M1 := TMenuItem.Create(TopMenuItem);
M1.Caption := FieldByName('NAME').AsString;
M1.Tag := FieldByName('ID').AsInteger;
M1.OnClick := BrowseCategories1Click;
TopMenuItem.Add(M1);

I am getting an error message when I compile. Specifically, the OnClick line is complaining about

Incompatible types: 'method pointer and regular procedure'.

I have defined BrowseCategories1Click exactly like it was before when I was doing this on a single form. The only difference is that it is now defined in a common unit, rather than as part of a form.

It is defined as

procedure BrowseCategories1Click(Sender: TObject);
begin
//
end;

What is the easiest way to get around this?

Thanks GS

Answer

Kenneth Cochran picture Kenneth Cochran · Jul 3, 2012

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.