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

时间:2012-07-03 15:50:55

标签: delphi pointers methods 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.

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

我正在使用代码将项目添加到我的PopupMenu。

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

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

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

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

定义为

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

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

由于 GS

5 个答案:

答案 0 :(得分:22)

一点背景......

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;

unit CommonUnit

interface
procedure BrowseCategories;
begin
//
end;

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

您选择哪个方向最终取决于您,但我要提醒您注意哪个选项将来会使维护更容易,而不是目前最便捷的选项。


匿名方法

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

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 :(得分: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.

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

uses
  CommonUnit;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;

<强> 更新

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

答案 2 :(得分:11)

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

OnClick被定义为TNotifyEvent

  

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

您无法为OnClick指定过程,因为它的类型错误。它需要是一个对象的过程。

答案 3 :(得分:10)

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

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

  4. procedure MyClick(Self, Sender: TObject);
    begin
      //...
    end;
    
    var
      M: TMethod;
    begin
      M.Data := nil;
      M.Code := @MyClick;
      MyMenuItem.OnClick := TNotifyEvent(M);
    end;
    

答案 4 :(得分:2)

一种解决方案是将OnClick方法放入TDatamodule。