Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically assigning anonymous functions in pascal

I would like to be able to dynamically generate popup menus in pascal.

I would also like to be able to dynamically assign OnClick handlers to each menu item.

This is the sort of thing that I am used to being able to do in C#, this is my attempt in pascal.

The menu item onClick event handler needs to belong to an object (of Object) so I create a container object for this.

Here is my code:

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.

MenuItems

On clicking menu item number 6

The program shows the expected message

Menu6

However the next message was not showing the expected result.

Instead of 6 it shows item 10

Menu10

No matter which item in the list I click on, they all seem to fire the event handler for the last item in the list (10).

It has been suggested to me that the NE object's member procedure Event is the same memory address for all instances of that object.

Whichever menu item I click on, the memory address MI.OnClick is the same.

like image 785
sav Avatar asked Mar 20 '15 05:03

sav


1 Answers

The key to understanding this is to understand that variable capture captures variables rather than values.

Your anon methods all capture the same variable F. There's only one instance of that variable since FormCreate only executes once. That explains the behaviour. When your anon methods execute the variable F has the value assigned to it in the final loop iteration.

What you need is for each different anon method to capture a different variable. You can do this by making a new stack frame when generating each different anon method.

function GetWrapper(F: Foo): TNotifyEventWrapper;
begin
  Result := TNotifyEventWrapper.Create(
    procedure(Sender: TObject)
    begin
      ShowMessage(F.Bar + ...);
    end
  );
end;

Because the argument to the function GetWrapper is a local variable in that function's stack frame, each invocation of GetWrapper creates a new instance of that local variable.

You can place GetWrapper where you please. As a nested function in FormCreate, or as a private method, or at unit scope.

Then build your menus like this:

F := TFoo.Create;
F.Bar := 'Hello World!';
F.Val := I;
NE := GetWrapper(F);
NE.I := I;

Related reading:

  • http://docwiki.embarcadero.com/RADStudio/en/Anonymous_Methods_in_Delphi#Variable_Binding_Mechanism
  • Anonymous methods - variable capture versus value capture
  • http://blogs.embarcadero.com/abauer/2008/10/15/38876
like image 102
David Heffernan Avatar answered Oct 24 '22 23:10

David Heffernan