Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Auf Änderungen in einer TObjectList<T> reagieren (https://www.delphipraxis.net/161145-auf-aenderungen-einer-tobjectlist-t-reagieren.html)

RWarnecke 19. Jun 2011 10:14

Delphi-Version: 2010

Auf Änderungen in einer TObjectList<T> reagieren
 
Hallo zusammen,

in diesem Beitrag wurde ich darauf aufmerksam gemacht, warum ich die TObjectList nicht durch eine TObjectList<T> ersetze und mit Generics arbeite. Ich habe mir einige Tutorials durchgelesen und folgendes mal zusammengebaut :
Delphi-Quellcode:
type
  TModifyActionList = (lnNone, lnChange, lnDelete);
  TZahlungsart = class
  private
    FOnChange      : TNotifyEvent;
    FModifyAction  : TModifyActionList;
    FID            : Integer;
    FZahlungsart   : string;
    procedure SetID(Value: Integer);
    procedure SetZahlungsart(Value: string);
  public
    property ID          : Integer          read FID          write SetID;
    property Zahlungsart : string           read FZahlungsart write SetZahlungsart;
    property ModifyAction : TModifyActionList read FModifyAction write FModifyAction;
    property OnChange    : TNotifyEvent     read FOnChange    write FOnChange;
  end;

var
  ZahlungsartList : TObjectList<TZahlungsart>;
  Zahlungsart: TZahlungsart;
  CounterDB: Integer;

procedure TForm3.Btn_1Click(Sender: TObject);
begin
  CounterDB := 0;
  L_1.Caption := 'Anzahl der Objekte in der Liste :' + IntToStr(ZahlungsartList.Count);
  Edt_1.Text := IntToStr(ZahlungsartList.Items[CounterDB].ID);
  Edt_2.Text := ZahlungsartList.Items[CounterDB].Zahlungsart;
end;

procedure TForm3.Edt_2Exit(Sender: TObject);
begin
  ZahlungsartList.Items[CounterDB].ModifyAction := lnChange;
  ZahlungsartList.Items[CounterDB].Zahlungsart := Edt_2.Text;
  ZahlungsartList.Items[CounterDB].ModifyAction := lnNone;
end;

procedure TForm3.FormCreate(Sender: TObject);
begin
  ZahlungsartList := TObjectList<TZahlungsart>.Create;
  if not UniCon_1.Connected then
    UniCon_1.Connect;
  with UniQuery_1 do
  begin
    SQL.Text := 'SELECT * FROM Zahlungsarten;';
    Open;
    while not Eof do
    begin
      Zahlungsart := TZahlungsart.Create;
      Zahlungsart.OnChange := ZahlungsartChange;
      Zahlungsart.ID := FieldByName('ID').AsInteger;
      Zahlungsart.Zahlungsart := FieldByName('Zahlungsart').AsString;
      ZahlungsartList.Add(Zahlungsart);
      Next;
    end;
    Close;
  end;
end;

procedure TForm3.SBtn_1Click(Sender: TObject);
begin
  CounterDB := CounterDB - 1;
  Edt_1.Text := IntToStr(ZahlungsartList.Items[CounterDB].ID);
  Edt_2.Text := ZahlungsartList.Items[CounterDB].Zahlungsart;
end;

procedure TForm3.SBtn_2Click(Sender: TObject);
begin
  CounterDB := CounterDB + 1;
  Edt_1.Text := IntToStr(ZahlungsartList.Items[CounterDB].ID);
  Edt_2.Text := ZahlungsartList.Items[CounterDB].Zahlungsart;
end;

procedure TForm3.ZahlungsartChange;
begin
  with UniQuery_1 do
  begin
    SQL.Text := 'UPDATE OR INSERT INTO Zahlungsarten (ID, Zahlungsart) VALUES (:ID, :Zahlungsart) MATCHING (ID);';
    ParamByName('ID').AsInteger := ZahlungsartList.Items[CounterDB].ID;
    ParamByName('Zahlungsart').AsString := ZahlungsartList.Items[CounterDB].Zahlungsart;
    Execute;
  end;
end;

{ TZahlungsart }

procedure TZahlungsart.SetID(Value: Integer);
begin
  FID := Value;
  if (Assigned(FOnChange)) and (FModifyAction = lnChange) then
    FOnChange(Self);
end;

procedure TZahlungsart.SetZahlungsart(Value: string);
begin
  FZahlungsart := Value;
  if (Assigned(FOnChange)) and (FModifyAction = lnChange) then
    FOnChange(Self);
end;
Da ich noch am Anfang stehe mit Generics, habe ich noch Fragen :

1.) Ist das soweit schon mal richtig oder gibt es da noch Verbesserungen ? Wenn ja, was kann man im allgemeinen verbessern und auch in Bezug auf mehrere TObjectList<TKlassenname>.Create's ?
2.) Wie realisiere ich es, dass das OnChange-Event bei neuen Datensätzen ausgelöst wird ?
3.) In meiner alten Konstelation hatte ich noch eine extra Procedure für das Löschen eines Datensatzes, wie mache ich das hier in diesem Beispiel ?

Uwe Raabe 19. Jun 2011 12:22

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
1.) Ist das soweit schon mal richtig oder gibt es da noch Verbesserungen ? Wenn ja, was kann man im allgemeinen verbessern und auch in Bezug auf mehrere TObjectList<TKlassenname>.Create's ?

Du solltest einen neuen Typ
Delphi-Quellcode:
TZahlungsartList = TObjectList<TZahlungsart>;
deklarieren.

Zitat:

Zitat von RWarnecke (Beitrag 1107277)
2.) Wie realisiere ich es, dass das OnChange-Event bei neuen Datensätzen ausgelöst wird ?

Überschreibe in TZahlungsartList die Methode Notify, dann kannst du auf die verschiedenen Ereignisse reagieren. Das gilt übrigens auch für den nicht-generischen Ansatz.

Zitat:

Zitat von RWarnecke (Beitrag 1107277)
3.) In meiner alten Konstelation hatte ich noch eine extra Procedure für das Löschen eines Datensatzes, wie mache ich das hier in diesem Beispiel ?

Wie hast du es bisher gemacht?

RWarnecke 19. Jun 2011 13:21

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1107290)
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
1.) Ist das soweit schon mal richtig oder gibt es da noch Verbesserungen ? Wenn ja, was kann man im allgemeinen verbessern und auch in Bezug auf mehrere TObjectList<TKlassenname>.Create's ?

Du solltest einen neuen Typ
Delphi-Quellcode:
TZahlungsartList = TObjectList<TZahlungsart>;
deklarieren.

In wie weit soll mir das helfen ?

Zitat:

Zitat von Uwe Raabe (Beitrag 1107290)
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
2.) Wie realisiere ich es, dass das OnChange-Event bei neuen Datensätzen ausgelöst wird ?

Überschreibe in TZahlungsartList die Methode Notify, dann kannst du auf die verschiedenen Ereignisse reagieren. Das gilt übrigens auch für den nicht-generischen Ansatz.

Das heißt also ich kann die TObjectList<TZahlungsart> genauso behandeln wie eine nicht generische ObjectList ?

Zitat:

Zitat von Uwe Raabe (Beitrag 1107290)
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
3.) In meiner alten Konstelation hatte ich noch eine extra Procedure für das Löschen eines Datensatzes, wie mache ich das hier in diesem Beispiel ?

Wie hast du es bisher gemacht?

Ich habe das bis jetzt folgendermaßen gemacht :
Delphi-Quellcode:
  TZahlungsartObjListe = class(TObjectList)
  private
    FOnChange    : TZahlungsartListeAddEvent;
    FModifyAction : TModifyActionList;
    FNewRecord   : Boolean;
    procedure DeleteRecord(Klasse: TZahlungsart);
  protected
    function getItem(Index: Integer): TZahlungsart; virtual;
    procedure setItem(Index: Integer; Objekt: TZahlungsart); virtual;
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    function Add(Objekt: TZahlungsart): Integer; virtual;
    function NewRecord(Objekt: TZahlungsart): Integer; virtual;
    function Remove(Objekt: TZahlungsart): Integer; virtual;
    function IndexOf(Objekt: TZahlungsart): Integer; virtual;
    procedure Insert(Index: Integer; Objekt: TZahlungsart); virtual;
    property Items[index: Integer]: TZahlungsart read getItem write setItem; default;
    property ModifyAction : TModifyActionList read FModifyAction write FModifyAction;
    property OnChange: TZahlungsartListeAddEvent read FOnChange write FOnChange;
  end;

{ TZahlungsartObjListe }

function TZahlungsartObjListe.Add(Objekt: TZahlungsart): Integer;
begin
  FNewRecord := False;
  Result := inherited Add(Objekt);
end;

procedure TZahlungsartObjListe.DeleteRecord(Klasse: TZahlungsart);
begin
  with DM_Main.UniQuery_Zahlungsart do
  begin
    SQL.Text := 'DELETE FROM Zahlungsarten WHERE ID = :ID AND Zahlungsart = :Zahlungsart;';
    ParamByName('ID').AsInteger := Klasse.FID;
    ParamByName('Zahlungsart').AsString := Klasse.FZahlungsart;
    Execute;
  end;
end;

function TZahlungsartObjListe.getItem(Index: Integer): TZahlungsart;
begin
  Result := TZahlungsart(inherited Items[Index]);
end;

function TZahlungsartObjListe.IndexOf(Objekt: TZahlungsart): Integer;
begin
  Result := inherited IndexOf(Objekt);
end;

procedure TZahlungsartObjListe.Insert(Index: Integer;
  Objekt: TZahlungsart);
begin
  inherited Insert(Index, Objekt);
end;

function TZahlungsartObjListe.NewRecord(Objekt: TZahlungsart): Integer;
begin
  FNewRecord := True;
  Result := inherited Add(Objekt);
end;

procedure TZahlungsartObjListe.Notify(Ptr: Pointer; Action: TListNotification);
begin
  if (Assigned(FOnChange)) and (FNewRecord) and (Action <> lnDeleted) then
  begin
    FNewRecord := False;
    FOnChange(Self, TZahlungsart(Ptr));
  end;
  if (FModifyAction = lnDelete) then
  begin
    DeleteRecord(TZahlungsart(Ptr));
  end;
  inherited Notify(Ptr, Action);
end;

function TZahlungsartObjListe.Remove(Objekt: TZahlungsart): Integer;
begin
  Result := inherited Remove(Objekt);
end;

procedure TZahlungsartObjListe.setItem(Index: Integer;
  Objekt: TZahlungsart);
begin
  inherited Items[Index] := Objekt;
end;

Stevie 19. Jun 2011 13:39

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
1.) Ist das soweit schon mal richtig oder gibt es da noch Verbesserungen ? Wenn ja, was kann man im allgemeinen verbessern und auch in Bezug auf mehrere TObjectList<TKlassenname>.Create's ?

Was meinst du mit verbessern im Bezug auf das Create? Du kannst für jede TObjectList<TKlassenname> eine Redefinition machen, wie Uwe schon ansprach. Würd ich aber nicht empfehlen.
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
2.) Wie realisiere ich es, dass das OnChange-Event bei neuen Datensätzen ausgelöst wird ?

Listen haben das OnNotify Event, was dich über hinzufügen und entfernen von Datensätzen informiert. Du willst ja zusätzlich noch ein Notify haben, wenn sich innerhalb eines Listenelements was ändert. Da würde ich eine Ableitung von TObjectList<T> schreiben und das dort noch reinnehmen.
Zitat:

Zitat von RWarnecke (Beitrag 1107277)
3.) In meiner alten Konstelation hatte ich noch eine extra Procedure für das Löschen eines Datensatzes, wie mache ich das hier in diesem Beispiel ?

Entweder mit Delete oder Remove, je nachdem ob du das konkrete Element hast oder den Index davon, beides geht.

RWarnecke 19. Jun 2011 13:59

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Stevie (Beitrag 1107308)
Was meinst du mit verbessern im Bezug auf das Create?

Ob ich in meiner FormCreate aus Beitrag #1 soweit alles richtig gemacht habe oder ob es da noch etwas zu verbessern gibt.
Zitat:

Zitat von Stevie (Beitrag 1107308)
Du kannst für jede TObjectList<TKlassenname> eine Redefinition machen, wie Uwe schon ansprach. Würd ich aber nicht empfehlen.

Warum empfiehlst Du das nicht. Ich brauche doch immer nur eine ObjectListe ? Oder habe ich da noch ein Verständnisproblem ?

Edit:
Ich habe mir jetzt mal eine Ableitung für TObjectList<TKlassenname> zusammengebaut, die anscheinend Recht gut funktioniert. Die komplette Datei findet Ihr im Anhang.
In der Delete-Procedure, lese ich über die RTTI den Klassennamen aus und dann werden entsprechend den ermittelten Namen die einzelnen DELETE SQL-Befehle ausgeführt. Oder ist die RTTI zu viel für das Auslesen des Klassennamens ?

Gibt es da noch Verbesserungsvorschläge ?

Uwe Raabe 19. Jun 2011 16:07

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Zitat:

Zitat von Stevie (Beitrag 1107308)
Du kannst für jede TObjectList<TKlassenname> eine Redefinition machen, wie Uwe schon ansprach. Würd ich aber nicht empfehlen.

Da würde mich aber doch wirklich das Warum interessieren.

Eine Redefinition hat den Vorteil, daß sie sehr leicht geändert werden kann. Im einfachsten Fall ist es einfach nur ein Alias
Delphi-Quellcode:
TZahlungsartList = TObjectList<TZahlungsart>;
, den ich natürlich auch weglassen könnte. Wenn ich dann aber an 1001 Stellen eine Variable definiert und eine Instanz dieser Liste erzeugt habe und dann feststelle, daß ich doch eine Ableitung brauche, bin ich mit dem Alias in nahezu Null-Komma-Nichts fertig und muss die Deklarationen und Instanzierungen nicht alle ändern.

Ich würde im Gegenteil sogar (fast) immer empfehlen, eine generische Liste über einen Alias zu definieren. Der Code liest sich einfach auch flüssiger.

RWarnecke 25. Jun 2011 20:13

AW: Auf Änderungen in einer TObjectList<T> reagieren
 
Hallo zusammen,

ich habe jetzt versucht das Beispiel aus Beitrag #5 auf mein Programm zu übertragen. Nur leider bekomme ich den Fehler : Internal Error URV1111. Dazu habe ich einige Beiträge gefunden, nur leider verstehe ich nicht ganz den Hintergrund, was da passiert. Anscheinend muss das irgendwas mit den Generics zu tun haben.

Hier die drei Beiträge die ich gefunden habe :
Link1
Link2
Link3

Ich benutze eine Ableitung von TObjectList<T> und in den Beiträgen wird von TArray<T> gesprochen. Kann mir bitte jemand weiterhelfen ?


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:31 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz