Autor Beitrag
baka0815
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Mi 10.12.08 14:15 
Hallo Forum,

ich habe ein Problem mit Eventhandlern unter Delphi. Wie das System generell funktioniert ist mir klar und folgendes Beispiel funktioniert auch problemlos:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
unit Unit1;

interface

type TTestEvent = procedure(Sender: TObject) of object;

type TKlasse = class
  private
    FTestEvent: TTestEvent;
  published
    property OnTestEvent: TTestEvent read FTestEvent write FTestEvent;
end;

type THandler = class
  public
    procedure HandleEvent(Sender: TObject);
end;

implementation

{ THandler }

procedure THandler.HandleEvent(Sender: TObject);
begin
  // nix
end;

var
  Klasse: TKlasse;
  Handler: THandler;
begin
  Klasse := TKlasse.Create;
  try
    Handler := THandler.Create;
    try
      Klasse.OnTestEvent := Handler.HandleEvent;
      Klasse.OnTestEvent := nil;
    finally
      Handler.Free;
    end;
  finally
    Klasse.Free;
  end;
end.


Wenn ich nun aber nicht nur einen Eventhandler, sondern mehrere zuordnen können möchte, dann brauche ich ja eine Datenstruktur in der ich die speichern kann.
Bei Java sind die Eventhandler eigene Objekte und so könnte man das hier natürlich auch machen. Man erstellt sich ein Interface, dass eine Methode hat, die diesem Event entspricht. Dann kann man allerdings nicht mehr "Delphi-Like" die Methode eines Objekts zuweisen, sondern muss "Java-Like" das Objekt zuweisen. Dann könnte ich jedoch eine TObjectList o.ä. verwenden um die Handler zu speichern.

Wenn ich nun aber bei der Methode als "procedure of object" bleiben möchte, welche Möglichkeiten habe ich dann?

Einer TObjectList kann ich nichts vom Typ TTestEvent zuweisen wie er oben definiert ist:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
unit Unit2;

interface

uses Classes, Contnrs;

type TTestEvent = procedure(Sender: TObject) of object;

type TKlasse = class
  private
    FTestEventHandler: TObjectList;
  public
    constructor Create;
  published
    procedure AddTestHandler(Handler: TTestEvent);
    procedure RemoveTestHandler(Handler: TTestEvent);
end;

type THandler = class
  public
    procedure HandleEvent(Sender: TObject);
end;

implementation

{ THandler }

procedure THandler.HandleEvent(Sender: TObject);
begin
  // nix
end;

{ TKlasse }

procedure TKlasse.AddTestHandler(Handler: TTestEvent);
begin
  FTestEventHandler.Add(Handler);
end;

constructor TKlasse.Create;
begin
  inherited Create;

  FTestEventHandler := TObjectList.Create;
end;

procedure TKlasse.RemoveTestHandler(Handler: TTestEvent);
begin
  FTestEventHandler.Remove(Handler);
end;

var
  Klasse: TKlasse;
  Handler: THandler;
begin
  Klasse := TKlasse.Create;
  try
    Handler := THandler.Create;
    try
      Klasse.AddTestHandler(Handler.HandleEvent);
      Klasse.RemoveTestHandler(Handler.HandleEvent);
    finally
      Handler.Free;
    end;
  finally
    Klasse.Free;
  end;
end.


Hier bekomme ich in Zeile 37 und 49 die Meldung, dass ich nicht genügend Parameter mitgeben würde. Das selbe wenn ich nur eine TList statt einer TObjectList verwende.

Wenn ich statt der T(Object)List jedoch ein array of TTestEvent generiere, funktioniert das ganze wie gewollt. Dann muss ich mich jedoch selbst darum kümmern, dass ich das Array vergrößere, etc.

Gibt es hierfür eine andere Lösung oder habe ich schon alle möglichen (TObjectList mit Objekten die sich an entsprechendes Interface halten müssen, wie bei Java; array of TTestEvent statt TObjectList) gefunden oder wisst ihr noch eine schönere/bessere/"richtigere" Lösung?


Zuletzt bearbeitet von baka0815 am Mi 10.12.08 16:16, insgesamt 2-mal bearbeitet
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mi 10.12.08 15:49 
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
var
  Klasse: TKlasse;
  Handler: THandler;
begin
  Klasse := TKlasse.Create;
  Handler := THandler.Create;
  try
    Klasse.OnTestEvent := Handler.HandleEvent;
  finally
    Klasse.Free;
    Handler.Free;
  end;
end.


Jedes Objekt gehört in einen EIGENEN Resourcen-Schutzblock. Auch wenn es Schreibaufwand ist.

Ferner: Beim Freigeben deines Handler-Objektes solltest Du vorher sicherstellen jegliche Referenzen, die andere Objekte auf dein Objekt haben könnten, bereits gelöst zu haben. Sonst kracht es ;-)

Nun zum eigentlichen Problem: Das Standard-Model mit den Methoden-Zeigern sieht diese Art der Event-Behandlung nicht vor und auch bei Java ist das eigentlich auch nur angelehnt. Java ruft auch nicht alle Methoden mit einem Schlag auf, sondern alle hintereinander. Daher hängt auch die gesamte Event-Behandlung in Java, wenn da einmal eine Exception auftritt, die nicht abgefangen wurde.

Aber nun zur konkreten Realisierung. In JAva ist das ganze eine Interface-Liste, die oftmals über eine ArrayList (oder einen Vector) realisiert ist. Hier jetzt unter Delphi auch auf die Interface-Schiene ausweichen zu wollen, ist zwar theoretisch möglich, auf Grund der fehlenden anonymen Interfaces (ein Segen unter Java *gfg*) macht das ganze unter Delphi aber sehr unhandlich.

Wenn Du trotzdem Multi-Event-Support haben willst und bei deinen Handler-Signaturen procedure(Sender: TObject) of object; bleiben möchtest, so kannst Du dir die addXYlistener-Methoden von Java für jedes Event inklusive der zugehörigen removeXYlistener-Methoden nachbauen. Die Abarbeitung eines Events erfolgt in diesem Falle dann mit etwa folgendem Code:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TMyObject.fireXYevent(const Params);
var
    X:Integer;
begin
    For X := 0 to High(listenerXYevent) do
        listenerXYevent[X](params);
end;


Wichtig ist hier anzumerken, dass man keine Pointer für Methoden-Zeiger verwenden darf, da es sonst ganz nette AccessViolations gibt. Die interne Verwaltung des XYevents müsste also so hier geschehen:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
type
    TXYevent = procedure (const params): of object;
    TXYeventArray = array of TXYevent;

type
    TMyObject = class(TFoo)
    private
        {...}
        listenerXYevent: TXYeventArray;
        {...}
        procedure fireXYevent(const params);
        {...}
    public
        {...}
        procedure addXYlistener(listener: TXYevent);
        procedure removeXYlistener(listener: TXYevent);
        {...}
    end;


Der Rest sollte offensichtlich sein ;-)

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
baka0815 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Mi 10.12.08 16:00 
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Jedes Objekt gehört in einen EIGENEN Resourcen-Schutzblock. Auch wenn es Schreibaufwand ist.

Kann denn bei der Freigabe des Objekts etwas schiefgehen oder wozu der separate Block?

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Ferner: Beim Freigeben deines Handler-Objektes solltest Du vorher sicherstellen jegliche Referenzen, die andere Objekte auf dein Objekt haben könnten, bereits gelöst zu haben. Sonst kracht es ;-)

Ist ja auch nur ein Beispiel, dass man daran erkennen kann, dass der Typ TTestEvent heißt. :)

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Wenn Du trotzdem Multi-Event-Support haben willst und bei deinen Handler-Signaturen procedure(Sender: TObject) of object; bleiben möchtest, so kannst Du dir die addXYlistener-Methoden von Java für jedes Event inklusive der zugehörigen removeXYlistener-Methoden nachbauen. Die Abarbeitung eines Events erfolgt in diesem Falle dann mit etwa folgendem Code:

[...]

Wichtig ist hier anzumerken, dass man keine Pointer für Methoden-Zeiger verwenden darf, da es sonst ganz nette AccessViolations gibt. Die interne Verwaltung des XYevents müsste also so hier geschehen:

[...]

Der Rest sollte offensichtlich sein ;-)

Dass ich hier dann Add/Remove-Methoden benötige ist mir klar und auch bei den anonymen Methoden/Objekten in Java gebe ich dir recht!

Was du aber im Grunde sagst, ist dass ich meinen "array of TTestEvent"-Ansatz verwenden soll und mich um die einzelnen Einträge im Array (nil/benutzt), sowie das dynamische Erweitern des Arrays selber kümmern muss.
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mi 10.12.08 16:05 
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Jedes Objekt gehört in einen EIGENEN Resourcen-Schutzblock. Auch wenn es Schreibaufwand ist.

Kann denn bei der Freigabe des Objekts etwas schiefgehen oder wozu der separate Block?

Jap. kann. z.B. wenn durch irgendein Problem beim Abarbeiten des Konstruktors eine Access Violation auftaucht oder beim Schließen eines Sockets ein Fehler geschmissen wird. Du kennst das ja sicherlich mit den Pferden und den Apotheken ... :mrgreen:

user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Ferner: Beim Freigeben deines Handler-Objektes solltest Du vorher sicherstellen jegliche Referenzen, die andere Objekte auf dein Objekt haben könnten, bereits gelöst zu haben. Sonst kracht es ;-)

Ist ja auch nur ein Beispiel, dass man daran erkennen kann, dass der Typ TTestEvent heißt. :)

Und ich bin der Meinung, dass man gerade der Copy&Paste-Fraktion hier im Forum schon saubren Testcode vorsetzen sollte, sonst gewöhnen die sich gleich noch Marotten an :twisted:

user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Wenn Du trotzdem Multi-Event-Support haben willst und bei deinen Handler-Signaturen procedure(Sender: TObject) of object; bleiben möchtest, so kannst Du dir die addXYlistener-Methoden von Java für jedes Event inklusive der zugehörigen removeXYlistener-Methoden nachbauen. Die Abarbeitung eines Events erfolgt in diesem Falle dann mit etwa folgendem Code:

[...]

Wichtig ist hier anzumerken, dass man keine Pointer für Methoden-Zeiger verwenden darf, da es sonst ganz nette AccessViolations gibt. Die interne Verwaltung des XYevents müsste also so hier geschehen:

[...]

Der Rest sollte offensichtlich sein ;-)

Dass ich hier dann Add/Remove-Methoden benötige ist mir klar und auch bei den anonymen Methoden/Objekten in Java gebe ich dir recht!

Was du aber im Grunde sagst, ist dass ich meinen "array of TTestEvent"-Ansatz verwenden soll und mich um die einzelnen Einträge im Array (nil/benutzt), sowie das dynamische Erweitern des Arrays selber kümmern muss.

Korrekt. Hier die VCL-Klassen dazu zu bringen, das für einen selber zu machen, ist einfach aufwändiger, als das schnell selber richtig zu machen.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
baka0815 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Mi 10.12.08 16:14 
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Jedes Objekt gehört in einen EIGENEN Resourcen-Schutzblock. Auch wenn es Schreibaufwand ist.

Kann denn bei der Freigabe des Objekts etwas schiefgehen oder wozu der separate Block?

Jap. kann. z.B. wenn durch irgendein Problem beim Abarbeiten des Konstruktors eine Access Violation auftaucht oder beim Schließen eines Sockets ein Fehler geschmissen wird. Du kennst das ja sicherlich mit den Pferden und den Apotheken ... :mrgreen:

Ah, jetzt weiß ich auch was du meinst. Dachte beim .Free könnte was schief laufen, aber dass das Create nicht richtig durchlaufen könnte, daran habe ich nicht gedacht. :roll:
Hab's oben angepasst - sicherheitshalber.
Das mit den Pferden und Apotheken kenne ich allerdings nicht - scheint irgendwie an mir vorbei gegangen zu sein... (vermute du meinst das hier, das kannte ich bisher nur ohne Apotheke).

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
Was du aber im Grunde sagst, ist dass ich meinen "array of TTestEvent"-Ansatz verwenden soll und mich um die einzelnen Einträge im Array (nil/benutzt), sowie das dynamische Erweitern des Arrays selber kümmern muss.

Korrekt. Hier die VCL-Klassen dazu zu bringen, das für einen selber zu machen, ist einfach aufwändiger, als das schnell selber richtig zu machen.

Ok, dann auf jeden Fall vielen Dank für die Tipps und Infos!
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mi 10.12.08 16:24 
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconbaka0815 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Jedes Objekt gehört in einen EIGENEN Resourcen-Schutzblock. Auch wenn es Schreibaufwand ist.

Kann denn bei der Freigabe des Objekts etwas schiefgehen oder wozu der separate Block?

Jap. kann. z.B. wenn durch irgendein Problem beim Abarbeiten des Konstruktors eine Access Violation auftaucht oder beim Schließen eines Sockets ein Fehler geschmissen wird. Du kennst das ja sicherlich mit den Pferden und den Apotheken ... :mrgreen:

Ah, jetzt weiß ich auch was du meinst. Dachte beim .Free könnte was schief laufen, aber dass das Create nicht richtig durchlaufen könnte, daran habe ich nicht gedacht. :roll:
Hab's oben angepasst - sicherheitshalber.

Auch ein Free kann crashen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
constructor TMyObject.Create;
begin
    SetLength(fSomeField, 1);
end;

destructor TMyObject.Destroy;
Begin
    while Length(FSomeField) > 0 do
    Begin
        FSomeField[Length(FSomeField)].Free; //Index out of Bounds-Exception
    end;
end;


Aber das mit dem Konstruktor ist der häufigere Fall auf Grund von Speichermangel ...

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
baka0815 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Mi 10.12.08 17:14 
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
type TEvent = procedure of object;
type TEvents = array of TEvent;

type
  TKlasse = class
  private
    FEvents: TEvents;
    FEventCount: Integer;
  public
    procedure AddEventListener(Listener: TEvent);
    procedure RemoveEventListener(Listener: TEvent);
    procedure RemoveAllEventListener;
end;

[...]

procedure TKlasse.RemoveEventListener(Listener: TEvent);
var
  I: Integer;
begin
  for I := Low(FEvents) to High(FEvents) do
  begin
    if (Assigned(FEvents[I])) and 
       (FEvents[I] = Listener) then
    begin
      FEvents[I] := nil;
      Dec(FEventCount);
      Break;
    end;
  end;
end;


In der markierten Zeile bekomme ich die Meldung "E2008: Inkompatible Typen". Warum? Ist doch beides vom Typ TEvent.

Auch eine Abfrage auf if (FEvents[I] = nil) ... verweigert er mir aus dem selben Grund.
Wieso darf ich denn auf nil setzen, aber nicht darauf abfragen?
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mi 10.12.08 20:24 
Um das Ziel eines Funktionszeigers zu vergleichen, musst Du @Zeigername abfragen. Ansonsten versucht der Compiler an der Stelle die Funktion aufzurufen.

Ach ja: Wozu brauchst Du die Zählvariable? Das geht auch mit Length direkt abzufragen.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
baka0815 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 489
Erhaltene Danke: 14

Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
BeitragVerfasst: Do 11.12.08 10:20 
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Um das Ziel eines Funktionszeigers zu vergleichen, musst Du @Zeigername abfragen. Ansonsten versucht der Compiler an der Stelle die Funktion aufzurufen.

Macht Sinn! Wenn's 'ne Funktion wäre, würde er ja den Rückgabewert vergleichen, ok.

Was sollte ich denn prüfen? Assigned(FEvents[I]) oder @FEvents[I] <> nil? Habe irgendwo mal gelesen, dass Assigned nicht so zuverlässig sein soll?!

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Ach ja: Wozu brauchst Du die Zählvariable? Das geht auch mit Length direkt abzufragen.

Weil ich wissen möchte, wieviele Listener tatsächlich registriert sind.
Wenn nun per Remove der 3. von 5 Listenern gelöscht wird, habe ich ja eine Lücke. Wenn nun ein weiterer Listener hinzugefügt wird, soll der diese Lücke ausfüllen und nicht hinten an gestellt werden.
matze
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 4613
Erhaltene Danke: 24

XP home, prof
Delphi 2009 Prof,
BeitragVerfasst: Do 11.12.08 10:32 
Nur so am Rande ohne jetzt die ganze Diskussion bis hierher verfolgt und gelesen zu haben:
Ich hatte vor nicht all zu langer Zeit ein OpenSource Projekt im Internet gesehen, mit dem man MultiCast Events in Delphi bauen kann.

_________________
In the beginning was the word.
And the word was content-type: text/plain.