web-dev-qa-db-ja.com

メソッドポインタと通常のプロシージャは互換性がありません

複数のフォームがあるアプリがあります。これらすべてのフォームにはPopupMenuがあります。メニュー項目は、すべて共通のルートメニュー項目の下にプログラムで作成します。すべてのメニュー項目で同じプロシージャを呼び出したいのですが、メニュー項目自体は基本的に引数として機能しています。

この機能を実行するフォームが1つしかないときに、これが機能していました。私は今、これを行う必要がある複数のフォームを持っています。すべてのコードを共通のユニットに移動しています。

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にコードを使用してアイテムを追加しています。

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

15
user1009073

少し背景...

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以降、anonymous(以下を参照)関数/メソッドポインタは次のように宣言されています。

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

スタンドアロンポインタとメソッドポインタは互換性がありません。この理由は、メソッドでアクセスできる暗黙のSelfパラメーターです。 Delphiのイベントモデルはメソッドポインタに依存しているため、スタンドアロン関数をオブジェクトのイベントプロパティに割り当てることはできません。

したがって、イベントハンドラーは、someクラス定義、anyクラス定義の一部として定義して、コンパイラーを緩和する必要があります。

TOndrejがコンパイラーをハックすることを提案したように、これらのイベントハンドラーが同じユニットにある場合は、いずれにせよそれらはすでに関連付けられているはずなので、先に進んでクラスにラップすることもできます。

私がまだ見たことがないもう1つの提案は、少しバックトラックすることです。各フォームに独自のイベントハンドラーを実装させますが、そのハンドラーは新しいユニットで宣言された関数に責任を委任します。

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

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

unit CommonUnit

interface
procedure BrowseCategories;
begin
//
end;

これには、ユーザーのアクションへの応答を、アクションをトリガーしたコントロールから分離するという追加の利点があります。ツールバーボタンのイベントハンドラーとポップアップメニュー項目を同じ関数に委任するのは簡単です。

どちらの方向を選択するかは最終的にはあなた次第ですが、現在最も便利なオプションではなく、将来の保守性を容易にするオプションに焦点を当てることをお勧めします。


匿名メソッド

匿名の方法は、すべて一緒に別の獣です。匿名メソッドポインターは、standalone関数、method、またはインラインで宣言された名前のない関数を指すことができます。この最後の関数タイプは、名前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;

これにより、手続き型ポインタタイプの中で最も柔軟性が高くなりますが、オーバーヘッドが増える可能性もあります。変数キャプチャは、インライン宣言と同様に追加のリソースを消費します。コンパイラは、インライン宣言に非表示の参照カウントインターフェイスを使用するため、わずかなオーバーヘッドが追加されます。

22
Kenneth Cochran

プロシージャをクラスにラップできます。このクラスは、別のユニットでは次のようになります。

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 投稿の。

17
TLama

これが「手順」と「オブジェクトの手順」の違いです

OnClickTNotifyEventとして定義されます:

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

OnClickは間違ったタイプなので、プロシージャを割り当てることはできません。オブジェクトのプロシージャである必要があります。

11
GDF

次のいずれかを選択できます。

  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;
10
Ondrej Kelle

1つの解決策は、OnClickメソッドをTDatamoduleに配置することです。

2
Uwe Raabe