![]() |
Objekt Kommunikation
Hallo Delphianer,
Eigentlich dachte ich relativ fit zu sein in delphi, scheitere aber nun an einer trivialen Sache ... Wie kann ich elegant und effizient Informationen zwischen zwei Objekten austauschen, ohne daß diese sich kennen. Jedes Objekt hat eine eigene Unit. Sie sollen sich NICHT gegenseitig über uses einbinden Zitat:
|
Re: Objekt Kommunikation
Ein Event ist die Lösung. z.B.
Delphi-Quellcode:
und in UnitGUI:
type
TObjectData = class private FNotify : TNotifyEvent; public property OnNotify : TNotifyEvent read FNotify write FNotify; procedure NotifyGUI; end; implementation procedure TObjectData.NotifyGUI; begin if Assigned (FNotify) then FNotify (Self); end;
Delphi-Quellcode:
type TObjectGUI = class private Data : TObjectData; procedure OnNotify (Sender : TObject); public Constructor Create; end; implementation constructor TObjectGUI.Create; begin Data := TObjectData.Create; Data.OnNotify := OnNotify; end; procedure OnNotify (Sender : TObject); begin { Wird aufgerufen wenn in TObjectData NotifyGUI durchlaufen wird. } end; |
Re: Objekt Kommunikation
Zitat:
Um ein Oberflächenobjekt von einem Datenobjekt zu benachrichtigen, dass sich am Objektzustand etwas geändert hat, kann man in Delphi mit Eventhandlern arbeiten. Das Datenobjekt würde dazu einen Eventhandler bereitstellen, den es aufruft wenn es eine Nachricht weitergeben möchte. |
Re: Objekt Kommunikation
Okay wie wäre es mit einem bestimmten Vorfahrentyp? Du kannst doch eine abstrakte Klasse in UnitData erstellen, die z.B. UpdateFunktionen vorschreibt. Das TObjectGUI muss nun von dieser Klasse abgeleitet sein und überschreibt die abstrakte Methode. Dann hat die Unit UnitData nichts mit dem TObjectGUI zu tun, aber kann trozdem davon ausgehen, dass es die Funktion UpdateData gibt. :roll:
Delphi-Quellcode:
type
TObjectDataGUI = class public procedure UpdateData(...); virtual; abstract; end; TObjectData = class private FGUI : TObjectDataGUI; public property GUI : TObjectDataGUI read FGUI; ... end;
Delphi-Quellcode:
type
TObjectGUI = class(TObjectDataGUI) public procedure UpdateData(...); override; end; |
Re: Objekt Kommunikation
Für sowas gibt es Interfaces.
Du definierst irgendwo ein Interface, welches die Methoden zur kommunikation definiert. Diese Implementierst Du in Deiner Klasse. Die andere Klasse kann dann eine instanz dieser Klasse über das Interface benutzen, ohne die konkrete Implementierung (also die Unit) zu kennen. Beide müssen halt die Unit referenzieren, in der das interface daklariert ist. |
Re: Objekt Kommunikation
Zitat:
|
Re: Objekt Kommunikation
Hallo Delphianer,
Das Problem war komplizierter als erwartet. Eure Vorschläge hatte ich im Prinzip schon teilweise ausprobiert, war dabei aber auf die Nase gefallen. Es hatte nicht funktioniert, was eigentlich hätte gehen sollen. Ich hatte aber etwas wesentliches Unterschlagen ... Ein DataObject soll nämlich auch mehr als ein GUI-Elemente "benachrichtigen". Hier hatte ich den Fehler gemacht, im DataObject pro "Entfernter ObjectMethode" einen 4Byte Zeiger in einer Liste zu speichern. Das geht so natürlich nicht, da Object-Methoden versteckte DoppelZeiger sind. Folgende Lösung scheint zu funktionieren.
Delphi-Quellcode:
...
uData.pas
type TOccupiedDataEvent = procedure (Sender: TObject; aODE:integer; const aMessage: string) of Object; type TOccupiedData = Class(TObject) ... protected FOccupiedGrids : TStringList; FOccupiedDataEvent: TOccupiedDataEvent; procedure OccupiedDataEventSend(aODE:integer; const aMessage: string); ... public function OccupiedDataEventConnect(aODE:TOccupiedDataEvent):integer; function OccupiedDataEventDisconnect(aODE:TOccupiedDataEvent):integer; function TOccupiedData.OccupiedDataEventConnect { >>>>>>>>>>>>>>>>>>>>>>>>>>>> } (aODE:TOccupiedDataEvent):integer; var i1:integer; p2:^TOccupiedDataEvent; p3:^TMethod; begin // local init Result := 0; i1 := 0; p2 := Nil; p3 := Nil; // local main if Assigned(aODE) then begin // Suchen ob GUI-Element bereits verlinkt ist For i1 := 0 to FOccupiedGrids.Count-1 do begin p3 := Pointer(FOccupiedGrids.Objects[i1]); if (p3<>NIL) then begin if (p3^.Code = TMethod(aODE).Code) and (p3^.Data = TMethod(aODE).Data) then begin // bereits vorhanden p2 := @aODE; break; end; end; end; if (p2=Nil) then begin New(p3); p3^ := TMethod(aODE); FOccupiedGrids.AddObject('S',TObject(p3)); aODE(Self, odeConnected, 'Connected'); end; end; end; { <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< } function TOccupiedData.OccupiedDataEventDisconnect { >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } (aODE:TOccupiedDataEvent):integer; var i1:integer; p2:^TOccupiedDataEvent; p3:^TMethod; begin // local init Result := 0; i1 := 0; p2 := Nil; p3 := Nil; // local main if Assigned(aODE) then begin // Suchen ob GUI-Element bereits verlinkt ist For i1 := FOccupiedGrids.Count-1 downto 0 do begin p3 := Pointer(FOccupiedGrids.Objects[i1]); if (p3<>NIL) then begin if (p3^.Code = TMethod(aODE).Code) and (p3^.Data = TMethod(aODE).Data) then begin // gefunden! // ... aus liste entfernen FOccupiedGrids.Delete(i1); // ... dynamischen speicher wieder freigeben Dispose(p3); p3 := NIL; // notification an ODE senden aODE(Self, odeDisConnected, 'DisConnected'); break; end; end; end; end; end; { <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< } procedure TOccupiedData.OccupiedDataEventSend { >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } (aODE:integer; const aMessage: string); var i1:integer; p2:^TOccupiedDataEvent; p3:^TMethod; begin // local init i1 := 0; p2 := Nil; p3 := Nil; // local main // Entfernte ObjectMethode aufrufen For i1 := 0 to FOccupiedGrids.Count-1 do begin p3 := Pointer(FOccupiedGrids.Objects[i1]); if (p3<>NIL) then begin Pointer(p2) := p3; p2^(Self, aODE,aMessage); end; end; end; { <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< }
Delphi-Quellcode:
uGui.pas
... DataObject.OccupiedDataEventConnect(OccupiedDataEvent); ... procedure TOccupiedGrid.OccupiedDataEvent { >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } (Sender: TObject; aODE:integer; const aMessage: string); begin CASE aODE of odeDataLoaded :begin if Assigned(FGrid) then begin FGrid.Invalidate; end; end; END; end; { <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< } |
Re: Objekt Kommunikation
Hallo Leute,
in diesem Threat wird gefragt und beantwortet, womit ich mich gerade herumschlage. Aber leider will es mir nicht gelingen, das Projekt ans Laufen zu kriegen. Folgende Aufgabenstellung: Ich entwickle ein Spiel, in dem man (sehr vereinfacht ausgedrückt) mit Raumschiffen rumfliegen und auf andere Schiffe ballern können soll. Da ich das Spiel vollständig objektorientiert programmieren will, muss ich mich mit der Frage beschäftigen, was genau passiert, wenn eine Einheit_1 auf eine Einheit_2 schießt. Ich habe - hierfür relevant - folgende Klassenstruktur: TSpiel - TSpieler - TEinheit - TWaffe Die Waffe soll nichts von den Einheiten wissen. Sie weiß nur, dass sie eine Waffe ist und was sie braucht um loszuballern. Die schießende Einheit soll nichts von der anvisierten Einheit wissen. Sie weiß nur das sie eine Einheit (ein Schiff) ist, eine Waffe hat und diese in irgend eine Richtung hin abfeuern soll. Der Spieler ist derjenige, der das in die Wege leitet und das Spiel, soll über ein Event erfahren, dass eine Einheit_1 in Richtung einer Einheit_2 geschossen hat. Das Spiel soll ermitteln, ob Einheit_1 einen Treffer landen kann und wenn ja, dies mit einem Event der Einheiten 2 mitteilen. Synonym zur ersten Antwort ganz oben von fajac habe ich das folgendermaßen umgesetzt:
Delphi-Quellcode:
type
TSpiel = class private _Spieler : TSpieler; _sName : String; _pVirtuelleWaffe : TWaffe; procedure OnWaffeAbfeuernEvent (Sender : TObject; koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie:Integer); public constructor Create; implementation constructor TSpiel.Create; begin _pVirtuelleWaffe := TWaffe.Create('Waffe_1', TRUE, 0, 0, 100); _pVirtuelleWaffe.OnWaffeAbfeuernEvent := OnWaffeAbfeuernEvent; _Spieler := TSpieler.Create(iMENSCH, 0, 'Frank', clYellow); _sName := 'TestSpiel'; end; procedure TSpiel.OnWaffeAbfeuernEvent(Sender : TObject; koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); begin // Wird aufgerufen wenn in TWaffe "Feuern" durchlaufen wird. ShowMessage('Jetzt hat das auch mein Spiel bemerkt. Es hat das "OnWaffeAbfeuernEvent" erhalten.'); end;
Delphi-Quellcode:
type
TWaffe = class private _sName : String; _bFunktionsbereit : Boolean; _iMasse : Integer; _iPreis : Integer; _iZustand : Integer; fWaffeAbfeuernEvent: TWaffeAbgefeuert_Event; public constructor Create(sNameDerWaffe: String; bWaffeFunktionsbereit: Boolean; iMasse: Integer; iNeuPreis: Integer; iProzentZustand: Integer); property OnWaffeAbfeuernEvent: TWaffeAbgefeuert_Event read fWaffeAbfeuernEvent write fWaffeAbfeuernEvent; procedure Feuern(koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); virtual; implementation procedure TWaffe.Feuern(koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); begin //ToDo -oWaffe -cCODE: Waffen.Feuern(Wohin: TKoordinaten; iEnergie: Integer) if Assigned(fWaffeAbfeuernEvent) then begin fWaffeAbfeuernEvent(self, koAktuelleKoordinaten, koZielKoordinaten, iSchussEnergie); end; ShowMessage('Jetzt fängt meine Waffe an, wie wild, in der Gegend herum zu ballern...!'); end; Wie der versierte Delphianer erkennt, möchte ich nach dem Druck auf den Feuerknopf zwei MessageBoxen sehen. In der ersten soll stehen, dass die Waffe wie wild rum ballert und in der zweiten soll stehen, dass das Spiel davon weiß!!
Delphi-Quellcode:
procedure TfrmStartGBH.FeuerButtonClick(Sender: TObject);
var slWaffenListe : TStringList; pWaffe : TWaffe; koStart, koZiel : TKoordinaten; pSpiel : TSpiel; pSpieler : TSpieler; pAktiveEinheit : TEinheit; begin try pSpiel := TSpiel.Create; slWaffenListe := TStringList.Create; pSpiel.GibSpieler(pSpieler); pAktiveEinheit := pSpieler.GibAktiveEinheit; pAktiveEinheit.GibWaffenListe(slWaffenListe); if (not Assigned(slWaffenListe)) or (slWaffenListe.Count = 0) then begin lLogger.LogFmt('TfrmStartGBH.Button1Click: slWaffenliste nicht angelegt oder leer.', [], llError); lLogger.LogFmt('TfrmStartGBH.Button1Click: Lege eine Test-Waffe an.', [], llWarning); pWaffe := TWaffe.Create('Test-Waffe', TRUE, 100, 99, 100); end else begin pWaffe := (slWaffenListe.Objects[0]) as TWaffe; end; koStart := TKoordinaten.Create(0, 0); koZiel := TKoordinaten.Create(1, 1); pWaffe.Feuern(koStart, koZiel, 500); except on e: Exception do begin lLogger.LogFmt(' Exception: %s.', [e.Message], llError); end; end; {*** TfrmStartGBH.FeuerButtonClick ***} end; Tatsächlich erhalte ich aber nur eine Messagebox, nämlich die Vollzugsmeldung meiner Waffe! Das Spiel kriegt davon offenbar nichts mit. Weiß jemand, warum nicht? Schon mal vielen Dank im Voraus. |
Re: Objekt Kommunikation
Delphi-Quellcode:
// Hast du an dieser Stelle mal im Debugger überprüft, welcher Zweig ausgeführt wird? // Falls nämlich der erste, so ist das Event der TWaffe-Instanz nicht gesetzt. if (not Assigned(slWaffenListe)) or (slWaffenListe.Count = 0) then begin lLogger.LogFmt('TfrmStartGBH.Button1Click: slWaffenliste nicht angelegt oder leer.', [], llError); lLogger.LogFmt('TfrmStartGBH.Button1Click: Lege eine Test-Waffe an.', [], llWarning); pWaffe := TWaffe.Create('Test-Waffe', TRUE, 100, 99, 100); end else begin pWaffe := (slWaffenListe.Objects[0]) as TWaffe; end; |
Re: Objekt Kommunikation
Es wird der erste Zweig ausgeführt, ja.
Um das Event zu setzten müsste ich WAS tun? |
Re: Objekt Kommunikation
Delphi-Quellcode:
if (not Assigned(slWaffenListe)) or (slWaffenListe.Count = 0) then begin
lLogger.LogFmt('TfrmStartGBH.Button1Click: slWaffenliste nicht angelegt oder leer.', [], llError); lLogger.LogFmt('TfrmStartGBH.Button1Click: Lege eine Test-Waffe an.', [], llWarning); pWaffe := TWaffe.Create('Test-Waffe', TRUE, 100, 99, 100); // ==> pWaffe.OnWaffeAbfeuernEvent := OnWaffeAbfeuernEvent; // <== end else begin pWaffe := (slWaffenListe.Objects[0]) as TWaffe; end; |
Re: Objekt Kommunikation
So geht es leider nicht:
Im Strukturfenster schreibt Delphi: Zitat:
Zitat:
|
Re: Objekt Kommunikation
Sorry, es muss wohl heißen:
Delphi-Quellcode:
Dazu musst du allerdings die Prozedur TSpiel.OnWaffeAbfeuernEvent public machen.
pWaffe.OnWaffeAbfeuernEvent := pSpiel.OnWaffeAbfeuernEvent;
|
Re: Objekt Kommunikation
Aaaahhh, vielen Dank!
Meine Waffe ballert offenbar mit Über-Lichtgeschwindigkeit. Da mein Spiel die Meldung, dass die Waffe schiesst schon bringt BEVOR die Waffe ihre Meldung bringen kann.... _*grins*_. Super. Ich hoffe, mit dieser Vorlage nun alle Events im Spiel hinzubekommen. Nochmals vieles Dankeschön ! |
Re: Objekt Kommunikation
Also gut, hier nocheinmal zusammenfassend, die Endlösung:
Das verwendete Event findet sich in einer eigenen Unit, die nur Events enthalten wird:
Delphi-Quellcode:
type
TWaffeAbgefeuert_Event = procedure(Sender : TObject; koAktuelleKoordinaten: TKoordinaten; koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer) of Object ; Das SPIEL ist die Unit, in der die gesamte Spiel-Logik untergebracht sein wird. Das heißt, alles was sich bewegt oder mit anderen interagieren will, muss dies über das SPIEL tun:
Delphi-Quellcode:
Die WAFFE ist Teil einer EINHEIT, die einem SPIELER gehört. Sie weiß nur dass sie eine Waffe ist und schießen kann. Das SPIEL muss selbst wissen, was es mit einem solchen Schuss tun muss.
unit uSpiel;
interface uses mEvents, mSpieler, mWaffe, SysUtils; type TSpiel = class private _Spieler : TSpieler; _pVirtuelleWaffe : TWaffe; public constructor Create; procedure OnWaffeAbfeuernEvent (Sender : TObject; koAktuelleKoordinaten: TKoordinaten; koZielKoordinaten: TKoordinaten; iSchussEnergie:Integer); end ; implementation uses Dialogs, Graphics; constructor TSpiel.Create; begin // Das virtuelle Abbild, aller im Spiel möglichen Waffen _pVirtuelleWaffe := TWaffe.Create('Test-Waffe'); _pVirtuelleWaffe.OnWaffeAbfeuernEvent := OnWaffeAbfeuernEvent; // Einen Spieler anlegen. // Dort wird dann auch eine Einheit mit ihren Waffen erzeugt. _Spieler := TSpieler.Create; end; procedure TSpiel.OnWaffeAbfeuernEvent(Sender : TObject; koAktuelleKoordinaten: TKoordinaten; koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); begin // Wird aufgerufen wenn in TWaffe "Feuern" durchlaufen wird. ShowMessage('Da schießt jemand!'); end; end.
Delphi-Quellcode:
Testinstanz in diesem Falle ist die frmStartseite (mein einziges Formular zu diesem Zeitpunkt):
unit uWaffe;
interface uses Classes, mEvents; type TWaffe = class private _sName : String; fWaffeAbfeuernEvent: TWaffeAbgefeuert_Event; public constructor Create(sNameDerWaffe: String); property OnWaffeAbfeuernEvent: TWaffeAbgefeuert_Event read fWaffeAbfeuernEvent write fWaffeAbfeuernEvent; procedure Feuern(koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); virtual; end ; implementation uses Dialogs; constructor TWaffe.Create(sNameDerWaffe: String); begin _sName := sNameDerWaffe; {*** TWaffe.Create ***} end; procedure TWaffe.Feuern(koAktuelleKoordinaten, koZielKoordinaten: TKoordinaten; iSchussEnergie: Integer); begin ShowMessage('Feuer frei!'); if Assigned(fWaffeAbfeuernEvent) then begin fWaffeAbfeuernEvent(self, koAktuelleKoordinaten, koZielKoordinaten, iSchussEnergie); end; ShowMessage('Jetzt fängt meine Waffe an, in der Gegend herum zu ballern...!'); end; end.
Delphi-Quellcode:
unit frmStartseite;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, mSpiel, mSpieler, uToolsFB, mWaffe, uGlobals, mEinheit; type TfrmStartGBH = class(TForm) btnFeuern: TButton; procedure btnFeuernClick(Sender: TObject); procedure FormCreate(Sender: TObject); end; var frmStartGBH: TfrmStartGBH; implementation {$R *.dfm} procedure TfrmStartGBH.btnFeuernClick(Sender: TObject); var pSpiel : TSpiel; pSpieler : TSpieler; pAktiveEinheit : TEinheit; slWaffenListe : TStringList; pWaffe : TWaffe; koStart, koZiel : TKoordinaten; iAnzElem : Integer; begin // Das Spiel anlegen pSpiel := TSpiel.Create; if not Assigned(pSpiel) then exit; // Eine Waffenliste erzeugen slWaffenListe := TStringList.Create; // Die aktive Einheit (z.B. ein Raumschiff) erfragen pSpiel.GibSpieler(pSpieler); pAktiveEinheit := pSpieler.GibAktiveEinheit; // Die Waffen dieser Einheit erfragen // Das Result von "GibWaffenListe" ist die Anzahl ihrer Elemente iAnzElem := pAktiveEinheit.GibWaffenListe(slWaffenListe); if (not Assigned(slWaffenListe)) or (iAnzElem = 0) then begin // slWaffenliste nicht angelegt oder leer --> Eine Waffe anlegen. pWaffe := TWaffe.Create('Test-Waffe', TRUE, 100, 99, 100); pWaffe.OnWaffeAbfeuernEvent := pSpiel.OnWaffeAbfeuernEvent; end else begin // slWaffenliste nicht leer --> Erste Waffe darin benutzen pWaffe := (slWaffenListe.Objects[0]) as TWaffe; end; // Die Koordinaten der schießenden Einheit festlegen koStart := TKoordinaten.Create(0, 0); // Die Koordinaten der Ziel-Einheit festlegen koZiel := TKoordinaten.Create(1, 1); // 500 Energiepunkte in den Schuss legen und FEUER FREI!! pWaffe.Feuern(koStart, koZiel, 500); {*** TfrmStartGBH.btnFeuernClick ***} end; end. |
Re: Objekt Kommunikation
Noch eine Anmerkung am Schluss:
In dieser Zeile des Feuer-Buttons vom Formular
Delphi-Quellcode:
wird es je nach Einstellung in den Projektoptionen evtl. zur Exception kommen:
iAnzElem := pAktiveEinheit.GibWaffenListe(slWaffenListe);
if (not Assigned(slWaffenListe)) or (slWaffenListe.count = 0) then begin Wenn slWaffenListe nicht "assigned" ist, dann kann ich auch nicht mit ".count" darauf zugreifen! Da ich aber in den Projektoptionen unter Compiler die "Vollst. Boolesche Auswertung" deaktiviert habe, wird bei mir die Prüfung einer if-Abfrage beendet, sobald das Gesamt-Ergebnis der Abfrage feststeht! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:01 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz