![]() |
TNotifyEvent Objektbezogen in Variable speichern
Hallo mal wieder (zu später Stunde).
Delphi treibt mich in den Wahnsinn. Warum kann ein Konzept nicht durchgängig angewendet werden? *seufz* Also, ich arbeite gerade an der Implementation eines Observer-Patterns: ![]() Meine Idee ist es, wenn ein Ereignis eintritt, mehrere andere Komponenten zu benachrichtigen (teilweise verschiedene Vorfahren). Siehe auch Ursprungs-Thread: ![]() Einfaches (sinnloses) Beispiel: Beim Programmstart werden die TNotifyEvent am Observer angemeldet. Beim Klick auf Button1 erscheint zuerst '111', direkt danach '222'.
Delphi-Quellcode:
Das ganze funktioniert auch. In diesem Beispiel.
procedure TForm1.Button6Click(Sender: TObject);
begin ShowMessage('111'); // Problem: an dieser Stelle hat die Variable "Self" den Wert NIL end; procedure TForm1.Button7Click(Sender: TObject); begin ShowMessage('222'); // Problem: an dieser Stelle hat die Variable "Self" den Wert NIL end; procedure TForm1.FormCreate(Sender: TObject); begin Observer := TMeinSelbstProgrammierterObserver.Create(Self); Observer.RegisterSubscriber(Button6Click); Observer.RegisterSubscriber(Button7Click); end; procedure TForm1.Button1Click(Sender: TObject); begin Observer.NotifySubscriber; end; Das Problem ist, dass beim Aufruf z.B. von Button6Click (über Observer.NotifySubscriber) "Self" = NIL ist.
Delphi-Quellcode:
Obwohl der Callback (sagt man so?) tatsächlich aus dem Haupt-Thread heraus erfolgt, wird scheinbar diese Prozedur ohne Bezug zu der Konkreten Instanz des Objektes gespeichert. Na toll.
procedure THAL_Observer.NotifySubscriber(_Sender: TObject);
var i: integer; FNotifyEvent: TNotifyEvent; begin try FNotifyEvent := NIL; for i := 0 to FListSubscriber.Count - 1 do begin if assigned(FListSubscriber.Items[i]) then begin @FNotifyEvent := FListSubscriber.Items[i]; FNotifyEvent(Self); end; end; except on E: Exception do begin end; end; end; Wenn ich einem TButton.OnClick ein TNotifyEvent zuweise, ist "Self" beim aufrufen doch zugewiesen. Warum also nicht wenn ich das TNotifyEvent dirket aufrufe, wenn ich den Pointer dazu in einer TList habe? Am fehlenden/falschen Sender kann es ja nicht liegen; der darf ja keinen Einfluss auf "Self" haben. Wie bekomme ich das nun am einfachsten hin? Gibt es einen Trick, um den Pointer auf die Prozedur der konkreten Instanz des Objektes in der Liste abzuspeichern, oder was kann ich tun? Ich will nicht für jedes Objekt, das benachrichtigt werden will, ein Interface implementieren um "zurückgerufen" zu werden. Ich meine, wofür gibt es TNotifyEvent, wenn ich noch nicht mal von genau diesem Objekt genau diese Prozedur bei einem anderen Objekt hinterlegen kann? Danke im Vorraus. Für verwendbare Alternativen wäre ich auch dankbar.
Delphi-Quellcode:
unit HAL1_CLASSES_Observer;
// ***************************************************************************// // THAL_Observer // RegisterSubscriber: Parameter nennt die Prozedur, die beim Ereinis aufgerufen wird // UnRegisterSubscriber: Komponente wird gelöscht (o.ä.), deshalb keine weiteren Infos mehr // // ***************************************************************************// interface {$REGION 'uses'} uses Classes, Contnrs, Controls, Dialogs, ExtCtrls, Forms, Graphics, Messages, StdCtrls, SysUtils, Variants, Windows ; {$ENDREGION} type THAL_Observer = class(TComponent) private FListSubscriber: TList; public procedure NotifySubscriber(_Sender: TObject); procedure RegisterSubscriber(_Event: TNotifyEvent); procedure UnRegisterSubscriber(_Event: TNotifyEvent); constructor Create(_Sender: TComponent); override; destructor Destroy; override; end; implementation {==============================================================================} {$Region 'constructor THAL_Observer.Create(_Sender: TComponent);'} constructor THAL_Observer.Create(_Sender: TComponent); begin try inherited Create(_Sender); FListSubscriber := TList.Create; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'destructor THAL_Observer.Destroy;'} destructor THAL_Observer.Destroy; begin try FreeAndNil(FListSubscriber); inherited Destroy; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.NotifySubscriber(_Sender: TObject);'} procedure THAL_Observer.NotifySubscriber(_Sender: TObject); var i: integer; FNotifyEvent: TNotifyEvent; begin try FNotifyEvent := NIL; for i := 0 to FListSubscriber.Count - 1 do begin if assigned(FListSubscriber.Items[i]) then begin @FNotifyEvent := FListSubscriber.Items[i]; FNotifyEvent(Self); end; end; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.RegisterSubscriber(_Event: TNotifyEvent);'} procedure THAL_Observer.RegisterSubscriber(_Event: TNotifyEvent); begin try if FListSubscriber.IndexOf(@_Event) < 0 then begin FListSubscriber.Add(@_Event) end; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.UnRegisterSubscriber(_Event: TNotifyEvent);'} procedure THAL_Observer.UnRegisterSubscriber(_Event: TNotifyEvent); begin try if FListSubscriber.IndexOf(@_Event) > -1 then begin FListSubscriber.Extract(@_Event); end; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} end. |
Re: TNotifyEvent Objektbezogen in Variable speichern
Du merkst dir hier einen Zeiger auf eine lokale Variable:
Delphi-Quellcode:
Wundersam, dass da überhaupt etwas funktioniert.
FListSubscriber.Add(@_Event)
|
Re: TNotifyEvent Objektbezogen in Variable speichern
Scheinbar scheint ein Event "in Delphi" doch irgendwie anders gehandhabt zu werden, als ausschließlich über einen Zeiger.
Bzgl. einer Alternative würde ich daher vorschlagen einfach das Event über einen Wrapper zu kapseln:
Delphi-Quellcode:
type
THAL_ObserverItem = class(TObject) private FEvent: TNotifyEvent; public constructor Create(iEvent: TNotifyEvent); public property Event: TNotifyEvent read FEvent write FEvent; end; THAL_Observer = class(TComponent) private FListSubscriber: TObjectList; protected function IndexOfEvent(_Event: TNotifyEvent): Integer; public procedure NotifySubscriber(_Sender: TObject); procedure RegisterSubscriber(_Event: TNotifyEvent); procedure UnRegisterSubscriber(_Event: TNotifyEvent); constructor Create(_Sender: TComponent); override; destructor Destroy; override; end; [...] { THAL_ObserverItem } constructor THAL_ObserverItem.Create(iEvent: TNotifyEvent); begin inherited Create; FEvent := iEvent; end; {==============================================================================} {$Region 'constructor THAL_Observer.Create(_Sender: TComponent);'} constructor THAL_Observer.Create(_Sender: TComponent); begin try inherited Create(_Sender); FListSubscriber := TObjectList.Create; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'destructor THAL_Observer.Destroy;'} destructor THAL_Observer.Destroy; begin try FreeAndNil(FListSubscriber); inherited Destroy; except on E: Exception do begin end; end; end; function THAL_Observer.IndexOfEvent(_Event: TNotifyEvent): Integer; var i: Integer; begin for i := 0 to FListSubscriber.Count - 1 do if @THAL_ObserverItem(FListSubscriber[i]).Event = @_Event then begin Result := i; Exit; end; Result := -1; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.NotifySubscriber(_Sender: TObject);'} procedure THAL_Observer.NotifySubscriber(_Sender: TObject); var i: integer; begin try for i := 0 to FListSubscriber.Count - 1 do begin THAL_ObserverItem(FListSubscriber[i]).Event(Self); end; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.RegisterSubscriber(_Event: TNotifyEvent);'} procedure THAL_Observer.RegisterSubscriber(_Event: TNotifyEvent); begin try if IndexOfEvent(_Event) = -1 then FListSubscriber.Add(THAL_ObserverItem.Create(_Event)); except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} {$Region 'procedure THAL_Observer.UnRegisterSubscriber(_Event: TNotifyEvent);'} procedure THAL_Observer.UnRegisterSubscriber(_Event: TNotifyEvent); begin try if IndexOfEvent(_Event) > -1 then begin FListSubscriber.Delete(IndexOfEvent(_Event)); end; except on E: Exception do begin end; end; end; {$EndRegion} {==============================================================================} |
Re: TNotifyEvent Objektbezogen in Variable speichern
Ein Event ist eine Methodenreferenz, also schon ein Zeiger
|
Re: TNotifyEvent Objektbezogen in Variable speichern
Zitat:
Delphi-Quellcode:
Edit: Was ich so auf die Schnelle sehe, würde ich FListSubscriber durch ein array of TMethod o.ä. ersetzen.
TMethod = record
Code, Data: Pointer; end; Bitte vergessen - ist ja schon so - siehe THAL_ObserverItem. :oops: |
Re: TNotifyEvent Objektbezogen in Variable speichern
@KrasserChecker: Das sieht verdammt gut aus und klappt auch soweit einwandfrei im Praxistest.
Das tolle ist so auch, dass sich nicht der Subscriber um den Wrapper kümmern muss, sondern nur der Observer selbst. Ich hätte (zumindest gestern Abend zu später Stunde) wohl angefangen, mit jedem Subscriber solch eine Wrapperkomponente zu erzeugen und dann am Observer anzumelden. :mrgreen: Wie gesagt, funktioniert Tadellos. Dickes Lob an KrasserChecker (und natürlich auch alle anderen :) ) |
Re: TNotifyEvent Objektbezogen in Variable speichern
Auch in der Funktion "IndexOfEvent" hat das @ nichts zu suchen.
|
Re: TNotifyEvent Objektbezogen in Variable speichern
Also hier müssen auch noch beide @ weg? Weil bisher hat es auch so gut geklappt.
Delphi-Quellcode:
function THAL_Observer.IndexOfEvent(_Event: TNotifyEvent): Integer;
var i: Integer; begin for i := 0 to FListSubscriber.Count - 1 do if @THAL_ObserverItem(FListSubscriber[i]).Event = @_Event then begin Result := i; Exit; end; Result := -1; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:38 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