|
Antwort |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#1
Delphi-Version: 2010
Hallo zusammen!
Ich hoffe, es ist laut den Regeln wunschgemäß, für eine weiterführende Frage zum selben Thema nach solch langer Zeit einen neuen Thread anzufangen. Dabei beziehe ich mich hier auf mein vorhergehendes Thema http://www.delphipraxis.net/139677-t...speichern.html , bei dem jetzt doch (immer noch) ein Problem aufgetreten ist. In dem unten aufgeführen Beispiel geht das darum, dass alle dynamisch erzeuten TMeinButton "in einem Rutsch" gedrückt werden sollen. Da sich die ausgeführten Prozeduren "Pressed" bzw. "OnClick" -trotz gleicher Komponente- von Button zu Buton unterschiedlich verhalten können (je nach Variable etc.), will ich natürlich in diesem Beispiel jeden Button genau einmal drücken, und nicht einfach nur das TMeinButton.Pressed Event 2x auslösen. Die Buttons verstehen sich hier bitte nur als Sinnbild für viele verschiedene Komponenten, die alle eine TNotifyEvent Prozedur anbieten. Da ich bei Register nur das TNotifyEvent der Instanz einer Komponente übergebe, hoffe ich ansich, dass das auch funktioniert. Ich könnte mir denken, dass die korrekte Implementierung nur und ausschließlich sich über Interfaces durchführen lässt, aber aus meiner laienhafen Sicht halte ich persönlich das für mit Kanonen nach Spatzen geschossen. Da dieses Beispiel generell funktioniert, denke ich, dass das Problem wirklich mit dem Pointer-Problem zu tun hat: "Blup" schrieb in seinem letzten Beitrag
Zitat:
Auch in der Funktion "IndexOfEvent" hat das @ nichts zu suchen.
Also mal angenommen, meine Ziele lassen sich generell mit dieser Technik erreichen, gibt es einige Fragen und Probleme: 1) Wenn ich RegisterEvent(b.Pressed) aufrufe, schlägt dies fehl, da IndexOfEvent(_Event) > -1 ( = also bereits vorhanden) ist. Darf ja so nicht sein. 2) Warum ist das so? 3) Wie korrigiert man das?
Delphi-Quellcode:
Vielen Dank für Eure Hilfe!
unit Unit2;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, contnrs; // Aufgabenstellung: Alle dynamisch erzeuten TMeinButton sollen "in einem Rutsch" gedrückt werden type // Sinnbild für irgendeine Komponente, die zurückgerufen werden soll // In diesem Fall soll einfach der Button gedrückt werden TMeinButton = class(TButton) public procedure Pressed(Sender: TObject); end; // Hilfsobjekt, um das TNotifyEvent zu speichern THAL_ObserverItem = class(TObject) public Event: TNotifyEvent; end; TForm2 = class(TForm) Button1: TButton; // dieser Button soll alle dynamisch erzeugten TMeinButton "in einem Rutsch" drücken procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); private FListSubscriber: TObjectList; // hier werden die TNotifyEvent (über Hilfsobjekt THAL_ObserverItem) gespeichert function IndexOfEvent(_Event: TNotifyEvent): Integer; // wurde das Event bereits in der Liste gespeichert? < 0 bedeutet Nein; >= 0 bedeutet Ja public a, b: TMeinButton; procedure RegisterEvent(_Event: TNotifyEvent); end; var Form2: TForm2; implementation {$R *.dfm} {$o-} procedure TMeinButton.Pressed(Sender: TObject); begin // Einfach den Komponentennamen anzeigen, damit man weiß, // welche Instanz von TMeinButton gedrückt wurde ShowMessage(Name); end; procedure TForm2.FormCreate(Sender: TObject); begin FListSubscriber := TObjectList.Create; // Die Liste für die Callback-Prozeduren a := TMeinButton.Create(Self); with a do begin Name := 'Button_A'; Left := 10; Top := 10; Parent := Self; OnClick := a.Pressed; // Beim Klick erscheint also "Button_A" end; b := TMeinButton.Create(Self); with b do begin Name := 'Button_B'; Left := 10; Top := 100; Parent := Self; OnClick := b.Pressed; // Beim Klick erscheint also "Button_B" end; RegisterEvent(a.Pressed); RegisterEvent(b.Pressed); end; procedure TForm2.RegisterEvent(_Event: TNotifyEvent); var tmp: THAL_ObserverItem; begin // Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed), // GENAU DIESES Event noch nicht in der Liste ist, // dann der Liste hinzufügen if IndexOfEvent(_Event) = -1 then begin tmp := THAL_ObserverItem.Create; // Hilfsobjekt erzeugen tmp.Event := _Event; // Callback-Prozedur übergeben FListSubscriber.Add(tmp); // Hilfsobjekt in Liste aufnehmen end; end; procedure TForm2.Button1Click(Sender: TObject); var i: integer; begin // Alle Subscriber benachrichtigen for i := 0 to FListSubscriber.Count - 1 do begin THAL_ObserverItem(FListSubscriber.Items[i]).Event(Self); end; // In meinem Beispiel ist mein Ziel dass das dann später so abläuft: // a.Pressed(Self); // b.Pressed(Self); end; function TForm2.IndexOfEvent(_Event: TNotifyEvent): Integer; var i: Integer; begin Result := -1; // Siehe Oben: Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed), // GENAU DIESES Event gefunden wird, dann gebe dessen Index zurück // wird bei RegisterEvent(a.Pressed) NICHT durchlaufen, da hier FListSubscriber.Count noch 0 ist! for i := 0 to FListSubscriber.Count - 1 do begin if @THAL_ObserverItem(FListSubscriber[i]).Event = @_Event then // ^-- HIER ist das Hauptproblem: Sei _Event = b.Pressed ergibt der Vergleich True, obwohl bisher nur a.Pressed in der Liste ist! begin Result := i; Exit; end; end; end; end. |
Zitat |
Registriert seit: 24. Aug 2004 140 Beiträge Delphi XE8 Professional |
#2
Hallo,
ich habe bis heute Ereignisse auch mit @Ereigniss1= @Ereigniss2 verglichen. Ich glaube das stand sogar mal in der Hilfe zu Delphi so drin. Jedenfalls wird/wurde es so auch in der VCL gemacht, wie man an einem Auszug von Controls.pas aus Delphi 2007 sehen kann:
Delphi-Quellcode:
Aber irgendwie will das nicht mehr.
function TControlActionLink.IsOnExecuteLinked: Boolean;
begin Result := inherited IsOnExecuteLinked and (@FClient.OnClick = @Action.OnExecute); end; In Delphi XE sieht die obige Funktion so aus.
Delphi-Quellcode:
Wobei DelegatesEqual so aus sieht:
function TControlActionLink.IsOnExecuteLinked: Boolean;
begin Result := inherited IsOnExecuteLinked and (( {$IF DEFINED(CLR)} (not Assigned(FClient.OnClick)) and (not Assigned(Action.OnExecute))) or (Assigned(FClient.OnClick) and {$IFEND} DelegatesEqual(@FClient.OnClick, @Action.OnExecute))); end;
Delphi-Quellcode:
Das funktioniert in deinem Fall aber auch nicht. Die Hilfe zu DelegatesEqual ist ja auch interessant:
function DelegatesEqual(A, B: Pointer): Boolean;
begin Result := A = B; end;
Zitat:
DelegatesEqual überprüft, ob zwei Delegaten gleich sind.
DelegatesEqual wird aus Gründen der Kompatibilität mit .NET bereitgestellt. DelegatesEqual hat in Win32-Anwendungen keinen echten Nutzen. So ein Ereignis ist ja ein Methodenzeiger, der aus zwei Zeigern besteht. Einer zeigt auf die Methode in der Klasse und der andere auf das Objekt, unter dessen Kontext die Methode aufgerufen werden soll. Im Hintergrund wird das über den Type TMethod behandelt, der so aussieht:
Delphi-Quellcode:
Der Vergleich @Ereigniss1= @Ereigniss2
scheint nur den ersten Zeiger zu vergleichen, der bei dir ja gleich ist, und den Zweiten zu ignorieren.
TMethod = record
Code, Data: Pointer; end; Wenn man in deinem Fall den Vergleich so macht: (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Data = TMethod(_Event).Data) and (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Code = TMethod(_Event).Code) funktioniert das unter Delphi 2007 und XE. Die Frage ist bloß, funktioniert das auch ab XE2 unter jeder Plattform so und wie muss man das unter .NET machen? Die nächste Frage wäre, wieso bei TMethod die Vergleichsoperatoren Equal und NotEqual nicht überschrieben wurden, damit folgendes reichen würde? TMethod(THAL_ObserverItem(FListSubscriber[i]).Event) = TMethod(_Event) Ich habe auch bei XE getestet ob der Compiler-Schalter {$T} Auswirkungen hat, was nicht der Fall ist. @All: Nun die Preisfrage, wieso wird in der VCL an entscheidenden Stellen Ereignisse mittels @ verglichen? Einbeliebigername. |
Zitat |
einbeliebigername |
Öffentliches Profil ansehen |
Mehr Beiträge von einbeliebigername finden |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#3
Scheint auf den ersten Blick einwandfrei zu funktionieren. Vielen Dank erstmal hierfür
Das mit
Delphi-Quellcode:
wurde ja schon im vorhergehenden Thread glaube ich erwähnt, aber das habe ich jetzt nicht weiter beachtet, denn:
TMethod = record
Code, Data: Pointer; end; Wenn mein "Event" ja in Wirklichkeit (nur) ein Pointer auf eine Instanz von TMethod ist, müssen die Pointer von a.Pressed und b.Pressed ja zwangsläufig verschiedene Speicherbereiche (also PointerAdressen) haben, da beide auf ein anderes Objekt verweisen. Sonst würde ja im regulären Programmablauf b.Pressed tatsächlich a.Pressed aufrufen. Als muss irgendwas mit dem @ nicht stimmen. Ich habe zwar schon mehrere Tutorials zu Pointer gelesen, aber gerade auch wieder aufgrund der aktuellen Problematik blicke ich da (wieder) nicht durch. Ich könnte mir denken, dass der einfache @ Vergleich nur dann zulässig ist (bzw. funktioniert) denn der Pointer auf eine einfache Variable verweist (Int, String, ...). Wenn mir jetzt einfach jemand sagt, dass @Pointer oder Klassen(de?)referenz^ mit Dach^ eh nur noch Überbleibsel aus früheren Zeiten sind, die in aktuellen Programmen gar nichts mehr verloren haben, wäre ich Euch echt sehr dankbar! Vielen Dank für Hilfe, Thema einstweilen erledigt. N8 |
Zitat |
Registriert seit: 24. Aug 2004 140 Beiträge Delphi XE8 Professional |
#4
Hallo,
Wenn mein "Event" ja in Wirklichkeit (nur) ein Pointer auf eine Instanz von TMethod ist
Delphi-Quellcode:
Raus kommt dann das:
function TForm1.IndexOfEvent(const _Event: TNotifyEvent): Integer;
var i: Integer; a: Integer; E: TNotifyEvent; b: Integer; begin Result := -1; // Siehe Oben: Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed), // GENAU DIESES Event gefunden wird, dann gebe dessen Index zurück // wird bei RegisterEvent(a.Pressed) NICHT durchlaufen, da hier FListSubscriber.Count noch 0 ist! for i := 0 to FListSubscriber.Count - 1 do begin a:= SizeOf(TNotifyEvent); E:= THAL_ObserverItem(FListSubscriber[i]).Event; b:= SizeOf(TMethod); OutputDebugString(PChar(Format('TMethod(T.Event).Code= %p; TMethod(T.Event).Data= %p; @E= %p; Int64(@E)= %d; @@E= %p; SizeOf(TNotifyEvent)= %d; SizeOf(TMethod)= %d; @a= %p; @b= %p; @i= %p ', [TMethod(E).Code, TMethod(E).Data, @E, Int64(@E), @@E, a, b, @a, @b, @i]))); if (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Data = TMethod(_Event).Data) and (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Code = TMethod(_Event).Code) then // ^-- HIER ist das Hauptproblem: Sei _Event = b.Pressed ergibt der Vergleich True, obwohl bisher nur a.Pressed in der Liste ist! begin Result := i; Exit; end; end; end;
Code:
Da sieht man, dass eine Variable vom Typ TNotifyEvent bereits so groß ist wie zwei Pointer. Bei dem @ und Methoden-Zeigern greift wiedermal ein wenig Compiler-Magie. Eigentlich gibt das @ die Adresse des dahinter stehenden Ausdrucks zurück. Bei @a bekommt man im obigen Beispiel die Adresse, wo die Variable a auf dem Stack liegt, zurück. Bei Methoden-Zeigern gibt das @ den Inhalt der Variable zurück. Die Adresse der Variable bekommt man erst mit einem @@ zurück. Wieso jetzt aber das einfache @ bei Methoden-Zeigern nur die Hälfte des Inhaltes wiedergibt, bleibt mir ein Rätsel.
Debug-Ausgabe: TMethod(T.Event).Code= 004CBE40; TMethod(T.Event).Data= 028D3870; @E= 004CBE40; Int64(@E)= 5029440; @@E= 0018FE5C; SizeOf(TNotifyEvent)= 8; SizeOf(TMethod)= 8; @a= 0018FE4C; @b= 0018FE48; @i= 0018FE50 Prozess dp165785Project1.exe (10976)
Leider habe ich gerade kein lauffähiges Delphi 7 zur Verfügung. Denn ich bin der Meinung das der Vergleich mit dem @ da noch funktioniert hat. Vielleich sind ja andere so nett und Testen das mal mit anderen Versionen. Ich habe das mit Delphi 2007, XE und jetzt sogar mit XE2 Update 3 in 32 und 64bit getestet. Immer mit dem gleichen Ergebnis. Nur das sich bei 64bit die Größen verdoppeln. Und da liegt vermutlich auch das Problem. Laut Hilfe von XE2 gibt es unter 64bit kein 128bit großen Datentyp äquivalent zu Int64 bei 32bit, so das gar nicht beide Pointer bei dem @ zurückgegeben werden können. Auch TControlActionLink.IsOnExecuteLinked und DelegatesEqual sehen bei XE2 so aus wie bei XE. Einbeliebigername. |
Zitat |
einbeliebigername |
Öffentliches Profil ansehen |
Mehr Beiträge von einbeliebigername finden |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |