16

複数のフォームを持つアプリがあります。これらすべてのフォームには 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

4

5 に答える 5

26

背景をちょっと…

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 が示唆したように、コンパイラをハックすることができますが、これらのイベント ハンドラが同じユニットにある場合は、いずれにせよ既に関連付けられているはずなので、先に進んでクラスにラップすることもできます。

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

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

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

unit CommonUnit

interface
procedure BrowseCategories;
begin
//
end;

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

どちらの方向を選択するかは最終的にはあなた次第ですが、現時点でどちらが最も便利かではなく、将来のメンテナンスを容易にするオプションに注目するように注意してください。


匿名メソッド

匿名メソッドはまったく別物です。匿名メソッド ポインターは、スタンドアロン関数、メソッド、またはインラインで宣言された名前のない関数を指すことができます。この最後の関数型は、匿名の名前を取得する場所です。匿名関数/メソッドには、スコープ外で宣言された変数をキャプチャする独自の機能があります

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;

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

于 2012-07-03T19:21:59.050 に答える
19

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

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は、投稿に従ってください。

于 2012-07-03T16:13:05.600 に答える
12

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

は次のOnClickように定義されますTNotifyEvent

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

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

于 2012-07-03T16:11:01.583 に答える
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;
于 2012-07-03T16:13:10.577 に答える
3

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

于 2012-07-03T16:09:52.663 に答える