Wie TList über Property in Klasse ansprechen?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
photor
Beiträge: 198
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux (L 2.0.10 FPC 3.2.0)
CPU-Target: 64Bit

Wie TList über Property in Klasse ansprechen?

Beitrag von photor »

Hallo Forum,

ich bin gerade dabei, mir eine Log-Klasse zu schreiben; die Log-Einträge werden in einer LogQueue: TList gespeichert und dann bei Bedarf auf ein (bzw. gerade zwei) LogDevices ausgegeben. Diese LogDevices können ein extra Fenster, ein Memo, eine Text-Datei oder ... sein (Memo und Text-Datei sind rudimentär implementiert und werden probiert).

Momentan (s.u.) habe ich einfach 2 Devices definiert und agieren parallel - das ist sehr unelegant. Mir schwebt eigentlich eine Liste von Devices vor, auf die dann die Log-Messages ausgegeben werden können (alle wichtigen Meldungen auf dem Memo; alles gleichzeitig in's File). Es gibt eine Basisklasse TLogDevices von der ich die konkreten LogDevices ableite (z.b. TLogDeviceMemo, TDeviceFile).

Hier die Logger-Klasse:

Code: Alles auswählen

TNewLogger = class
    private
      LogQueue: TList;

      FNoLogEntries: integer;
      FLastDisplayed: integer;

      FLogDevicesList: TList;   // soll die beiden unten ersetzen

      FLogDevice: TLogDevice;   // wohin das alles ausgeben?
      FLogDevice2: TLogDevice;  // 2. zum Testen

      procedure SetLogDevice(aLogDevice: TLogDevice);
      procedure SetLogDevice2(aLogDevice: TLogDevice);

    public
      property NoLogEntries: integer read FNoLogEntries;
      property LastDisplayed: integer read FLastDisplayed;

      property LogDevice: TLogDevice read FLogDevice write SetLogDevice;
      property LogDevice2: TLogDevice read FLogDevice2 write SetLogDevice2;

      constructor Create;

      procedure LogMessage(aLogLevel: TLogLevel; aLogText: string);
      procedure DisplayLogMessages;
  end;
Mir ist nun nicht klar, wie ich die DeviceList: TList am geschicktesten implementiere, so dass ich Devices hinzufügen oder auch entfernen kann - und am liebsten wieder als Property ansprechbar. Kann mir jemand eine Tritt in die richtige Richtung geben?

Ciao,
Photor

wp_xyz
Beiträge: 3269
Registriert: Fr 8. Apr 2011, 09:01

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von wp_xyz »

Wahrscheinlich willst du die Details der Liste vor der Außenwelt verbergen (wenn nicht, dann hast du einen Haufen Mehrarbeit, denn du musst sicherstellen, dass niemand etwas sinnloses in der Liste speichert).

Als erstes brauchst du ein Array-Property LogDevices[AIndex]: TLogDevice, das über den angegebenen Index per GetLogDevice aus der Liste ausgelesen und per SetLogDevice in die Liste eingeschrieben wird. Außerdem eine Methode AddLogDevice(ALogDevice), um ein neues Device zu registrieren (evtl besserer Name: RegisterLogDevice(ALogDevice). Wahrscheinlich ist auch ein DeleteLogDevice (oder UnregisterLogDevice) sinnvoll, höchstwahrscheinlich auch ein ClearAllLogDevices. Und ein NumLogDevices, um die Anzahl der gerade registrierten Devices abzufragen.

Und wenn die Liste von dem Logger verwaltet wird und die TLogDevice's Klassen sind, wäre es wahrscheinlich sinnvoller, statt TList eine TObjectList zu nehmen, denn diese räumt die Listenelemente (LogDevices) automatisch auf.

Das würde dann (ungetestet) ungefähr so aussehen:

Code: Alles auswählen

uses
  Contnrs;

type
  TNewLogger = class
  private
    FLogDeviceList: TObjectList;
    function GetLogDevice(AIndex: Integer): TLogDevice;
    procedure SetLogDevice(AIndex: Integer; ADevice: TLogDevice);
  public
    ..
    Function AddLogDevice(ADevice: TLogDevice): Integer;
    procedure DeleteLogDevice(AIndex: Integer);
    procedure ClearAllLogDevices;
    function NumLogDevices: Integer;

    property LogDevices[AIndex: Integer] read GetLogDevices write SetLogDevices;
  end;

  function TNewLogger.GetLogDevice(AIndex: Integer): TLogDevice;
  begin
    Result := TLogDevice(FLogDeviceList[AIndex]);
  end;

  procedure TNewLogger.SetLogDevice(AIndex: Integer; ADevice: TLogDevice);
  var
    dev: TLogDevice;
  begin
    if ADevice = nil then
      raise Exception.Create('Kein NIL als LogDevice');
    dev := GetLogDevice(AIndex);
    // Properties kopieren von ADevice nach dev
  end;

  function TNewLogger.AddLogDevice(ADevice: TLogDevice): Integer;
  begin
    if ADevice = nil then   // wahrscheinlich willst du keine nil-Zeiget in der Liste haben
      exit(-1);
    Result := FLogDevices.Add(ADevice);
  end;

  procedure TNewLogger.DeleteLogDevice(AIndex: Integer);
  begin
    FLogDevices.Delete(AIndex);   // Das Zerstören des TLogDevice wird von TObjectList gemacht
  end;

  procedure TNewLogger.ClearAllLogDevices;
  begin
    FLogDevices.Clear;  // TLogDevices räumt alles auf
  end;
  
  function TNewLogger.NumLogDevices: Integer;
  begin
    Result := FLogDevices.Count;
  end;    

Benutzeravatar
photor
Beiträge: 198
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux (L 2.0.10 FPC 3.2.0)
CPU-Target: 64Bit

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von photor »

Danke wp_xyz.

Ich würde gerne so viel verstecken, wie möglich (nächste Ausbaustufe könnte ein Interface werdeb; aber darüber weiß ich noch fast gar nichts).

Das mit dem Index in der Property hatte ich schon irgendwo gesehen, aber nicht so richtig verstanden. Ich werde mir deinen Code mal in Ruhe zu Gemüte führen und austesten; beim ersten drüber schauen glimmte die „ich glaube, ich verstehe“-Lampe schonmal auf.

An eine TObjectList hatte ich noch nicht gedacht (TList hatte bisher immer getan). Auch das schau ich mir nochmal an - auch die TLists, die ich schon verwende.

Eventuell komme ich nochmal mit Fragen zurück.

Danke,
Photor

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 598
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Winux (L 2.0.7 FPC 3.04)
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von fliegermichl »

Du könntest die Property LogDevices auch als default deklarieren. Dann kannst du ohne Typecast darauf zugreifen

Code: Alles auswählen

property LogDevices[AIndex: Integer] read GetLogDevices write SetLogDevices; default;
Wen du dann eine Instanz von TNewLogger hast, kannst du per

Code: Alles auswählen

NewLogger[0].Log('Message');
direkt auf die Einträge zugreifen.

Benutzeravatar
photor
Beiträge: 198
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux (L 2.0.10 FPC 3.2.0)
CPU-Target: 64Bit

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von photor »

Hallo fliegermichl, hallo alle,

danke für die Tipps. Die werde ich bestimmt noch ausprobieren.

Die wesentlichen Tipps von wp_xyz habe ich mal eingebaut - und es tut erstmal. Obwohl da wahrscheinlich noch irgendwo Kinken drin sind (das ganze Zeug a la "virtual", "abstract", "override" etc ist noch nicht wirklich in meinem Kopf). Und das ganze Aufräumen am Ende des Programms fehlt auch noch.

Kann man das hier

Code: Alles auswählen

for j:=0 to FLogDeviceList.Count-1 do
        GetLogDevice(j).WriteMessage(LogNodeData);
durch so etwas

Code: Alles auswählen

for dev in FLogDeviceList do
	dev.WriteMessage(LogNodeData);
ersetzen? Bei meinem Versuch erhielt ich Error: Incopatible types: got "pointer" expected "TLogDevice.. Wäre irgendwie eleganter.

Ciao,
Photor

wp_xyz
Beiträge: 3269
Registriert: Fr 8. Apr 2011, 09:01

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von wp_xyz »

photor hat geschrieben:
Mi 8. Jul 2020, 15:54
das ganze Zeug a la "virtual", "abstract", "override" etc ist noch nicht wirklich in meinem Kopf
Sorry - das wäre jetzt eine längere Abhandlung, du findest das sicher irgendwo im Internet, z.B. https://www.delphi-treff.de/object-pasc ... -methoden/.
photor hat geschrieben:
Mi 8. Jul 2020, 15:54
Und das ganze Aufräumen am Ende des Programms fehlt auch noch.
Wenn die LogDevices von TObject abstammen und in einer TObjectList gespeichert sind, dann weiß die Liste, dass es jedes Listenelement einen Destructor Destroy gibt und ruft diesen entsprechend automatisch auf. Es muss also nur der Destructor von TLogDevice so aufgebaut sein, dass eventuell angeforderter Speicher wieder freigegeben wird. Entfällt natürlich, wenn kein Speicher angefordert wurde.
photor hat geschrieben:
Mi 8. Jul 2020, 15:54
Kann man das hier

Code: Alles auswählen

for j:=0 to FLogDeviceList.Count-1 do
        GetLogDevice(j).WriteMessage(LogNodeData);
durch so etwas

Code: Alles auswählen

for dev in FLogDeviceList do
	dev.WriteMessage(LogNodeData);
ersetzen? Bei meinem Versuch erhielt ich Error: Incopatible types: got "pointer" expected "TLogDevice.. Wäre irgendwie eleganter.
Ja. Dazu brauchst du einen Enumerator, der mit dem TLogDevice arbeitet (der Default-Enumerator von TFPList setzt nur einen Pointer voraus). Schau dir in classes.inc (im Ordner (fpc)/rtl/objpas/classes) den Aufbau von TFPListEnumerator an. Du musst das neu implementieren (du kannest wahrscheinlich sogar abschreiben) und immer dort wo "Pointer" steht "TLogDevice" verwenden. Den neuen Enumerator musst du in der Methode GetEnumerator der Listenklasse erzeugen. (wie in TFPList.GetEnumerator). Allerdings habe ich in meinem Beispiel TObjectList direkt verwendet, so dass du da nicht rankommst. Daher leite dir von TObjectList eine neue TLogDeviceList ab, in der du das einbauen kannst.

Das ganze müsste mit Generics einfacher gehen, aber da bin ich nicht ganz firm...

Socke
Lazarusforum e. V.
Beiträge: 2748
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von Socke »

photor hat geschrieben:
Mi 8. Jul 2020, 15:54
Kann man das hier

Code: Alles auswählen

for j:=0 to FLogDeviceList.Count-1 do
        GetLogDevice(j).WriteMessage(LogNodeData);
durch so etwas

Code: Alles auswählen

for dev in FLogDeviceList do
	dev.WriteMessage(LogNodeData);
ersetzen? Bei meinem Versuch erhielt ich Error: Incopatible types: got "pointer" expected "TLogDevice.. Wäre irgendwie eleganter.
Hier kommst du mit einem einfachen Typecast recht schnell ans Ziel:

Code: Alles auswählen

var
  dev: Pointer; // Listenelement
begin
  for dev in FLogDeviceList do
    TLogDevice(dev).WriteMessage(LogNodeData);
end.
Oder aber du nutzt Generics und erstellst eine spezialisierte Klasse:

Code: Alles auswählen

uses fgl;
type
  TLogDeviceList = specialize TFPGObjectList<TLogDevice>;
var
  dev: TLogDevice;
  FLogDeviceList;
 begin
   FLogDeviceList := TLogDeviceList.Create;
   // Liste befüllen etc.
   // jetzt hat dev den selben Typ wie die Elemente von FLogDeviceList - also ist kein Typecast mehr notwendig.
   for dev in FLogDeviceList do
     dev.WriteMessage(LogNodeData);
 end;
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
photor
Beiträge: 198
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux (L 2.0.10 FPC 3.2.0)
CPU-Target: 64Bit

Re: Wie TList über Property in Klasse ansprechen?

Beitrag von photor »

Moin Socke,

danke für den Tipp. Die Sache mit den Generics wollte ich mir sowieso nochmal ansehen. Bislang hat mich da abgehalten, dass es da doch ein paar Unterschiede zu Delphi gibt/gab.

Das mit dem Cast - meine ich - hätte ich schon mal probiert, hab aber eine Compiler-Meldung bekommen. Ich werde mir das nochmal in Ruhe ansehen.

Ciao,
Photor

Antworten