Einzelnen Beitrag anzeigen

Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#5

Re: Messages zwischen Objekten austauschen?

  Alt 16. Sep 2006, 01:12
Korrekt, .Dispatch() leitet die Message erstmal NUR an das eine Objekt. Aber das ist ja auch kein Problem, denn so ist es konzeptonell vorgesehen. Denn je nachdem WAS du daraus machst kannst du es so implementieren wie es bei Fenster Handles möglich ist.
Hast du zb. nur ein TForm und sendest dem einen Broadcast dann empfängt das auch nur 1 Fenster, nämlich das TForm.

Ergo: willst DU eine Message broadcasten (hm also einfach verteilen) und DU hast die Struktur der Objekt und deren Verwaltung kontrolliert dann bist auch du dafür verantwortlich.

Dazu hast du folgende Möglichkeiten:

1.) das Root Objekt bekommt eine Message an eine Funktion die diese Message verteilt indem es alle .Dispatch() Methoden der verlinkten Objekte aufruft

2.) ein Objekt das NICHT auf eine Message reagiert, also keine dynamsiche Methode enthält die als message Integer_Konstant, deklariert wurde, wird in .Dispatch() die Methode .DefaultHandler() aufrufen. Überschreibst du diese Methode so bekommst du alle Nachrichten die das Objekt selber nicht abarbeitet. In .DefaultHandler() kanst du solche Nachrichten weiter verteilen an verlinkte Objekte.

Message Methoden sind ein grundsätzliches Konzept das auf dynamischen Methoden basiert. Wenn du zb. nicht mit dem Paramter Msg: TMessage arbeiten möchtest dann kannst du sogar deine eigene .Dispatch() Methode coden die eben mit allen möglichen Params arbeiten kann. Eine dynamische Methode in enem Objekt ist im Grunde eine Methode deren Addresse in der DMT=Dynamic Method Table unter einem Index vom Typ Word gespeichert wird. .Dispatch nimmt nun unseren Message-Code, zb. wm_Paint, als Index in diese DMT einer Klasse. Wurde eine Methode in diesem objekt als dynamic wm_Paint; deklariert so steht in der DMT am Index wm_Paint ein Zeiger auf diese dynamische Methode, so einfach. Der Bezeichner "message wm_Paint" ist also gleichzusetzen mit "dynamic wm_Paint" eben nur mit explizerter Angabe der Slotnummer in der DMT. Da ja nun im Wertebereich WORD 2^16 =65535 mögliche Einträge drinnen sein können kann eine solche DMT sehr viel Speicherplatz wegnehmen, wenn man diese DMT statisch konstruiert. Um das zu vermeiden besteht die DMT aus Einträgen der Form

Delphi-Quellcode:

  DMT_Count: Word;
  DMT_Table: array[DMT_Count] of packed record
              SlotID: Word; // ID der Methode, bei message wm_Paint also SlotID = wm_Paint
              MethodAddress: Pointer;
             end;
Dh. zu jeder Methode in der DMT wird deren SlotID gespeichert. Nur die Klasse die diese Methode real implementiert wird einen solchen Eintrag in ihrer DMT enthalten. Die Methode Dispatch() geht nun TopDown in der Vererbungshierarchie vor um die Methode zu finden die sie aufrufen muß. Enthält also die Klasse die zu oberst in .Dispatch() aufgerufen wurde KEINE entsprechende Methode in iherer DMT dann geht .Dispatch() indie DMT der Parent Klasse und sucht dort weiter. Das geht dann runter bis TObject. Und wenn dann immer noch keine Methode gefunden wurde, dann wird .DefaultHandler aufgerufen.

In der VMT == virtual Method Table sieht es anders aus. Dort wird tatsächlich eine 1 zu 1 Tabelle aller virtuellen Methoden benutzt und jede descend Klasse erbt quasi die komplette VMT der Parent Klasse und fügt die neu eingeführten virtuellen Methoden an diese VMT KOpie noch hinten dran.

An Hand dieses Verhaltens der DMT im Vergleich zur VMT ergibt sich folgendes:

VMT ist schneller als die DMT beim Aufruf.
DMT kostet weit weniger Speicherplatz als die VMT.
dynamische Methoden müssen nicht durch implementiert sein in einer Klassenhierarchie. Ist dies der Fall wird bei einem Aufruf von Dispatch() die Methode .DefaultHandler() aufgerufen.

DMTs werden von alle Klassen unterstützt und wenn die Methoden die als "message XYZ" deklariert wurden nichts anders als "dynamic" deklariert Methoden darstellen, mit der Besonderheit das man die SlotID explizit angeben kann, dann heist die das alle Klassen auch Windows-Botschaften verarbeiten können.

Ich persnlich nutze in meinen TNode Baum Strukturen exakt dieses Verhalten. TNode ist abgeleitet von TObject und ist ein interaktives Datenobjekt das als hierarisch und strukturierter Baum Daten verwalten kann. Zusätzlich gints aber auch visuelle Komponneten wie zb. ein TNodeGrid. Diese Komponenten können nun Windows Botschaften die es empfängt weitereiten zu den TNOde Baum. D.h. zb. Mouse/Keyboard/Paint Botschaften die sich auf eine Zeile im Grid beziehen werden zu der dahinterliegenden TNode weitergeleitet. Je nachdem wie und ob diese TNode reagiert kann also diese TNode zb. ihren eigenen grafischen Bereich in deisem Control selber zeichnen, auf Editierungen, Maus/Keyereignisse reagieren.
Defakto kapselt man also in solchen TNode OBjekten nicht nur die Datenhaltung sondern auch deren Visualisierung und Bearbeitung in diesen Objekten. Das visuelle Control TNodeGrid stellt nur eine Schnittstelle im GUI zu diesen Datenobjekten dar.
Diese Bibliothek habe ich schon seit Delphi 1 lauffähig und wurde mit der Zeit bis nach Delphi 7 portiert. Die allerersten Vorfahren dieser TNode Objekte habe ich aber schon in Borland Pasal 7 erzeugt.

Damit will ich nur aufzeigen das das was du möchtest sehr gut zu implementieren geht und das es auch schon andere Programmierer real gecodet haben (ich zb. )

Der Link den delphirocks angebegeben hat beschreibt dagegen eine Lösung die viel zu wenig die Hauseigene Möglichkeiten der Delphi Klassen ausnutzt (meiner Meinung nach)

Ein sehr sehr großer Vorteil dieser Methodik im Vergleich zum obigen Link ist deren direkte Intergration in das OOP Klassenkonzept. Also so wie du es gewohnt bist bei TWinControls Message-Methoden zu überschreiben, deren inherited Implemetierungen aufrufen zu können usw. kannst auch du damit arbeiten und musst nicht zwangsläufig Windows-Botschaften damit verarbeiten.

Du kannst also sowas machen:
Delphi-Quellcode:

const
  msg_One = $1000;
  msg_Two = $1001;
  msg_Three = $1002;

type
  TTest = class
    procedure DefaultHandler(var Msg: Tmessage); override;
  end;
 
  TTest_1 = class(TTest)
    procedure msgOne(var Msg: TMessage); message msg_One;
  end;

  TTest_2 = class(TTest_1)
    procedure msgTwo(var Msg: TMessage); message msg_Two;
  end;
Rufst du TTest_2.Disptach(msg_Two) auf wird also direkt die Methode TTest_2.msgTwo() aufgerufen da TTest_2 diese implementiert. Rufst du in TTest_2.msgTwo() nun inherited auf so geht der Aufruf bis zu TTest.DefaultHandler() durch da ja keine weitere Vorfahrklasse eine Methode mit SlotID msg_Two mehr implementiert.

Rufst du TTest_2.Disptach(msg_One) auf wird TTest_1.msgOne() aufgerufen da ja in der DMT von TTest_2 keine Methode mit SlotID msg_One deklariert wurde.

Rufst du TTest_2.Dispatch(msg_Three) auf wird TTest.DefaultHandler aufgerufen da ja TTest_2 und auch TTest_1 diese Methode nicht deklariert haben.

Du hast also die Möglichkeit in deiner Objekthirarchie solche Methoden zu überschrieben, deren Vorfahr-Implementierungen aufzurufen und kannst auch frei neue Message Konstanten deklarieren ohne das du schon im Vorhinein eine Implementierung dazui bauen musst. Du baust also Basisfunktionalität die erst in abgeleiteten Klassen, die du nicht kennen musst, benutzt wird.

Das sind entscheidende Argumente die den Vorteil gegenüber der im Link angegebenen Lösung aufzeigen.

Gruß Hagen

Hier siehst du ein par Besipeile der TNode und TNodeGrid

http://www.dinosgmbh.de/produkte/care/b_ambulant3.htm
http://www.dinosgmbh.de/produkte/care/b_dpl1.htm
http://www.dinosgmbh.de/produkte/care/b_dpl2.htm

Alle Gitter in diesem Screenshots sind TNodeGrids und TNode als Datenkontainer die sich selber zeichnen, auf Mau/Keabord reagieren und natürlich Daten speichern,laden, berechen und visuaiseren.
  Mit Zitat antworten Zitat