2015-03-20 18 views
8

Ich möchte Popup-Menüs in Pascal dynamisch erstellen können.Dynamische Zuweisung anonymer Funktionen in Pascal

Ich würde auch gerne in der Lage sein, OnClick-Handler zu jedem Menüpunkt dynamisch zuzuweisen.

Dies ist die Art von Dingen, die ich in C# tun kann, das ist mein Versuch in Pascal.

Der Menüpunkt onClick-Event-Handler muss zu einem Objekt gehören (of Object), also erstelle ich ein Container-Objekt dafür.

Hier ist mein 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

Menüpunktnummer 6

Das Programm zeigt die erwartete Nachricht

Menu6

jedoch die nächste Nachricht Auf klicken war nein t zeigt das erwartete Ergebnis.

Statt 6 zeigt es Artikel 10

Menu10

Egal, welches Element in der Liste mich auf, sie alle scheinen den Event-Handler für das letzte Element in der Liste (10) zu schießen.

Es wurde mir vorgeschlagen, dass die Mitgliedsprozedur NE des Objekts Event die gleiche Speicheradresse für alle Instanzen dieses Objekts ist.

Egal welchen Menüpunkt ich anklicke, die Speicheradresse MI.OnClick ist gleich.

+0

Ich frage mich, ob ich einen Fehler/Einschränkung von Delphi – sav

+1

Nein haben Sie nicht gefunden haben. Sie haben die Nuance der variablen Erfassung noch nicht vollständig verstanden. Es erfasst Variablen und nicht Werte. –

+0

Beachten Sie, dass Ihr Code undicht ist. Ich nehme an, Sie wissen das und haben einen Plan, um das später anzugehen. –

Antwort

7

Der Schlüssel dies zu verstehen, ist zu verstehen, dass variable Erfassung Variablen statt Werte einfängt.

Ihre Anon-Methoden erfassen alle die gleiche Variable F. Es gibt nur eine Instanz dieser Variablen, da FormCreate nur einmal ausgeführt wird. Das erklärt das Verhalten. Wenn Ihre Anon-Methoden ausgeführt werden, hat die Variable F den Wert, der ihr in der letzten Schleifeniteration zugewiesen wurde.

Was Sie brauchen, ist für jede unterschiedliche Anon-Methode, um eine andere Variable zu erfassen. Sie können dies tun, indem Sie beim Generieren jeder anderen Anon-Methode einen neuen Stapelrahmen erstellen.

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

Da das Argument der Funktion GetWrapper eine lokale Variable des Stapelrahmens in dieser Funktion ist, jeder Aufruf von GetWrapper erzeugt eine neue Instanz der lokalen Variablen.

Sie können GetWrapper wo Sie bitte platzieren. Als verschachtelte Funktion in FormCreate, oder als private Methode oder im Einheitsbereich.

bauen dann Ihre Menüs wie folgt aus:

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

Related reading:

+0

Da ich den Konstruktor 'F: = TFoo.Create' in eine Schleife gesetzt habe, hätte ich erwartet, dass es 10 Instanzen von TFoo gibt. Was habe ich verpasst? – sav

+1

Es gibt 10 Instanzen von 'TFoo'-Objekten, aber nur eine Instanz der Variablen' F'. Denken Sie daran, dass 'F' einfach ein Zeiger ist. Ihre Anon-Methoden erfassen die Variable. Wenn die Anon-Methoden ausgeführt werden, zeigt "F" auf den zuletzt erstellten "TFoo". –

+0

Oh, ich denke ich verstehe es. Es verwendet die gleiche Objektreferenz auf dem Stapel. – sav