方法指针和常规过程不兼容

use*_*073 14 delphi methods pointers delphi-2010

我有一个应用程序,它有多种形式.所有这些表单都有一个PopupMenu.我以编程方式构建菜单项,所有这些都在一个共同的根菜单项下.我希望所有菜单项都调用相同的程序,菜单项本身基本上作为参数....

当我只有一个表单执行此功能时,我有这个工作.我现在有多种形式需要这样做.我正在将所有代码移动到一个公共单元.

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.
Run Code Online (Sandbox Code Playgroud)

当我需要从每个表单调用我的弹出窗口时,我调用我的顶级过程(在单元CommonUnit中),将顶部菜单项的名称从每个表单传递到公共单元中的顶级过程.

我正在使用代码向PopupMenu添加项目.

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

我编译时收到错误消息.具体来说,OnClick线正在抱怨

不兼容的类型:'方法指针和常规过程'.

我已经定义了BrowseCategories1Click,就像我在单个表单上执行此操作之前一样.唯一的区别是它现在定义在一个公共单元中,而不是作为表单的一部分.

它被定义为

procedure BrowseCategories1Click(Sender: TObject);
begin
//
end;
Run Code Online (Sandbox Code Playgroud)

解决这个问题的最简单方法是什么?

谢谢GS

Ken*_*ran 23

一点背景......

Delphi有3种程序类型:

  • 单独或单元范围的函数/过程指针声明如下:

    var Func: function(arg1:string):string;
    var Proc: procedure(arg1:string);

  • 方法指针声明如下:

    var Func: function(arg1:string):string of object;
    var Proc: procedure(arg1:string) of object;

  • 而且,自从Delphi 2009,匿名(见下文)函数/方法指针声明如下:

    var Func: reference to function(arg1:string):string;
    var Proc: reference to procedure(arg1:string);

独立指针和方法指针不可互换.原因Self是方法中可以访问的隐式参数.Delphi的事件模型依赖于方法指针,这就是为什么不能将独立函数分配给对象的事件属性的原因.

因此,您的事件处理程序必须被定义为某个类定义的一部分,任何类定义都是为了安抚编译器.

正如TOndrej建议你可以破解编译器,但如果这些事件处理程序在同一个单元中,那么它们应该已经相关,所以你可以继续将它们包装成一个类.

我还没有看到的另一个建议是稍微退一步.让每个表单实现自己的事件处理程序,但让该处理程序将责任委托给在新单元中声明的函数.

TForm1.BrowseCategoriesClick(Sender:TObject)
begin
  BrowseCategories;
end;

TForm2.BrowseCategoriesClick(Sender:TObject)
begin
  BrowseCategories;
end;
Run Code Online (Sandbox Code Playgroud)
unit CommonUnit

interface
procedure BrowseCategories;
begin
//
end;
Run Code Online (Sandbox Code Playgroud)

这具有将对用户操作的响应与触发操作的控件分开的附加好处.您可以轻松地将工具栏按钮和弹出菜单项的事件处理程序委托给同一个函数.

你选择哪个方向最终取决于你,但我要提醒你要关注哪个选项将来会使维护更容易,而不是现在最方便的选择.


匿名方法

匿名方法是一种不同的野兽.匿名方法指针可以指向独立函数,方法或内联声明的未命名函数.最后一个函数类型是匿名的名称.匿名函数/方法具有捕获在其范围之外声明的变量的独特能力

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;
Run Code Online (Sandbox Code Playgroud)

这使它们成为最灵活的过程指针类型,但它们也可能具有更多的开销.变量捕获消耗额外的资源,内联声明也是如此.编译器使用隐藏引用计数接口进行内联声明,这会增加一些小的开销.


TLa*_*ama 17

您可以将程序包装到一个类中.这个类可能在一个单独的单元中看起来像这样:

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.
Run Code Online (Sandbox Code Playgroud)

将动作分配给不同单位的菜单项就足以使用它:

uses
  CommonUnit;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;
Run Code Online (Sandbox Code Playgroud)

更新:

更新为使用David的建议使用类过程(而不是对象方法).对于那些想要使用对象方法需要对象实例的人,请按照this version帖子进行操作.

  • 使它成为一个类过程,您不需要实例化一个对象 (6认同)

GDF*_*GDF 11

这是"程序"和"对象程序"之间的区别

OnClick定义为TNotifyEvent:

type TNotifyEvent = procedure(Sender: TObject) of object;

您无法为其分配过程,OnClick因为它是错误的类型.它需要是一个对象的过程.


Ond*_*lle 11

您可以选择以下其中一种:

  1. 从共同的祖先派生您的表单并在其中声明方法,以便后代可以使用它
  2. 使用所有表单共享的类(例如数据模块)的全局实例
  3. 使用过程作为这样的假方法:

procedure MyClick(Self, Sender: TObject);
begin
  //...
end;

var
  M: TMethod;
begin
  M.Data := nil;
  M.Code := @MyClick;
  MyMenuItem.OnClick := TNotifyEvent(M);
end;
Run Code Online (Sandbox Code Playgroud)

  • 啊! 不是低调,因为从技术上来说,它们*都会起作用.但是,@ TLama的解决方案要比IMO中的任何一个更清晰,特别是如果添加了David的"类过程"的建议,那么在使用它之前甚至不需要创建实例. (3认同)
  • @KenWhite我完全同意你的看法。我只是试图列出其他人尚未提及的选项。(我忘记了类方法。) (2认同)