Einzelnen Beitrag anzeigen

Benutzerbild von Phoenix
Phoenix
(Moderator)

Registriert seit: 25. Jun 2002
Ort: Hausach
7.611 Beiträge
 
#1

Multicaster in Delphi 2009

  Alt 20. Apr 2009, 22:37
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.
Sebastian Gingter
Phoenix - 不死鳥, Microsoft MVP, Rettungshundeführer
Über mich: Sebastian Gingter @ Thinktecture Mein Blog: https://gingter.org
  Mit Zitat antworten Zitat