Method pointer and regular procedure incompatible
我有一个应用程序,它具有多种形式。所有这些形式都有一个PopupMenu。我以编程方式构建菜单项,所有这些都在一个公共的根菜单项下。我希望所有菜单项都调用相同的过程,并且菜单项本身基本上充当参数...。
当我只有一种形式执行此功能时,我就可以使用此功能。我现在有多种形式需要执行此操作。我将所有代码移到一个通用单元。
1 2 3 | 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. |
当我需要从每种形式调用我的弹出窗口时,我调用顶层过程(在CommonUnit单元中),将顶层菜单项的名称从每种形式传递到通用单元中的顶层过程。
我将代码添加到我的PopupMenu中。
1 2 3 4 5 | M1 := TMenuItem.Create(TopMenuItem); M1.Caption := FieldByName('NAME').AsString; M1.Tag := FieldByName('ID').AsInteger; M1.OnClick := BrowseCategories1Click; TopMenuItem.Add(M1); |
编译时出现错误信息。具体来说,OnClick行抱怨
不兼容的类型:"方法指针和常规过程"。
我已经定义了BrowseCategories1Click,与在单个表单上执行此操作时完全一样。唯一的区别是,它现在是在公共单位中定义的,而不是作为表单的一部分定义的。
定义为
1 2 3 4 | procedure BrowseCategories1Click(Sender: TObject); begin // end; |
解决此问题的最简单方法是什么?
谢谢
GS
一点背景...
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);
独立指针和方法指针不可互换。这样做的原因是方法中可以访问的隐式
因此,必须将您的事件处理程序定义为某些类定义的一部分,以定义任何可以使编译器满意的类定义。
正如TOndrej所建议的那样,您可以修改编译器,但是如果这些事件处理程序位于同一单元中,则它们应该已经相关,因此您最好继续将它们包装到一个类中。
我还没有看到的另一个建议是稍微回溯一下。让每种表单实现其自己的事件处理程序,但让该处理程序将责任委托给新单元中声明的函数。
1 2 3 4 5 6 7 8 9 | TForm1.BrowseCategoriesClick(Sender:TObject) begin BrowseCategories; end; TForm2.BrowseCategoriesClick(Sender:TObject) begin BrowseCategories; end; |
1 2 3 4 5 6 7 | unit CommonUnit interface procedure BrowseCategories; begin // end; |
这具有将对用户操作的响应与触发该操作的控件分开的额外好处。您可以轻松地将工具栏按钮的事件处理程序和弹出菜单项委托给同一函数。
您最终选择哪个方向取决于您,但是我提醒您,专注于哪个选项将使将来的可维护性更加容易,而不是目前最方便的选择。
匿名方法
匿名方法是完全不同的野兽。匿名方法指针可以指向内联声明的独立函数,方法或未命名函数。最后一种函数类型是他们从中获取匿名名称的地方。匿名函数/方法具有捕获超出其范围声明的变量的独特能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; |
这使它们成为过程指针类型中最灵活的,但它们也可能具有更多的开销。变量捕获和内联声明一样消耗更多资源。编译器将隐藏的引用计数接口用于内联声明,这会增加一些次要开销。
您可以将过程包装到一个类中。此类在单独的单元中看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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. |
并将操作分配给其他单位中的菜单项足以使用此功能:
1 2 3 4 5 6 7 | uses CommonUnit; procedure TForm1.FormCreate(Sender: TObject); begin PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick; end; |
更新:
根据David的建议,已更新为使用类过程(而不是对象方法)。对于那些想要在需要对象实例的情况下使用对象方法的人,请遵循文章的
这是"过程"和"对象过程"之间的区别
type TNotifyEvent = procedure(Sender: TObject) of object;
您不能为
您可以选择以下之一:
1 2 3 4 5 6 7 8 9 10 11 12 | procedure MyClick(Self, Sender: TObject); begin //... end; var M: TMethod; begin M.Data := nil; M.Code := @MyClick; MyMenuItem.OnClick := TNotifyEvent(M); end; |
一种解决方案是将OnClick方法放入TDatamodule中。