Method pointer and regular procedure incompatible

后端 未结 5 845
谎友^
谎友^ 2020-12-14 03:27

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

相关标签:
5条回答
  • 2020-12-14 03:52

    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.

    0 讨论(0)
  • 2020-12-14 03:53

    You could choose one of these:

    1. Derive your forms from a common ancestor and declare the method in it so it's available to descendants
    2. Use a global instance of a class (e.g. data module) shared by all forms
    3. Use a procedure as a fake method like this:

    procedure MyClick(Self, Sender: TObject);
    begin
      //...
    end;
    
    var
      M: TMethod;
    begin
      M.Data := nil;
      M.Code := @MyClick;
      MyMenuItem.OnClick := TNotifyEvent(M);
    end;
    
    0 讨论(0)
  • 2020-12-14 04:00

    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.

    0 讨论(0)
  • 2020-12-14 04:03

    One solution is to place the OnClick method into a TDatamodule.

    0 讨论(0)
  • 2020-12-14 04:11

    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.

    0 讨论(0)
提交回复
热议问题