2012-07-03 13 views
9

Ich habe eine App, die mehrere Formen hat. Alle diese Formen haben ein PopupMenu. Ich erstelle die Menüpunkte programmatisch, alles unter einem gemeinsamen Wurzelmenüpunkt. Ich möchte, dass ALLE Menüpunkte die gleiche Prozedur aufrufen, und der Menüeintrag selbst dient im Grunde als Argument.Methodenzeiger und reguläre Prozedur inkompatibel

Ich hatte dies funktioniert, wenn ich nur ein Formular hatte, diese Funktionalität zu tun. Ich habe jetzt mehrere Formulare, die dies tun müssen. Ich verschiebe meinen gesamten Code in eine gemeinsame Einheit.

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. 

Wenn ich brauche, um meine Pop-up von jeder Form zu nennen, nenne ich mein Top-Level-Verfahren (die in Einheit CommonUnit ist), von jeder Form den Namen des oberen Menüpunkt vorbei auf die oberste Ebene Verfahren in der gemeinsame Einheit.

Ich füge Elemente zu meinem PopupMenu mit Code hinzu.

M1 := TMenuItem.Create(TopMenuItem); 
M1.Caption := FieldByName('NAME').AsString; 
M1.Tag := FieldByName('ID').AsInteger; 
M1.OnClick := BrowseCategories1Click; 
TopMenuItem.Add(M1); 

Ich erhalte eine Fehlermeldung, wenn ich kompiliere. Insbesondere beschwert sich die OnClick-Zeile über

Inkompatible Typen: "Methodenzeiger und reguläre Prozedur".

Ich habe BrowseCategories1Click genau wie zuvor definiert, als ich dies auf einem einzigen Formular getan habe. Der einzige Unterschied besteht darin, dass es jetzt in einer gemeinsamen Einheit und nicht als Teil eines Formulars definiert ist.

Es ist definiert als

procedure BrowseCategories1Click(Sender: TObject); 
begin 
// 
end; 

Was ist der einfachste Weg, dies zu umgehen?

Dank GS

Antwort

2

Eine Lösung ist die Methode OnClick in eine TDataModule zu platzieren.

9

Das ist der Unterschied zwischen einem "Verfahren" und ein "Verfahren des Objekts"

Die OnClick als TNotifyEvent definiert:

type TNotifyEvent = procedure(Sender: TObject) of object;

Sie können kein Verfahren zuweisen zu der wie es der falsche Typ ist. Es muss eine Prozedur des Objekts sein.

14

Sie können Ihre Prozeduren in eine Klasse einbinden. Diese Klasse kann in einer separaten Einheit wie folgt aussehen:

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. 

Und die Aktion zu einem Menüpunkt in einer anderen Einheit zuzuordnen ist genug, um dies zu nutzen:

uses 
    CommonUnit; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick; 
end; 

Update:

Aktualisiert, um Klassenverfahren (anstelle von Objektmethoden) nach Davids Vorschlag zu verwenden. Wenn Sie die Objektmethoden mit der Objektinstanz verwenden möchten, folgen Sie this version des Posts.

+6

Machen Sie es zu einer Klassenprozedur und Sie müssen kein Objekt instanziieren –

+0

Aber was, wenn ich auf den 'self' Parameter (d. H. Das besitzende Formular) innerhalb des Click-Handlers zugreifen möchte? – Johan

9

Sie könnten einer von ihnen wählen:

  1. leite Ihre Formulare von einem gemeinsamen Vorfahren und erklären, das Verfahren darin, so dass es zu Nachkommen vorhanden ist
  2. Verwenden Sie eine globale Instanz einer Klasse (zB Datenmodul) von allen Formen gemeinsamen
  3. ein Verfahren als Fälschung Methode wie folgt verwenden:

procedure MyClick(Self, Sender: TObject); 
begin 
    //... 
end; 

var 
    M: TMethod; 
begin 
    M.Data := nil; 
    M.Code := @MyClick; 
    MyMenuItem.OnClick := TNotifyEvent(M); 
end; 
+2

Ugh! Nicht downvoting, denn technisch * werden * alle funktionieren. Allerdings ist @ TLamas Lösung viel sauberer als alle diese, IMO, besonders wenn Davids Vorschlag einer 'Klassenprozedur' hinzugefügt wird, so dass eine Instanz nicht einmal erstellt werden muss, bevor sie verwendet wird. –

+1

@KenWhite Ich stimme dir vollkommen zu. Ich habe nur versucht, Optionen aufzulisten, die nicht schon von anderen erwähnt wurden. (Ich habe die Klassenmethode vergessen.) –

+0

Diese Methode hat ihre Verwendung ... vorausgesetzt, Sie haben kein Objekt, um den Event-Handler zu hosten. Kommt nicht oft vor, ist aber gelegentlich nützlich! –

18

Ein wenig Hintergrund ...

Delphi hat drei Verfahrenstypen:

  • Standalone oder Einheit-scoped Funktion/Prozedur Zeiger erklärt wie folgt:

    var Func: function(arg1:string):string;
    var Proc: procedure(arg1:string);

  • Methode Zeiger erklärt wie folgt:

    var Func: function(arg1:string):string of object;
    var Proc: procedure(arg1:string) of object;

  • Und da Delphi 2009, anonym (siehe unten) Funktion/Methode Zeiger wie so erklärt:

    var Func: reference to function(arg1:string):string;
    var Proc: reference to procedure(arg1:string);

Standalone Zeiger und Methode Zeiger sind nicht austauschbar. Der Grund dafür ist der implizite Parameter Self, auf den in Methoden zugegriffen werden kann. Das Delphi-Ereignismodell beruht auf der Methode Zeiger, weshalb Sie der Ereigniseigenschaft eines Objekts keine eigenständige-Funktion zuweisen können.

So wird Ihre Event-Handler müssen als Teil einige Klassendefinition definiert werden, jede Klassendefinition den Compiler zu beschwichtigen.

Wie TOndrej vorgeschlagen hat, können Sie den Compiler hacken, aber wenn diese Event-Handler in der gleichen Unit sind, sollten sie sowieso schon miteinander verbunden sein, also können Sie sie auch in eine Klasse einbinden.

Ein weiterer Vorschlag, den ich noch nicht gesehen habe, ist, ein wenig zurückzugehen. Lassen Sie jedes Formular seinen eigenen Event-Handler implementieren, aber diese Handler delegieren die Verantwortung an eine Funktion, die in Ihrer neuen Einheit deklariert ist.

TForm1.BrowseCategoriesClick(Sender:TObject) 
begin 
    BrowseCategories; 
end; 

TForm2.BrowseCategoriesClick(Sender:TObject) 
begin 
    BrowseCategories; 
end; 

unit CommonUnit 

interface 
procedure BrowseCategories; 
begin 
// 
end; 

Dies hat den zusätzlichen Vorteil, dass die Antwort auf den von der Steueraktion des Benutzers zu trennen, der die Aktion ausgelöst hat. Sie könnten leicht die Ereignisbehandlungsroutinen für eine Symbolleistenschaltfläche und ein Popup-Menüelement delegieren für dieselbe Funktion haben.

Welche Richtung Sie wählen, hängt letztlich von Ihnen ab, aber ich warne Sie davor, sich darauf zu konzentrieren, welche Option die Wartbarkeit in Zukunft erleichtern wird und nicht die, die in der Gegenwart am zweckmäßigsten ist.


Anonyme Methoden

Anonyme Methoden sind ein anderes Tier alle zusammen. Ein anonymer Methodenzeiger kann auf eine eigenständige-Funktion, eine Methode oder eine unbenannte inline deklarierte Funktion zeigen. Bei diesem letzten Funktionstyp erhalten sie den Namen anonymous aus. Anonyme Funktionen/Methoden haben die einzigartige Fähigkeit Variablen außerhalb ihres Umfangs zu erfassen erklärt

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; 

Das macht sie die flexibelste der Verfahrenszeigertypen, aber sie haben auch potenziell mehr Overhead. Die Variablenerfassung verbraucht zusätzliche Ressourcen wie Inline-Deklarationen. Der Compiler verwendet eine verdeckte Referenzzählerschnittstelle für Inline-Deklarationen, die einen geringen Overhead hinzufügt.