2015-03-20 15 views
8

Mi piacerebbe essere in grado di generare dinamicamente i menu popup in pascal.Assegnazione dinamica di funzioni anonime in pascal

Vorrei anche essere in grado di assegnare dinamicamente i gestori di OnClick a ciascuna voce di menu.

Questo è il tipo di cosa che sono abituato a fare in C#, questo è il mio tentativo in pascal.

La voce di menu onClick gestore eventi deve appartenere a un oggetto (of Object), quindi creare un oggetto contenitore per questo.

Ecco il mio codice:

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

Cliccando numero della voce di menu 6

Il programma mostra il messaggio previsto

Menu6

Tuttavia il messaggio successivo era no t mostra il risultato atteso.

Invece di 6 si mostra voce 10

Menu10

Non importa quale elemento dell'elenco clicco su, tutti sembrano sparare il gestore di eventi per l'ultimo elemento della lista (10).

Mi è stato suggerito che la procedura membro dell'oggetto NEEvent sia lo stesso indirizzo di memoria per tutte le istanze di tale oggetto.

Qualunque voce di menu clicco su, l'indirizzo di memoria MI.OnClick è lo stesso.

+0

Mi chiedo se ho trovato un bug/limitazione del delphi – sav

+1

No, non l'hai fatto. Non hai ancora compreso appieno una sfumatura di cattura variabile. Cattura le variabili piuttosto che i valori. –

+0

Si noti che il codice perde. Presumo che tu sappia questo e abbia un piano per affrontarlo più tardi. –

risposta

7

La chiave per comprendere questo è capire che la cattura variabile acquisisce le variabili anziché .

I metodi anon catturano tutti la stessa variabile F. Esiste solo un'istanza di tale variabile poiché FormCreate viene eseguita una sola volta. Questo spiega il comportamento. Quando i metodi anon eseguono la variabile F, il valore è assegnato ad esso nell'iterazione del ciclo finale.

Quello che ti serve è un metodo diverso per acquisire una variabile diversa. Puoi farlo creando un nuovo stack frame durante la generazione di ogni diverso metodo anon.

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

Poiché l'argomento della funzione GetWrapper è una variabile locale nel frame dello stack quella funzione, ogni chiamata GetWrapper crea una nuova istanza di tale variabile locale.

È possibile inserire GetWrapper dove si desidera. Come funzione annidata in FormCreate, o come metodo privato o nell'ambito di unità.

quindi creare il menu in questo modo:

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

lettura correlati:

+0

Dato che ho inserito il costruttore 'F: = TFoo.Create' in un ciclo, mi sarei aspettato che ci fossero 10 istanze di TFoo. Cosa ho perso – sav

+1

Ci sono 10 istanze di oggetti 'TFoo' ma solo una istanza della variabile' F'. Ricorda che 'F' è semplicemente un puntatore. I tuoi metodi anon catturano la variabile. Quando i metodi anon vengono eseguiti, allora 'F' punta all'ultimo TFoo' creato. –

+0

Oh, penso di averlo capito. Utilizza lo stesso riferimento all'oggetto sulla pila. – sav