Simulierte Prozedur: E2004 Bezeichner redeklariert: "Self"

    Simulierte Prozedur: E2004 Bezeichner redeklariert: "Self"

    Hallo,

    habe das Thema Methodenzeiger in Angriff genommen (Quelle: hier) und unten stehenden Code erfolgreich getestet:

    Delphi-Code

    1. ​procedure SimulatedMethod(Self: TForm1; Sender: TObject);
    2. begin
    3. Self.Caption := 'Du hast mich aufgerufen';
    4. end;
    5. procedure TForm1.FormCreate(Sender: TObject);
    6. var
    7. Event: TNotifyEvent;
    8. begin
    9. TMethod(Event).Code := @SimulatedMethod;
    10. TMethod(Event).Data := Self;
    11. Button1.OnClick := Event;
    12. end;


    Jetzt würde ich gerne von der Prozedur SimulatedMethod auf Komponenten und Variablen des Hauptprogramms zugreifen und habe dann einfach mal

    Quellcode

    1. TForm1.procedure SimulatedMethod(Self: TForm1; Sender: TObject);

    aus

    Quellcode

    1. procedure SimulatedMethod(Self: TForm1; Sender: TObject);

    gemacht, was den Fehler E2004 Bezeichner redeklariert: "Self" generiert.

    Wie kann ich dennoch auf Komponenten und Variablen des Hauptprogramms zugreifen?


    LZ
    Moin... 8o
    Zum Beispiel ist es möglich eine gewöhnliche Prozedur als Methode zu missbrauchen

    ...was hast du denn eigentlich vor? Wo steckt der Sinn dahinter? :whistling:

    Zur Frage:
    TForm1.procedure SimulatedMethod(Self: TForm1; Sender: TObject);

    Das ist keine Methode einer Klasse (in diesem Falle Form1). Du übergibst der Methode mit dem Parameter SELF die Instanz von Form1.
    procedure SimulatedMethod(Self: TForm1; Sender: TObject);

    ...Self als Parametername und TForm1 als Parameter finde sehr bedenklich. 8|

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „haentschman“ ()

    Hallo haentschman,

    es geht hier um ein 3D-Brettspiel, wo die Figuren per Würfeln gezogen werden.

    Dabei habe ich mehrere Klassen im Einsatz, wie z.B.:
    Eine Klasse für das Würfeln: Beim Klick auf den Würfel rotiert dieser zufällig und am Ende der Animation wird die Zahl festgestellt.
    Eine Klasse für das Spielfeld: Dort werden zunächst Figuren und danach das Zielfeld angeklickt. Wenn man z.B. versucht, eine Figur zu überspringen oder auf andere Weise ungültig zieht, wird dieser Status in einer Variablen festgehalten.
    Eine Klasse für die Auswahl der Spielfigur (Figurenleiste). Klickt man auf die Figur, erscheint die Farbe der Figur, Klickt man wieder darauf, verschwindet sie wieder.
    Eine Klasse rund um die Daten der Spieler, also Spielstände, wer am Zug ist, usw.

    Die Klassen sind in einer eigenen Unit eingebunden.
    Erwähnenswert ist noch, dass der Typ des Spielers festgelegt werden kann, ob manuell oder per KI gesteuert.

    Hier der Code, um den es tatsächlich geht:

    Delphi-Code

    1. procedure SimulatedMethod_Figurenleiste_Aufstellung(Self: TDashlock3D_F; Sender: TObject);
    2. begin
    3. Figurenleiste.OnClick(Sender);
    4. If fNr=blabla//hier möchte ich auf eine Variable im Hauptprogramm zugreifen, welche aber nicht erkannt wird
    5. end;


    Hatte zuvor einen Timer im Einsatz, der bis auf Ereigniseintritt gepollt hat, aber das war für mich extrem fragwürdiger Stil.

    @Slipstream: Ja, die Anwendung habe ich mit Seattle erstellt.
    Das wäre sehr schade, wenn man nicht auf die Unit-Klasse zugreifen kann.

    LZ

    ZYLAGON schrieb:


    @Slipstream: Ja, die Anwendung habe ich mit Seattle erstellt. Das wäre sehr schade, wenn man nicht auf die Unit-Klasse zugreifen kann.


    Deiner Simulation-Procedure ist die Instanz deiner Formular-Klasse nicht bekannt, deshalb kann die darauf auch nicht zugreifen. Du verwendest den Bezeichner Self in deiner Simultan-Procedure, der ist aber bereits vergeben, weil damit die Instanz deines Formular-Klasse bezeichnet wird. Mir ist der Sinn dieser Simultan-Procedure nicht klar, auch mit deinem Spiel-Projekt kann ich da keinen Zusammenhang erkennen.

    Wenn du Klassen entwickelt hast, um damit die einzelnen Objekte deines Spiels zu kapseln, dann verwendest du diese Klassen, indem du Instanzen dieser Klassen-Objekte in der Hauptunit erzeugst: MeineKlasse := TMeineKlasse.Create; und beim Programmende wieder freigibst: MeineKlasse.Free;

    Auf die Methoden deiner Klasse greifst du durch Voranstellen des Instanzbezeichners zu: MeineKlasse.MeineMethode(Bezeichner: Integer); oder sonstwas.

    Nehmen wir als Beispiel deine Würfelklasse:

    Delphi-Code

    1. unit Wuerfeln;
    2. interface
    3. uses
    4. ...;
    5. Type
    6. TWuerfel = class
    7. private { Private-Deklarationen }
    8. public { Public-Deklarationen }
    9. Constructor Create();
    10. Destructor Destroy; override;
    11. Function GetWuerfelResult : Integer;
    12. Implementation
    13. { TWuerfel }
    14. Constructor TWuerfel.Create;
    15. begin
    16. inherited;
    17. randomize;
    18. end;
    19. Destructor TWuerfel.Destroy;
    20. begin
    21. inherited;
    22. end;
    23. Function TWuerfel.GetWuerfelResult : Integer;
    24. begin
    25. ... blabla ... Randombasierte Ermittlung
    26. Result := ...
    27. end;


    Das könnte im Grunde deine Würfelklasse sein. In der Hauptunit legst du damit eine Instanz an:

    Delphi-Code

    1. ...
    2. Implementation
    3. Var
    4. Wuerfe : TWuerfel;
    5. Procedure TForm1.FormCreate(Sender: TObject);
    6. begin
    7. Wuerfe := TWuerfel.Create; // ab dem Moment hast du Zugriff auf die Public-Methoden deiner Klasse TWuerfel
    8. end;


    ... und gibst sie im OnDestroy wieder frei:

    Delphi-Code

    1. Procedure TForm1.FormDestroy(Sender: TObject);
    2. begin
    3. Wuerfe.Free;
    4. end;


    Jetzt kannst du würfeln, zumindest rein rechnerisch. Wofür benötigst du jetzt aber diese Simultan-Procedure?
    Der verlinkte Beispielcode zeigt, wie Methoden intern funktionieren. Wenn man nun aus der regulären Prozedur tatsächlich eine Methode macht, ist das doppelt gemoppelt und führt zu Fehlern. Allerdings frage ich mich bislang, was damit eigentlich bezweckt werden soll. Möchtest Du dynamisch erzeugten Instanzen irgendwelche EventHandler zuweisen? Das geht auch sehr viel einfacher.
    10 Minuten Nachdenken ersparen oftmals 10 Stunden Fehlersuche.
    Moin... 8o
    was damit eigentlich bezweckt werden soll

    ...das hab ich schon in 2. Beitrag gesagt. :whistling: Ich vermute das du nach einer Lösung suchst, die die Unzulänglichkeiten des Programmdesigns ausgleichen soll. (jede Klasse kennt jeden und jedes Control) Sorry... :/
    So sollte es sein (sinngemäß):
    Die GUI kennt nur die Logik, die Logik kennt niemanden.
    1. die GUI erzeugt die Logik
    2. die GUI gibt der Logik Befehle (Taste Würfeln ist gedrückt -> Würfeln)
    entweder...
    3. die GUI "horcht" auf die Logik mit Eventhandlern
    4. die GUI empfängt das Event "NachDemWürfeln" mit dem Würfelergebnis und führt die Anweisungen aus.
    oder...
    3. die GUI führt die function "NachDemWürfeln" mit dem Würfelergebnis und führt die Anweisungen aus.
    ...und so weiter.

    Schau mal auf dem Papier wie deine Klassen "Informationen" vom wem verarbeiten, welche Klassen sich kennen müssen und wie du diesen "Kreislauf" durchbrechen kannst.
    Möchtest Du dynamisch erzeugten Instanzen irgendwelche EventHandler zuweisen?

    ...das ist eine der Möglichkeiten für eigene Events. :thumbup:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „haentschman“ ()

    @Slipstream:

    Gut beschrieben, so habe ich das auch programmiert.
    Genauso in der Art sieht meine Klasse aus, wo gewürfelt wird.
    Die Hauptunit erzeugt und gibt die Instanz wieder frei.
    Ich kann auf die Klasse zugreifen.

    Nun habe ich aber den Fall, dass die Klasse eine ihrer Prozeduren ausführt, jedoch Daten von außerhalb benötigt.

    Beispiel:
    Die oben erwähnte Spielfeld-Klasse prüft, ob der Klick auf eines seiner Felder korrekt ist oder nicht.
    Das funktioniert so weit.
    Als nächstes soll sich der Würfeltisch wieder drehen, welcher den nächsten Spieler auffordert, auf den Würfel zu drücken.
    Die Spielfeld-Klasse kümmert sich aber nur um den Vorgang, ob korrekt gedrückt wurde.
    Aber erst wenn korrekt gedrückt wurde, darf sich der Würfeltisch drehen.
    Deshalb hatte ich angenommen, die Kontrolle in der Hauptunit wiedererlangen zu müssen.

    Hier beginnt jetzt wohl mein Irrtum:
    Mit der Klasse kann ich nur den Code in der Klasse ausführen.
    Mit der simulierten Methode kann ich den Klick in der Spielfeld-Klasse und zusätzlichen Code des Hauptprogramms ausführen, wie z.B. die Hauptprogrammkomponente TCylinder zu drehen.

    @DeddyH:
    Ich warte also auf ein Ereignis der Klasse, welches im Hauptprogramm einen Prozess anstoßen soll.

    @haentschman:
    Wie sähe der Punkt 4 aus, wo die GUI das Event empfängt und Anweisungen ausführt?


    LZ
    Hallöle... 8o
    Im Anhang ein Beispiel für Events. Die Form selbst nimmt sich aus dem Event das Ergebnis und stellt es dar. Der Logik ist es wurscht wer das Ergebnis entgegenimmt. Die Logik kennt kein visuelles Control. Ob das Ergebnis später in einem Edit oder einem Label steht ist völlig egal.

    Wenn du Fragen hast...wir sind da. :thumbsup:

    Frage:
    Im Sender steht das Objekt (Klasse) welches das Event ausgelöst hat. Mit einem "cast" kommen wir wieder auf TMeineKlasse...wenn man es braucht. 8)

    Für alle die das Projekt nicht laden können:

    Delphi-Code

    1. unit Unit1;
    2. interface
    3. uses
    4. Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
    5. Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
    6. Unit2;
    7. type
    8. TForm1 = class(TForm)
    9. btn1: TButton;
    10. lbl1: TLabel;
    11. procedure FormDestroy(Sender: TObject);
    12. procedure FormCreate(Sender: TObject);
    13. procedure btn1Click(Sender: TObject);
    14. private
    15. FMeineLogik: TMeineKlasse; // Name egal
    16. procedure SummeFertschHandler(Sender: TObject; Ergebnis: Integer); // Name egal, Parameter identisch wie TMeineKlasseSummeEvent
    17. public
    18. end;
    19. var
    20. Form1: TForm1;
    21. implementation
    22. {$R *.dfm}
    23. procedure TForm1.FormCreate(Sender: TObject);
    24. begin
    25. FMeineLogik := TMeineKlasse.Create;
    26. FMeineLogik.SummeFertsch := SummeFertschHandler; // hier wird das Event "eingehängt"
    27. end;
    28. procedure TForm1.FormDestroy(Sender: TObject);
    29. begin
    30. FMeineLogik.Free;
    31. end;
    32. procedure TForm1.SummeFertschHandler(Sender: TObject; Ergebnis: Integer);
    33. begin
    34. lbl1.Caption := IntToStr(Ergebnis);
    35. end;
    36. procedure TForm1.btn1Click(Sender: TObject);
    37. begin
    38. FMeineLogik.Rechnen(1, 2);
    39. end;
    40. end.

    Delphi-Code

    1. unit Unit2;
    2. interface
    3. type
    4. TMeineKlasseSummeEvent = procedure(Sender: TObject; Ergebnis: Integer) of object;
    5. TMeineKlasse = class
    6. strict private
    7. FSummeFertsch: TMeineKlasseSummeEvent;
    8. public
    9. property SummeFertsch: TMeineKlasseSummeEvent read FSummeFertsch write FSummeFertsch; // Name egal
    10. procedure Rechnen(Wert1, Wert2: Integer); // Name egal
    11. end;
    12. implementation
    13. { MeineKlasse }
    14. procedure TMeineKlasse.Rechnen(Wert1, Wert2: Integer);
    15. var
    16. Summe: Integer;
    17. begin
    18. Summe := Wert1 + Wert2;
    19. if Assigned(SummeFertsch) then // Sicherheit: Prüfung ob das Event zugeordnet ist.
    20. begin
    21. SummeFertsch(Self, Summe); // hier löst die Klasse das Event aus. Der Klasse ist es egal ob jemand darauf "horcht"
    22. end;
    23. end;
    24. end.

    Dateien
    • Events.zip

      (53,8 kB, 49 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „haentschman“ ()

    ZYLAGON schrieb:

    Gut beschrieben, so habe ich das auch programmiert. Genauso in der Art sieht meine Klasse aus, wo gewürfelt wird. Die Hauptunit erzeugt und gibt die Instanz wieder frei. Ich kann auf die Klasse zugreifen.

    Danke, freut mich, dass wir ein Verständnisebene finden. Das ist die einfachste oder auch die Einsteigerform einer Klasse. Mit Interfaces und anderem Brimbum willst du dich vorlaufig nicht herumschlagen, denke ich.

    ZYLAGON schrieb:

    Nun habe ich aber den Fall, dass die Klasse eine ihrer Prozeduren ausführt, jedoch Daten von außerhalb benötigt.

    In diesem Fall übergibt man der Klasse diese benötigten Daten, zB als Parameter einer Function oder einer Procedure. Man kann in einer Klasse aber auch Properties deklarieren und diese dann von aussen mit Daten füllen.

    ZYLAGON schrieb:

    Beispiel:
    Die oben erwähnte Spielfeld-Klasse prüft, ob der Klick auf eines seiner Felder korrekt ist oder nicht. Das funktioniert so weit. Als nächstes soll sich der Würfeltisch wieder drehen, welcher den nächsten Spieler auffordert, auf den Würfel zu drücken. Die Spielfeld-Klasse kümmert sich aber nur um den Vorgang, ob korrekt gedrückt wurde. Aber erst wenn korrekt gedrückt wurde, darf sich der Würfeltisch drehen. Deshalb hatte ich angenommen, die Kontrolle in der Hauptunit wiedererlangen zu müssen.

    Die Methode deiner zu erzeugenden Klasse, die zB das Würfeln erledigt, kann ja eine Function sein, die den gewürfelten Wert zurückgibt, wie ich das in meinem Beispiel angedeutet habe. Wieso sollte dann eine Methode in deiner Spielfeldklasse, die prüft, ob richtig gedrückt wurde, kein True oder False zurückgeben können? Und was meinst du damit, die Kontrolle in der Hauptunit wiedererlangen zu müssen? Was meinst du mit Kontrolle überhaupt? Der Code controlliert das Spiel, und der Anwender kontrolliert einen Teil des Programmablaufs.

    ZYLAGON schrieb:

    Hier beginnt jetzt wohl mein Irrtum:
    Mit der Klasse kann ich nur den Code in der Klasse ausführen. Mit der simulierten Methode kann ich den Klick in der Spielfeld-Klasse und zusätzlichen Code des Hauptprogramms ausführen, wie z.B. die Hauptprogrammkomponente TCylinder zu drehen.

    Ich verstehe das nicht. Du hast eine Spielfeldklasse, die genauso aufgerufen wird die meine Beispiel-Wuerfelklasse. Der Anwender drückt einen Button und ein OnClick des Button entsteht, das du im OnClickhandling des Buttons bearbeitest. Von dieser OnClickProcedure aus rufst du in deiner Spielfeldklasse die Methode auf, die prüft, ob richtig gedrückt wurde, was immer du da zu prüfen hast. Die Spielfeldklasse ist nur eine Art Erweiterung der Form1-Klasse, die Spielfeldklasse kapselt alle Berechnungen die im Zusammenhang mit dem Spielfeld gebraucht werden. Die Form1-Klasse, das ist ja auch die GUI-Klasse, reagiert nur darauf, was die anderen Klassen ihr zurückliefern. Aber nochmal, ich verstehe dein Problem noch nicht.
    Danke an alle für die Mühe und Hilfe.
    Das hat mich jetzt mit Siebenmeilenstiefeln vorangebracht.
    Procedure of object hatte ich zwar schon mal im Vorfeld gegoogelt, konnte das aber noch nicht verwerten.
    Vielen Dank nochmal für das Beispiel, welches mir die Augen geöffnet hat.

    LZ