Динамическое назначение анонимных функций в pascal
Я хотел бы иметь возможность динамически генерировать всплывающие меню в pascal.
Я также хотел бы иметь возможность динамически назначать обработчики OnClick каждому пункту меню.
это то, что я привык делать в C#, это моя попытка в pascal.
элемент меню обработчик событий onClick должен принадлежать объекту (of Object
) поэтому я создаю объект контейнера для этого.
вот мой код:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Menus;
type
TForm1 = class(TForm)
PopupMenu1: TPopupMenu;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TFoo = class
public
Bar : String;
Val : Integer;
end;
TNotifyEventWrapper = class
private
FProc: TProc<TObject>;
I : Integer;
public
constructor Create(Proc: TProc<TObject>);
published
procedure Event(Sender: TObject);
end;
var
Form1: TForm1;
NE : TNotifyEventWrapper;
implementation
{$R *.dfm}
constructor TNotifyEventWrapper.Create(Proc: TProc<TObject>);
begin
inherited Create;
FProc := Proc;
end;
procedure TNotifyEventWrapper.Event(Sender: TObject);
begin
ShowMessage(IntToStr(I));
FProc(Sender);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
F : TFoo;
I: Integer;
mi : TMenuItem;
begin
if Assigned(NE) then FreeAndNil(NE);
for I := 1 to 10 do
begin
F := TFoo.Create;
F.Bar := 'Hello World!';
F.Val := I;
NE := TNotifyEventWrapper.Create
(
procedure (Sender :TObject)
begin
ShowMessage(F.Bar + ' ' + inttostr(F.Val) + Format(' Addr = %p', [Pointer(F)]) + Format('Sender = %p, MI.OnClick = %p', [Pointer(Sender), Pointer(@TMenuItem(Sender).OnClick)]));
end
);
NE.I := I;
mi := TMenuItem.Create(PopupMenu1);
mi.OnClick := NE.Event;
mi.Caption := inttostr(F.Val);
PopupMenu1.Items.Add(mi);
end;
end;
end.
при нажатии пункта меню номер 6
программа показывает ожидаемое сообщение
однако следующее сообщение не показывало ожидаемого результата.
вместо 6 он показывает пункт 10
независимо от того, какой элемент в списке я нажимаю, все они, похоже, запускают обработчик событий для последнего элемента в списке (10).
Это был предложил мне, что NE
процедура-член объекта Event
- Это один и тот же адрес памяти для всех экземпляров этого объекта.
какой бы пункт меню я ни нажал, адрес памяти MI.OnClick
то же самое.
1 ответов
ключ к пониманию этого-понять, что захват переменной захватывает переменные, а не значения.
ваши методы anon захватывают одну и ту же переменную F
. Существует только один экземпляр этой переменной с FormCreate
выполняется только один раз. Это объясняет поведение. Когда ваши методы anon выполняют переменную F
имеет значение, присвоенное ему в последней итерации цикла.
что вам нужно для каждого другой метод anon для захвата другой переменной. Вы можете сделать это, создав новый кадр стека при генерации каждого другого метода anon.
function GetWrapper(F: Foo): TNotifyEventWrapper;
begin
Result := TNotifyEventWrapper.Create(
procedure(Sender: TObject)
begin
ShowMessage(F.Bar + ...);
end
);
end;
потому что аргумент функции GetWrapper
является локальной переменной в кадре стека этой функции, каждый вызов GetWrapper
создает новый экземпляр этой локальной переменной.
вы можете разместить GetWrapper
куда вам угодно. Как вложенная функция в FormCreate
, или как частный метод,или в единичной области.
затем создайте свои меню следующим образом:
F := TFoo.Create;
F.Bar := 'Hello World!';
F.Val := I;
NE := GetWrapper(F);
NE.I := I;
связанные чтения: