複数のフォームがあるアプリがあります。これらすべてのフォームには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
少し背景...
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;
これにより、手続き型ポインタタイプの中で最も柔軟性が高くなりますが、オーバーヘッドが増える可能性もあります。変数キャプチャは、インライン宣言と同様に追加のリソースを消費します。コンパイラは、インライン宣言に非表示の参照カウントインターフェイスを使用するため、わずかなオーバーヘッドが追加されます。
プロシージャをクラスにラップできます。このクラスは、別のユニットでは次のようになります。
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
投稿の。
これが「手順」と「オブジェクトの手順」の違いです
OnClick
はTNotifyEvent
として定義されます:
type TNotifyEvent = procedure(Sender: TObject) of object;
OnClick
は間違ったタイプなので、プロシージャを割り当てることはできません。オブジェクトのプロシージャである必要があります。
次のいずれかを選択できます。
procedure MyClick(Self, Sender: TObject);
begin
//...
end;
var
M: TMethod;
begin
M.Data := nil;
M.Code := @MyClick;
MyMenuItem.OnClick := TNotifyEvent(M);
end;
1つの解決策は、OnClickメソッドをTDatamoduleに配置することです。