![]() |
TListView - viele Daten - viel Zeit ...
Hallo zusammen,
ich habe so ca. 2.000 Datensätze, die angezeigt werden sollen - bis jetzt mittels TListView. Das funktioniert auch alles wunderschön, dauert aber im Aufbau uuunglaublich lange. Ein ernsthafter Geschwindigkeitsgewinn ergibt sich schon, wenn man vor dem Befüllen der Liste sämtliche Spaltenbreiten auf 0 setzt, und sie, nach dem Befüllen, wiederherstellt. Gibt es eine Komponente, die hierfür geeigneter ist? Übrigens: D5 Std, also leider nix mit Datenbank o.ä. Für einen Tipp wäre ich dankbar Viele Grüße Marco |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
Sonst halt den virtuellen Modus verwenden. |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
die OH kennt nichts "virtuelles". Gruß K-H |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
![]() Mit den angesprochenen Methoden ![]() ![]() |
AW: TListView - viele Daten - viel Zeit ...
Vielen Dank!
Ich hätte "OwnerData" nie mit "virtuell" in Verbindung gebracht, man lernt nie aus. Gruß K-H |
AW: TListView - viele Daten - viel Zeit ...
Hallo zusammen,
Tja, wenn's bei euch so schnell geht, dann liegt's evtl. an meinem Screenreader (Programm das den Bildschirminhalt vorliest), das es so lange dauert - probier ich gleich mal aus. Aaaber: Begin- und EndUpdate hab ich bereits verwendet. Aber, vielleicht bin ich auch heute zu doof, was nützt mir die OwnerData? Ich kann hier zwar Zeiger auf die korrespondierenden Objekte hinterlegen - und jetzt? Viele Grüße Marco |
AW: TListView - viele Daten - viel Zeit ...
... vielleicht ist ja was anderes falsch...
Delphi-Quellcode:
ja, ;-), ich weiß, ich hätte alles auf Englisch machen sollen... schlechte Angewohnheit...
procedure TPostHauptformular.FuelleEintragsliste;
var NeueSpalte: TListColumn; NeuerEintrag: TListItem; i, x: integer; s: string; breiten: array of integer; begin with liEintraege do // normale TListView, style Report begin {Alle (alten) Spalten und ListenItems bereinigen.} columns.clear; Items.clear; {Memo vorhanden?} NeueSpalte := Columns.add; NeueSpalte.Caption := ' '; NeueSpalte.Width := ColumnTextWidth; {Versanddatum} NeueSpalte := Columns.add; NeueSpalte.Caption := 'Datum'; NeueSpalte.Width := ColumnTextWidth; // und noch 4 stück... // breiten speichern und auf 0 setzen... SetLength (breiten, Columns.Count); for i := 0 to columns.count -1 do begin breiten[i] := columns[i].Width; columns[i].width := 0 end; Items.BeginUpdate; for i := 0 to se.letzter do begin with se[i] do begin NeuerEintrag := Items.Add; neuerEintrag.Caption := s; NeuerEintrag.SubItems.Add(empfaenger); NeuerEintrag.SubItems.Add(inhalt); // und noch'n paar... end end; Items.EndUpdate; for i := 0 to columns.count -1 do columns[i].Width := breiten[i]; end end; {FuelleEintragsliste} Hab ich hier irgendwo einen Grundfehler drin... Viele Grüße Marfco |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
Delphi-Quellcode:
with liEintraege do // normale TListView, style Report
begin Items.BeginUpdate; Zitat:
|
AW: TListView - viele Daten - viel Zeit ...
Ne er hat recht, er macht nix falsch ist ein BUG in TCustomListView, der ist sogar noch in XE3 drinne...
das liegt am (ViewStyle = vsReport) der in TCustomListView.ColumnsShowing abgefragt wird. ist die spaltengröße <= ColumnTextWidth rast er jedesmal ins UpdateColumns, das macht die Verlangsamung. Müssen nicht mal mehrere Spalten definiert sein. Also UpdateColumn und UpdateColumns (bei mehreren spalten) sind die Bösewichte
Delphi-Quellcode:
bei mehreren Spalten ist der Aufrufende
procedure TListItem.SetCaption(const Value: string);
begin if Value <> Caption then begin FCaption := Value; if not Owner.Owner.OwnerData then {$IFDEF CLR} ListView_SetItemText(Handle, Index, 0, IntPtr(Integer(LPSTR_TEXTCALLBACK))); {$ELSE} ListView_SetItemText(Handle, Index, 0, LPSTR_TEXTCALLBACK); {$ENDIF} if ListView.ColumnsShowing and (ListView.Columns.Count > 0) and (ListView.Column[0].WidthType <= ColumnTextWidth) then ListView.UpdateColumns; //hier passierts if ListView.SortType in [stBoth, stText] then ListView.AlphaSort; end; end;
Delphi-Quellcode:
procedure TSubItems.SetColumnWidth(Index: Integer);
var ListView: TCustomListView; begin ListView := Owner.ListView; if ListView.ColumnsShowing and (ListView.Columns.Count > Index) and (ListView.Column[Index].WidthType = ColumnTextWidth) then ListView.UpdateColumn(Index); //hier.... end; Ne vernünftige alternative ohne Spaltenbreite auf 0 zu setzen sehe ich leider nicht :( |
AW: TListView - viele Daten - viel Zeit ...
Hallo, und herzlichen Dank für eure Antworten.
Prima, und ich dachte schon, ich hätte was grundlegendes nicht verstanden - schon witzig,daß der Bug sogar noch in den neuesten Versionen drin ist... Bleibt also offensichtlich wirklich nur, spaltenbreite auf 0, oder gleich mit festen Spaltenbreiten zu arbeiten. Noch eine Frage zu OwnerData: OwnerData steht auf true. Im einfachsten Beispiel brauche ich dann ja nur im OnData der Listview z.B.
Delphi-Quellcode:
zu schreiben, und erhalte, sagen wir der listview.Items.Count steht auf 3, 3 Elemente.
item.caption := format ('Test%d', [item.index]);
Das funktioniert auch prima. Das OnData wird aber unglaublich oft aufgerufen, egal, ob ich mich durch die Liste bewege, oder nicht. Lasse ich mein Testprogramm, und mehr steht da wirklich nicht drin, einfach 10 Sekunden völlig in Ruhe, hab ich gut 250 Aufrufe von OnData - ähm, hab ich hier was verpaßt, denn ich dachte, das Ereignis wird nur aufgerufen, wenn wirklich was geschrieben werden muß, oder weißt der, ernsthaft, bei jedem Durchlauf, alles neu zu... Ähm, etwas ratlose Grüße Marco |
AW: TListView - viele Daten - viel Zeit ...
Wenn das Control neu gezeichnet wird. wird der Event pro Item getriggert.
|
AW: TListView - viele Daten - viel Zeit ...
Ich würde zunächst einmal die Methode aufteilen
Delphi-Quellcode:
und das auch mit einem
BeginUpdate ... EndUpdate
Delphi-Quellcode:
absichern.
try ... finally
Wenn du eine generellere Methode zum Vorbereiten einer ListView haben möchtest, dann definierst du dir eben:
Delphi-Quellcode:
Wie man sieht werden die Methoden wesentlich kürzer aber dafür flexibler :)
TColumnDef = record
Caption : string; Width : Integer; end; procedure ListViewPrepare( ALV : TListView; AColumnDefs : array of TColumnDef ); var LIdx : Integer; begin ALV.Columns.BeginUpdate; try ALV.Columns.Clear; for LIdx := Low(AColumnDefs) to High(AColumnDefs) do begin LColumn := ALV.Columns.Add; LColumn.Caption := AColumnDefs[LIdx].Caption; LColumn.Width := AColumnDefs[LIdx].Width; end; finally ALV.Columns.EndUpdate; end; end; |
AW: TListView - viele Daten - viel Zeit ...
<OT>@Sir Rufo: Schönes Beispiel für ein gelungenes Refactoring: Durch Struktur Klarheit in den Code gebracht und dann noch eine Generalisierung abgeleitet :)</OT>
|
AW: TListView - viele Daten - viel Zeit ...
Nur mal so aus Spaß ... komplett dynamisch :)
Zwei Listen (Personen, Adressen) werden in einer ListView präsentiert. Klick auf Button1 zeigt die Personen, Klick auf Button2 die Adressen. Das Umschalten zwischen den beiden Listen benötigt bei 9000 Personen ca. 675ms Der Refresh der Personen-Liste benötigt bei 9000 Personen ca. 330ms Das Umschalten zwischen den beiden Listen benötigt bei 10000 Adressen ca. 1065ms Der Refresh der Adressen-Liste benötigt bei 10000 Adressen ca. 740ms Hier die ganzen Code-Schnipsel (sollte auch mit Delphi 7 so laufen)
Delphi-Quellcode:
unit FormMain;
interface uses {Winapi.} Windows, {Winapi.} Messages, {System.} SysUtils, {System.} Variants, {System.} Classes, {System.} Contnrs, {Vcl.} Graphics, {Vcl.} Controls, {Vcl.} Forms, {Vcl.} Dialogs, {Vcl.} StdCtrls, {Vcl.} ComCtrls, DataListContainer; type TForm1 = class( TForm ) ListView1 : TListView; Button1 : TButton; Button2 : TButton; Label1 : TLabel; procedure Button1Click( Sender : TObject ); procedure Button2Click( Sender : TObject ); private FPersons : TObjectList; FAddresses : TObjectList; FPersonsPresenter : TDataListContainer; FAddressPresenter : TDataListContainer; procedure ShowInfo( AStart, AStop : TDateTime; ACount : Integer ); public procedure AfterConstruction; override; procedure BeforeDestruction; override; end; var Form1 : TForm1; implementation {$R *.dfm} uses {System.} DateUtils, DataListToListView, Person, Address; { TForm1 } procedure TForm1.AfterConstruction; var LIdx : Integer; begin inherited; // Musterdaten erstellen FPersons := TObjectList.Create( True ); for LIdx := 1 to 3000 do begin FPersons.Add( TPerson.Create( 'Lustig, Peter', EncodeDate( 1975, 1, 1 ) ) ); FPersons.Add( TPerson.Create( 'Traurig, Walter', EncodeDate( 1975, 2, 1 ) ) ); FPersons.Add( TPerson.Create( 'Mustermann, Erika', EncodeDate( 1975, 3, 1 ) ) ); end; // Definition der Spalten FPersonsPresenter := TDataListContainer.Create; FPersonsPresenter.AddColumn( 'Name', 'Fullname', 150 ); FPersonsPresenter.AddColumn( 'Geburtstag', 'DOB', 80 ); FPersonsPresenter.DataList := FPersons; // Musterdaten erstellen FAddresses := TObjectList.Create( True ); for LIdx := 1 to 2500 do begin FAddresses.Add( TAddress.Create( 'Am Walde 23', 12345, 'Hinterm Berg' ) ); FAddresses.Add( TAddress.Create( 'Im Weiher 12', 23456, 'Vordem Berg' ) ); FAddresses.Add( TAddress.Create( 'Auf der Hecke 5', 34567, 'Beidem Berg' ) ); FAddresses.Add( TAddress.Create( 'Nebenstollen 5', 45678, 'Unterm Berg' ) ); end; // Definition der Spalten FAddressPresenter := TDataListContainer.Create; FAddressPresenter.AddColumn( 'Straße', 'Street', 150 ); FAddressPresenter.AddColumn( 'PLZ', 'ZipCode', 80 ); FAddressPresenter.AddColumn( 'Ort', 'City', 80 ); FAddressPresenter.DataList := FAddresses; end; procedure TForm1.BeforeDestruction; begin inherited; FPersonsPresenter.Free; FPersons.Free; FAddressPresenter.Free; FAddresses.Free; end; procedure TForm1.Button1Click( Sender : TObject ); var LStart, LStop : TDateTime; begin LStart := Now; PresentData( ListView1, FPersonsPresenter ); LStop := Now; ShowInfo( LStart, LStop, FPersonsPresenter.DataList.Count ); end; procedure TForm1.Button2Click( Sender : TObject ); var LStart, LStop : TDateTime; begin LStart := Now; PresentData( ListView1, FAddressPresenter ); LStop := Now; ShowInfo( LStart, LStop, FAddressPresenter.DataList.Count ); end; procedure TForm1.ShowInfo( AStart, AStop : TDateTime; ACount : Integer ); begin Label1.Caption := Format( '%d Einträge in %dms', [ACount, MilliSecondsBetween( AStop, AStart )] ); end; end.
Delphi-Quellcode:
unit Person;
interface type TPerson = class private FFullname : string; FDOB : TDate; public constructor Create( const Fullname : string; DOB : TDate ); published property Fullname : string read FFullname write FFullname; property DOB : TDate read FDOB write FDOB; end; implementation { TPerson } constructor TPerson.Create( const Fullname : string; DOB : TDate ); begin inherited Create; FFullname := Fullname; FDOB := DOB; end; end.
Delphi-Quellcode:
unit Address;
interface type TAddress = class private FStreet : string; FZipCode : Integer; FCity : string; public constructor Create( const Street : string; ZipCode : Integer; const City : string ); published property Street : string read FStreet write FStreet; property ZipCode : Integer read FZipCode write FZipCode; property City : string read FCity write FCity; end; implementation { TAddress } constructor TAddress.Create( const Street : string; ZipCode : Integer; const City : string ); begin inherited Create; FStreet := Street; FZipCode := ZipCode; FCity := City; end; end.
Delphi-Quellcode:
unit DataListContainer;
interface uses Contnrs; type TDataColumnDef = record Caption : string; PropertyName : string; Width : Integer; Visible : Boolean; end; TDataColumnDefs = array of TDataColumnDef; TDataListContainer = class private FDataList : TObjectList; FColumnDefs : TDataColumnDefs; public procedure AddColumn( ACaption, APropertyName : string; AWidth : Integer; AVisible : Boolean = true ); property ColumnDefs : TDataColumnDefs read FColumnDefs; property DataList : TObjectList read FDataList write FDataList; end; implementation { TDataListContainer } procedure TDataListContainer.AddColumn( ACaption, APropertyName : string; AWidth : Integer; AVisible : Boolean ); var LIdx : Integer; begin LIdx := Length( FColumnDefs ); SetLength( FColumnDefs, LIdx + 1 ); FColumnDefs[LIdx].Caption := ACaption; FColumnDefs[LIdx].PropertyName := APropertyName; FColumnDefs[LIdx].Width := AWidth; FColumnDefs[LIdx].Visible := AVisible; end; end.
Delphi-Quellcode:
unit DataListToListView;
interface uses {Vcl.} ComCtrls, DataListContainer; procedure PresentData( AListView : TListView; AContainer : TDataListContainer ); procedure PrepareColumns( AListView : TListView; AContainer : TDataListContainer ); procedure FillData( AListView : TListView; AContainer : TDataListContainer ); implementation uses {System.} TypInfo; procedure PresentData( AListView : TListView; AContainer : TDataListContainer ); begin PrepareColumns( AListView, AContainer ); FillData( AListView, AContainer ); end; procedure PrepareColumns( AListView : TListView; AContainer : TDataListContainer ); var LCount : Integer; LIdx : Integer; LColumn : TListColumn; begin AListView.Columns.BeginUpdate; try LCount := Length( AContainer.ColumnDefs ); // Spalten hinzufügen, wenn nicht ausreichend vorhanden while AListView.Columns.Count < LCount do AListView.Columns.Add; for LIdx := 0 to AListView.Columns.Count - 1 do begin LColumn := AListView.Columns.Items[LIdx]; if LIdx < LCount then begin LColumn.Caption := AContainer.ColumnDefs[LIdx].Caption; if AContainer.ColumnDefs[LIdx].Visible then LColumn.Width := AContainer.ColumnDefs[LIdx].Width else LColumn.Width := 0; end else begin LColumn.Caption := ''; LColumn.Width := 0; end; end; finally AListView.Columns.EndUpdate; end; end; procedure FillData( AListView : TListView; AContainer : TDataListContainer ); var LIdx : Integer; LCount : Integer; LItem : TListItem; LDataItem : TObject; LColIdx : Integer; LColCount : Integer; LPropValue : string; begin AListView.Items.BeginUpdate; try if Assigned( AContainer.DataList ) then LCount := AContainer.DataList.Count else LCount := 0; if AListView.Items.Count - LCount > LCount then AListView.Items.Clear; // Zeilen hinzufügen, wenn nicht ausreichend vorhanden while AListView.Items.Count < LCount do AListView.Items.Add; // Zeilen entfernen, wenn zuviel while AListView.Items.Count > LCount do // Löschen immer vom Ende her, das spart Zeit AListView.Items.Delete( AListView.Items.Count - 1 ); for LIdx := 0 to LCount - 1 do begin LItem := AListView.Items[LIdx]; LDataItem := AContainer.DataList.Items[LIdx]; LColCount := Length( AContainer.ColumnDefs ); // SubItems LItem.SubItems.BeginUpdate; try LItem.SubItems.Clear; for LColIdx := 0 to LColCount - 1 do begin LPropValue := GetPropValue( LDataItem, AContainer.ColumnDefs[LColIdx].PropertyName, True ); if LColIdx = 0 then // Caption LItem.Caption := LPropValue else LItem.SubItems.Add( LPropValue ); end; finally LItem.SubItems.EndUpdate; end; end; finally AListView.Items.EndUpdate; end; end; end. |
AW: TListView - viele Daten - viel Zeit ...
Hallo,
joa, das ist doch mal ein Beispiel! wow!! Mein Code macht, dem Grunde nach, das Gleiche - ich hab natürlich das try-except nicht drin - und das hier kannstde natürlich allgemeiner verwenden. Meine Prozedur zum Füllen der Liste wird nur einmal aufgerufen, oder per Hand, zum Aktualsisieren der Ansicht. Und da das Befüllen mit ca. 530 Zeilen knapp 800 ms dauert, wollte ich's ein bißchen schneller haben. Deshalb kam ich ja auch auf die Idee, OwnerData auf true zu setzen, ich hänge aber immernoch daran, daß er OnData so oft aufruft, selbst wenn ich das Programm komplett in Ruhe lasse, also, denke ich, nix neu gezeichnet werden muß. Vielleicht hat ja doch noch jemand einen Tipp, sonst muß ich eben mit den 800 ms leben. Was ich, fällt mir gerade ein, noch nicht ausprobiert habe ist, die Zeilen zunächst anzulegen, und dann indiziert zu füllen - wie in deinem Beispiel - wer weiß, ob das nochwas bringt... Viele Grüße Marco |
AW: TListView - viele Daten - viel Zeit ...
Schau dir deinen Code nochmal genauer an.
Du rufst
Delphi-Quellcode:
auf, obwohl du noch nicht
Items.Clear
Delphi-Quellcode:
aufgerufen hast.
Items.BeginUpdate
Mach das und du wirst sehen, dass es schneller geht. Das Wiederverwenden der ListItems spart auch noch eine Menge Zeit ein. |
AW: TListView - viele Daten - viel Zeit ...
Auch hier poste ich einmal den Hinweis auf die VirtualTreeView:
![]() Darin haben wir durchaus auch hunderttausende Einträge, die innerhalb weniger Millisekunden angezeigt werden. Oder z.B. 10000 Einträge, die absolut live bei der Eingabe eines Filters gefiltert werden. Ich probiere heute Abend einmal das Beispiel von Sir Rufo darin aus. |
AW: TListView - viele Daten - viel Zeit ...
Könnte man den Thread nach Tutorial oder die CodeLibrary verschieben?
Gruß K-H |
AW: TListView - viele Daten - viel Zeit ...
Liste der Anhänge anzeigen (Anzahl: 1)
So, ich habe es nur ganz kurz gemacht, eigentlich könnte man das noch deutlich schöner machen. Ergebnisse jedenfalls:
Das Umschalten zwischen den beiden Listen benötigt bei 9000 Personen ca. 2ms Der Refresh der Personen-Liste benötigt bei 9000 Personen ca. 2ms Das Umschalten zwischen den beiden Listen benötigt bei 10000 Adressen ca. 2ms Der Refresh der Adressen-Liste benötigt bei 10000 Adressen ca. 2ms Das Projekt liegt im Anhang. Füge ich die Daten direkt in die Knoten als Daten ein, dauern die Operationen ca. 10ms. Sprich anders als im Anhang:
Delphi-Quellcode:
Und:
procedure FillData(AListView: TVirtualStringTree; AContainer: TDataListContainer);
var i: Integer; begin AListView.BeginUpdate; try AListView.Clear; if Assigned(AContainer.DataList) then for i := 0 to AContainer.DataList.Count - 1 do AListView.AddChild(nil, AContainer.DataList[i]); finally AListView.EndUpdate; end; end;
Delphi-Quellcode:
procedure TForm6.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); var CurrentEntry: TObject; begin CurrentEntry := TObject(Sender.GetNodeData(Node)^); if CurrentEntry is TPerson then ... |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
Meine Güte, manchmal programmiert man sich einen Mist zusammen, wie kann man denn erst Clear und dann BeginUpdate aufrufen ... Vielen Dank! Zitat:
Ich schau mir jetzt mal VirtualTreeview an - wenn das Ergebnis mit Screenreader gut auszulesen ist - warum nicht wechseln... So hat sich auch, schlicht weil's nicht nötig ist, das Problem mit dem OwnerData gelöst ;-). Viele Grüße und ganz herzlichen Dank! Marco |
AW: TListView - viele Daten - viel Zeit ...
Zitat:
|
AW: TListView - viele Daten - viel Zeit ...
Hallo Marco,
ich habe ebenfalls festgestellt, daß OnData sehr oft aufgerufen wird. Hast du herausbekommen, warum? Gruß Manfred |
AW: TListView - viele Daten - viel Zeit ...
Manfred, vernachlässige TListView einfach und sattel über zu VirtualTreeview.
Erspar dir die Kopfschmerzen die dir TListView bringen werden. |
AW: TListView - viele Daten - viel Zeit ...
Der Thread ist zwar schon "etwas" älter, aber: Ein TStringGrid ist deutlich schneller als ein TListView im Report-Modus und ein TCustomGrid mit eigenen Events (z.B.
![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:08 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 by Thomas Breitkreuz