Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Multicaster in Delphi 2009 (https://www.delphipraxis.net/132841-multicaster-delphi-2009-a.html)

Phoenix 20. Apr 2009 22:37


Multicaster in Delphi 2009
 
Hi,

ich habe ein Problem mit Delphi 2009.

Ich will einen Multicast delegate für einen TButton-Click bauen (beispielhaft, soll gegen später generisch mit jedem Event gehen).

Die Idee ist folgende:
Mittels eines Class helpers erweitere ich die Klasse TButton um zwei Methoden: AddEvent und RemoveEvent.
In einem generischen Dictionary merke ich mir dann zu jedem verwendeten Button eine Liste mit registrierten Events. Wird mit AddEvent eines hinzugefügt, so hänge ich dieses Event in die Liste ein und bei remove soll das Event auch aus der Liste entfernt werden. (Ja, ich fake mittels des Dictionarys sozusagen ein Property zu dem Button dazu - class helper mit zusätzlichen Properties könnten mittels eines generischen Dictionaries auch so hinzubekommen sein).

Das Hinzufügen und Ausführen der Events funktioniert auch schon, aber das Entfernen eines einmal registrierten Events schmeisst eine Zugriffsverletzung, und zwar genau an der Stelle an dem geprüft wird, ob das Event in der Liste ist.

Kann man jemand drübergucken und mir sagen, warum ich das Event auf diese Art zwar in die Liste hinzufügen, aber nicht mer entfernen kann? Mir erscheint das etwas unlogisch und die Zugriffsverletzung passiert jedes mal an einer anderen Adresse, so dass ich gar nicht sagen kann wo die herkommt.

Hier mal die komplette betreffende Unit:

Delphi-Quellcode:
unit UMulticaster;

interface

uses
  Classes, StdCtrls;

type
   TButtonMulticaster = class helper for TButton
     procedure AddEvent(event: TNotifyEvent); overload;
     procedure RemoveEvent(event: TNotifyEvent); overload;
   end;

implementation

uses
   SysUtils, Generics.Defaults, Generics.Collections;

type
  eventList = TList<TNotifyEvent>;
  eventDictionary = TDictionary<TButton, eventList>;

  EventDispatcher = class
  private
    eventList: eventDictionary;
  public
    constructor Create;
    destructor Destroy;
  public
    procedure OnSomeButtonClick(sender: TObject);
    property Events: eventDictionary read eventList write eventList;
  end;

var
  dispatcher: EventDispatcher;

{ EventDispatcher }

constructor EventDispatcher.Create;
begin
  eventList := eventDictionary.Create();
end;

destructor EventDispatcher.Destroy;
begin
  FreeAndNil(eventList);
end;

procedure EventDispatcher.OnSomeButtonClick(sender: TObject);
var
  event: TNotifyEvent;
begin
  for event in eventList.Items[TButton(sender)] do
  begin
    event(sender);
  end;
end;

{ TButtonMulticaster }

procedure TButtonMulticaster.AddEvent(event: TNotifyEvent);
begin
  if not dispatcher.Events.ContainsKey(self) then
  begin
    dispatcher.Events.Add(self, eventList.Create());
    self.OnClick := dispatcher.OnSomeButtonClick;
  end;

  dispatcher.Events[self].Add(event);
end;

procedure TButtonMulticaster.RemoveEvent(event: TNotifyEvent);
var
  evList: eventList;
begin
  if dispatcher.Events.ContainsKey(self) then
  begin
    evList := dispatcher.Events[self];
    if evList.IndexOf(event) >= 0 then // HIER wird die Zugriffsverletzung ausgelöst
      evList.Remove(event);

    if dispatcher.Events[self].Count = 0 then
    begin
      dispatcher.Events.Remove(self);
      self.OnClick := nil;
    end;
  end;
end;

initialization
begin
  dispatcher := EventDispatcher.Create();
end;

finalization
begin
  FreeAndNil(dispatcher);
end;

end.

mjustin 21. Apr 2009 06:58

Re: Multicaster in Delphi 2009
 
Zitat:

Zitat von Phoenix
Ich will einen Multicast delegate für einen TButton-Click bauen (beispielhaft, soll gegen später generisch mit jedem Event gehen).

Ich teste es später mal, wäre mein erster Code mit Generics :?

Soll der Code der die class helper Lösung verwendet, eventuell mit fremdem Code kombiniert werden? Man kann nur einen class helper je Klasse definieren, andere Libraries könnten daher mit eigenem Code kollidieren. Delphi erzeugt afaik dann noch nicht einmal eine Warnung.

Uwe Raabe 21. Apr 2009 07:54

Re: Multicaster in Delphi 2009
 
Versuch mal, beim eventList.Create() einen Comparer für TNotifyEvent zu übergeben. Der wird nämlich von IndexOf verwendet.

stoxx 21. Apr 2009 16:17

Re: Multicaster in Delphi 2009
 
geh jedes Element der Liste durch und prüfe so auf Gleichheit.
Ich vermute, dass Du das Event beim reinen hinschreiben sogar aufrufst, und das willst Du ja nicht.
so geht es bei meinem Multicaster.

Delphi-Quellcode:
procedure TButtonMulticaster.RemoveEvent(event: TNotifyEvent);
var

...
  evList: eventList;
  for i := (eventList.Count - 1) downto 0 do begin
    ev := eventList.Items[i];

    if (TMethod(ev).Code = TMethod(event).Code ) and
       (TMethod(ev).Data = TMethod(event).Data ) then begin
            eventList.Delete(i);
//            exit;
       end; // if
  end; // for

end;

ULIK 21. Apr 2009 19:57

Re: Multicaster in Delphi 2009
 
Zitat:

Zitat von Uwe Raabe
Versuch mal, beim eventList.Create() einen Comparer für TNotifyEvent zu übergeben. Der wird nämlich von IndexOf verwendet.

Jup, damit geht es.

Grüße,
Uli

mjustin 23. Apr 2009 10:22

Re: Multicaster in Delphi 2009
 
Hier ist ein Artikel (mit Source) zu Multicast-Events von Allen Bauer:

The Oracle at Delphi: Multicast events using generics
http://blogs.embarcadero.com/abauer/2008/08/15/38865

Generics in Delphi scheinen noch etwas verbesserungsbedürftig zu sein.

Der Sourcecode zu diesem Artikel ist auch noch nicht komplett veröffentlicht, und im vorletzten Artikel werden auch einige Risiken angesprochen.

Phoenix 23. Apr 2009 10:28

Re: Multicaster in Delphi 2009
 
Ich hab ihn jetzt am laufen.
Muss noch ein paar Feinheiten anpassen, aber im Prinzip sollte er dann sogar für jedes beliebige Event gehen ;-)


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:31 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