Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Neuen Beitrag zur Code-Library hinzufügen (https://www.delphipraxis.net/33-neuen-beitrag-zur-code-library-hinzufuegen/)
-   -   Delphi Auf Interface basierende MultiCastEvents (https://www.delphipraxis.net/172339-auf-interface-basierende-multicastevents.html)

geskill 27. Dez 2012 16:46


Auf Interface basierende MultiCastEvents
 
Liste der Anhänge anzeigen (Anzahl: 1)
.
Warum MultiEvents?
Die nativen Events in Delphi haben zwei große Nachteile:
  1. Es ist eine 1 zu 1 Verbindung, es gibt ein Event-Auslöser (trigger) und den Event-Zuhörer (event listener or handler)
    jedoch ist es oftmals gewünscht an mehreren Stellen auf ein Event zu reagieren.
    Die MultiEvents führen hier die aus vielen anderen Programmiersprachen (z.B. Java) bekannte 1 zu N Verbingdung ein,
    in der sich zu einem Event X mehrere Zuhörer über die Add() Methode registrieren können.
  2. Die Events sind nur innerhalb des Delphi Sprachraums gültig. Für programmiersprachenübergreifende Schnittstellen gibt
    es keine "out of the box" Lösung.
    Das besondere an den MultiEvents ist die programmiersprachenübergreifende Verträglichkeit. Durch Interfaces und COM
    Datentypen ermöglichen dies den Einsatz in großen Plug-In Systemen.

Außerdem nutzen die MultiEvents geschickt die in D2009+ eingeführten Generics
um den Mehraufwand an Code auf ein Minimum zu reduzieren.
Inhalt
  1. Interfaces als Ausgangspunkt
    • 1 zu 1 Verbindung
    • 1 zu N Verbindung
  2. Einfache Benutzung
    • Event-Auslöser Betrachtung
    • Event-Zuhörer Betrachtung
  3. Quellcodeminimierung durch Generics
    • TGenericEventHandler Klasse
    • TGenericEvent Klasse
  4. Verweise
    • Ähnliche Ansätze

1. Interfaces als Ausgangspunkt
1 zu 1 Verbindung
Ganz ohne das alt bekannte procedure ... of object Gerüst geht es nicht. Zusätzlich wird ein EventHandler Interface definiert.
Delphi-Quellcode:
  TNotifyMethod = procedure(const Sender: IUnknown) of object;

  INotifyEventHandler = interface(IUnknown)
    ['{EE9407DD-2337-4DFC-BC35-A40C4FA0A1A7}']
    procedure Invoke(const Sender: IUnknown); safecall;
  end;
Aus diesen beiden "Bauteilen" entsteht die TINotifyEventHandler Klasse, die das zuvor definierte Interface implementiert. Dies ist eine Art Wrapper für die TNotifyMethod.
Delphi-Quellcode:
  TINotifyEventHandler = class(TGenericEventHandler<TNotifyMethod>, INotifyEventHandler)
  public
    procedure Invoke(const Sender: IUnknown); safecall;
  end;
1 zu N Verbindung
Hat man erstmal einen EventHandler definiert kann man daraus nun ein entsprechendes MultiEvent erstellen. Über Add, Remove können dann Instanzen des INotifyEventHandlers hinzugefügt und wieder entfernt werden. Mit Invoke ruft man das Event auf. Somit werden alle Events die in dem MultiEvent registriert sind benachrichtigt.
Delphi-Quellcode:
  INotifyEvent = interface(IUnknown)
    ['{14551B63-78C4-4A70-9E54-7656CEF4D6A7}']
    procedure Add(const AHandler: INotifyEventHandler); safecall;
    procedure Remove(const AHandler: INotifyEventHandler); safecall;
    procedure Invoke(const Sender: IUnknown); safecall;
  end;
Für die Implementierung reicht es eine for-Schleife in der Invoke-Methode zu tippen:
Delphi-Quellcode:
  TINotifyEvent = class(TGenericEvent<INotifyEventHandler>, INotifyEvent)
  public
    procedure Invoke(const Sender: IUnknown); safecall;
  end;

2. Einfache Benutzung
Event-Auslöser Betrachtung
In der Klasse in der ein MultiEvent eingesetzt werden soll wird eine lokale Variable
Delphi-Quellcode:
FNotifyEvent: INotifyEvent;
definiert. Außerdem muss für den Zugriff von außen gesorgt werden - am Besten durch eine property und einen getter.

Die Erstellung, ein Aufruf und die Zerstörung ist so einfach wie es nur sein kann:
Delphi-Quellcode:
// Im constructor der Klasse
FNotifyEvent := TINotifyEvent.Create;

// button1 von Typ TButton
// Eine Event Nachricht/Aufruf sieht das bspw. so aus
NotifyEvent.Invoke(button1);

// Im destructor der Klasse
FNotifyEvent := nil;

Event-Zuhörer Betrachtung
Die Klasse die sich beim MultiEvent registiert. Wie schon erwähnt benötigt diese Zugriff auf das MultiEvent (hier: Main.NotifyEvent).
Delphi-Quellcode:
  TOtherForm = class(TForm)
    // [...]
  private
    FNotifyEventHandler: INotifyEventHandler;
  protected
    // Diese Methode wird dann aufgerufen
    procedure OnNotify(const Sender: IUnknown);

// [...]

// Im constructor der Klasse
  FNotifyEventHandler := TINotifyEventHandler.Create(OnNotify);
  Main.NotifyEvent.Add(FNotifyEventHandler);

// Im destructor der Klasse
  Main.NotifyEvent.Remove(FNotifyEventHandler);
  FNotifyEventHandler := nil;


3. Quellcodeminimierung durch Generics
TGenericEventHandler Klasse
Delphi-Quellcode:
  TGenericEventHandler<T: constructor> = class abstract(TInterfacedObject)
  protected
    FHandler: T;
  public
    constructor Create(AEventHandler: T);
    property EventHandler: T read FHandler write FHandler;
    destructor Destroy; override;
  end;
TGenericEvent Klasse
Delphi-Quellcode:
  TGenericEvent<T: IUnknown> = class abstract(TInterfacedObject)
  private
    FMethods: TList<T>;
  protected
    property Methods: TList<T>read FMethods;
  public
    constructor Create; virtual;
    procedure Add(const AHandler: T); safecall;
    procedure Remove(const AHandler: T); safecall;
    destructor Destroy; override;
  end;


4. Verweise
Die Arbeit von Jolyon Smith hat mich ich für MultiEvents auf Interface-Basis inspiriert. Da dies mMn noch nicht Dokumentiert ist, sollte es an dieser Stelle ganz gut aufgehoben sein.

Ähnliche Ansätze
http://www.delphipraxis.net/158578-m...st-events.html
Multicast events using generics


Im Anhang befindet sich der komplette Quellcode und ein kleines Beispiel. Die Events lassen sich natürlich nach eigenen Wünschen beliebig anpassen und unter Verwendung der generischen Klassen, sollte sich die Tipparbeit in Grenzen halten.

Viel Spaß damit.
.


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz