Delphi-PRAXiS
Seite 2 von 6     12 34     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   VirtualStringTree - Nodes hinzufügen (https://www.delphipraxis.net/179669-virtualstringtree-nodes-hinzufuegen.html)

d7user1 24. Mär 2014 01:08

AW: VirtualStringTree - Nodes hinzufügen
 
kleiner zwischenbericht:

- checkbox ist nun korrekt positioniert, dank dem property "MainColumn".
- die items werden nun dank dieses threads korrekt angezeigt und verschwinden auch nicht mehr: http://www.delphipraxis.net/152062-v...imagelist.html .

das andere problem mit den images in den anderen columns habe ich auch bereits gelöst.


wenn ich alle dateien und verzeichnisse aus C:\Windows\System32 in eine stringliste lade und die VST dann mit nodes fülle (listview-artig mit 6 spalten) dauert das ganze nun 4 sekunden.
mit einer listview dauert das mehr als 20.
ich füge nicht nur hinzu ich ändere auch schrift- und hintergrundfarben manchmal + es sind checkboxen vorhanden.

bei biden varianten dauert die erstellung der stringliste rund 400ms.

sind 4 sekunden für alles in ordnung oder ist das für 13000 einträge mit schöner darstellung zu lange?

jaenicke 24. Mär 2014 05:20

AW: VirtualStringTree - Nodes hinzufügen
 
Das Problem warum es bei dir so lange dauert ist das Hinzufügen. Eigentlich ist genau das schöne an der VST, dass du die Knoten hinzufügen kannst und diese nicht direkt abgerufen werden, erst wenn du sie anzeigst. Du rufst aber unnötigerweise ValidateNode auf und führst das damit ad absurdum. ;-)

Eine Möglichkeit
Delphi-Quellcode:
type
  TTreeData = class
  private
    FFilename: string;
    FIconIndex: Integer;
  public
    constructor Create(const AFilename: string);
    property IconIndex: Integer read FIconIndex write FIconIndex;
    property Filename: string read FFilename write FFilename;
  end;

  TOnAddFile = procedure(const AFilename: string) of object;

  TFileSearchTree = class(TThread)
  private
    FPath: string;
    FOnAddFile: TOnAddFile;
    FCurrentFile: string;
    procedure DoAddFile;
  protected
    procedure Execute; override;
  public
    constructor Create(const APath: string; const AOnAddFile: TOnAddFile);
    property OnAddFile: TOnAddFile read FOnAddFile write FOnAddFile;
  end;

  TForm67 = class(TForm)
    VST: TVirtualStringTree;
    ImageList1: TImageList;
    procedure FormCreate(Sender: TObject);
    procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: string);
    procedure VSTGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
      var Ghosted: Boolean; var ImageIndex: Integer);
  private
    procedure AddFile(const AFilename: string);
    procedure AddFileEvent(const AFilename: string);
  public
  end;

var
  Form67: TForm67;

implementation

{$R *.dfm}

procedure TForm67.AddFileEvent(const AFilename: string);
begin
  AddFile(AFilename);
end;

procedure TForm67.FormCreate(Sender: TObject);
begin
  VST.NodeDataSize := SizeOf(TTreeData);
  TFileSearchTree.Create('c:\windows', AddFileEvent);
end;

procedure TForm67.VSTGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: Integer);

  function GetIconIndex(const AFilename: string): Integer;
  var
    Icon: TIcon;
    FileIcon: TSHFileInfo;
  begin
    Icon := TIcon.Create;
    try
      ZeroMemory(@FileIcon, SizeOf(FileIcon));

      SHGetFileInfo(PChar(AFilename), 0, FileIcon, SizeOf(FileIcon), SHGFI_SYSICONINDEX or SHGFI_ICON);
      Icon.Handle := FileIcon.hIcon;

      Result := ImageList1.AddIcon(Icon);
    finally
      Icon.Free;
    end;
  end;

var
  TreeData: TTreeData;
begin
  if (Kind <> ikOverlay) and (Column = 0) then
  begin
    TreeData := TTreeData(Sender.GetNodeData(Node)^);
    if TreeData.IconIndex < 0 then
      TreeData.IconIndex := GetIconIndex(TreeData.Filename);
    ImageIndex := TreeData.IconIndex;
  end;
end;

procedure TForm67.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
var
  TreeData: TTreeData;
begin
  TreeData := TTreeData(Sender.GetNodeData(Node)^);
  case Column of
    0:
      CellText := TreeData.Filename;
  end;
end;

procedure TForm67.AddFile(const AFilename: string);
var
  NewNode: PVirtualNode;
begin
  NewNode := VST.AddChild(nil, TTreeData.Create(AFilename));
  NewNode.CheckType := ctCheckBox;
  NewNode.CheckState := csCheckedNormal;
end;

{ TTreeData }

constructor TTreeData.Create(const AFilename: string);
begin
  FFilename := AFilename;
  FIconIndex := -1;
end;

{ TFileSearchTree }

constructor TFileSearchTree.Create(const APath: string; const AOnAddFile: TOnAddFile);
begin
  inherited Create(False);
  FPath := APath;
  FOnAddFile := AOnAddFile;
end;

procedure TFileSearchTree.DoAddFile;
begin
  FOnAddFile(FCurrentFile);
end;

procedure TFileSearchTree.Execute;

  procedure FindAllFiles(ARootFolder: string; AMask: string = '*.*'; ARecurse: Boolean = True);
  // von: http://www.delphipraxis.net/2235-verzeichnisse-nach-dateien-durchsuchen.html
  var
    SR: TSearchRec;
  begin
    // Implementation bis einschließlich Delphi 4
    if ARootFolder = '' then
      Exit;
    if AnsiLastChar(ARootFolder)^ <> '\' then
      ARootFolder := ARootFolder + '\';

    // Implementation ab Delphi 5
    ARootFolder := IncludeTrailingPathDelimiter(ARootFolder);

    if FindFirst(ARootFolder + AMask, faAnyFile, SR) = 0 then
      try
        repeat
          if SR.Attr and faDirectory <> faDirectory then
          begin
            FCurrentFile := ARootFolder + SR.Name;
            Synchronize(DoAddFile);
          end;
        until FindNext(SR) <> 0;
      finally
        FindClose(SR);
      end;
    if ARecurse then
      if FindFirst(ARootFolder + '*.*', faAnyFile, SR) = 0 then
        try
          repeat
            if SR.Attr and faDirectory = faDirectory then
              // --> ein Verzeichnis wurde gefunden
              // der Verzeichnisname steht in SR.Name
              // der vollständige Verzeichnisname (inkl. darüberliegender Pfade) ist
              // ARootFolder + SR.Name
              if (SR.Name <> '.') and (SR.Name <> '..') then
                FindAllFiles(ARootFolder + SR.Name, AMask, ARecurse);
          until FindNext(SR) <> 0;
        finally
          FindClose(SR);
        end;
  end;

begin
  if Assigned(FOnAddFile) then
    FindAllFiles(FPath, '*.*', True);
end;
Das ist immer noch nicht optimal, da jede Datei aus dem Thread einzeln synchronisiert und angezeigt wird. Da könnte man auch z.B. alle Dateien in einem Verzeichnis in eine Liste packen und die ganze Liste synchronisiert hinzufügen oder ähnliches. Aber das sollte als beispiel erst einmal reichen. ;-)

Popov 24. Mär 2014 05:56

AW: VirtualStringTree - Nodes hinzufügen
 
@ d7user1

Alles dauert seine Zeit. Das Lesen der 13000 Dateien dauert nur ein Augenblick, das Erstellen der passenden Objekte und sogar sortieren geht auch ganz schnell, selbst das Erstellen der Items. Aber kaum packst du die ListView an (vermutlich auch Virtual TreeView) um Daten anzulegen, dauert es Zeit. Das zuweisen aller Caption's ungleich "" dauert fast 1,5 Sekunden. Das Zuweisen der ImageIndex's ungleich 0 dauert etwa 2,5 Sekunden. Womit wir bei 4 Sekunden wären. Und die SubItems, selbst wenn leer, dauert auch seine Sekunden.

Das bedeutet, die 13000 Dateien lesen und die gleiche Anzahl Objekte und Items anzulegen (plus sortieren) dauert nur paar Millisekunden, der Rest kostet Zeit.

Ich hab gerade mein Beispiel insoweit geändert, dass zuerst alle Items angelegt werden, ohne eine Zuweisung. Der Rest wird beim Anzeigen der Items gefüllt. Also nicht wie im ersten Beispiel nur die Subitems, sondern alles. Das Ergebnis bei 13000 Dateien sind 265 Millisekunden.

Ob das alles noch so seinen Sinn macht sein dahingestellt, ist einfach nur ein Test.

nuclearping 24. Mär 2014 07:14

AW: VirtualStringTree - Nodes hinzufügen
 
Zitat:

Zitat von d7user1 (Beitrag 1253186)
aber es gibt viele dinge die ich vermisse und ich habe keine ahnung wie man das macht. z.b. vermisse ich MeinItem.SubItemImages[X].

[...]

leider weiß ich nicht wie man eine checkbox bei virtuellem modus in eine listiew zeichnet. dann würde ich VST nicht nutzen müssen.

Unterschätze den VST nicht. Wie in dem anderen Thread schon gesagt hat er eine gewisse steile Lernkurve, aber wenn's einmal richtig Klick gemacht hat, wie mächtig der VST und was du alles damit machen kannst, wirst du feststellen, dass du dich mit Komponenten wie TListView und / oder TTreeView in der Steinzeit bewegt hast. Also gib dir Zeit, den Umgang zu lernen und zu verstehen und setze dich nicht unter Druck. :mrgreen:

Wie der Name schon sagt ist der "Virtual Tree View" virtuell. Also es gibt da keinen direkten Zugriff auf Knoten oder Unterknoten.
ItemIndex-Images werden auch nicht direkt zum Baum gespeichert, sondern "On Demand" mittels GetNodexIndex geholt. Das hat den Vorteil, dass dadurch der VST nicht an eine Struktur gebunden ist, sondern du alle möglichen Daten mit dem Baum verknüpfen und je nach Bedarf nach deinen eigenen Vorstellungen darstellen kannst.

Auf einzelne Knoten (und deren Daten) kannst du somit auch nicht direkt über VST.Node[X] zugreifen, sondern müsstest erst durch den Baum iterieren. Entweder per Hand mit
Delphi-Quellcode:
VST.GetFirst ... while ... VST.GetNodeData ... Daten mit Suchparametern vergleichen ... end VST.GetNext
, bzw.
Delphi-Quellcode:
GetFirstSibling
,
Delphi-Quellcode:
GetNextSibling
, usw. Oder per
Delphi-Quellcode:
OnIncrementalSearch
-Event und dann mit VST.IterateSubTree.

d7user1 24. Mär 2014 12:21

AW: VirtualStringTree - Nodes hinzufügen
 
hallo, popov, fügst du die daten aus C:\Windows\System32 in deine liste hinzu? und wenn ja, ist es noch immer das beispiel aus deinem anderen thread? denn das war nicht rekursiv meine ich und es waren glaube ich etwas mehr als 2000 daten.

ich habe gerade mal geguckt. daten sammeln (C:\Windows\System32 rekursiv) und diese in records zu speichern ohne daten in der VST anzuzeigen dauert bei mir etwas mehr als 700ms.
man darf nicht vergessen dass ich noch OnPaintText und OnAfterItemErase verwende und dort mit Font.Color und Brush rumgespielt wird.


zu deinem beispiel jaenike:
ich mache alles genau so wie du außer dass ich das nicht auf einen thread ausgelagert habe und dass ich die nodes so erzeuge:
Delphi-Quellcode:
 Node := aVST.AddChild(nil);
 Node.CheckType := ctCheckBox;
 Node.CheckState := csCheckedNormal;

 Data := aVST.GetNodeData(Node);
 Data^ := aRecord;
laut dem post von Popov sind 4 sekunden also normal für das alles was ich mache?

p.s.: selbst wenn ich die Icon in der VST weglasse und die Zellentexte nicht zuweise duaert es bei mir 700ms.
ich glaube der lädt die Icons und die Zellentexte von haus aus erst wenn sie gebracht werden.


hier eine kleine zeitmessung für C:\Windows\System32 NICHT rekursiv:
- das lesen der daten dauert bei mir ungefähr 60ms.
- das erstellen der records ohne nodes zu erzeugen etwa 600ms
- das erstellen der records mit nodes zu erzeugen etwa 650ms
- und alles zusammen mit erstellen und freigeben der dateiliste, BeginUpdate, EndUpdate etwa 700ms.

ist das so in ordnung?


großes edit:
ich weiß jetzt warum das 4 sekunden dauert. beim erstellen meiner records nutze ich einmal IntToStr(getFileSizeA()) und einmal DateTimeToStr(getFileLastModified()).
ohne IntToStr(getFileSizeA()) dauert es nur 1.5 sekunden und ohne DateTimeToStr(getFileLastModified()) dauert es nur 3.3 sekunden.
ohne beides dauert alles zusammen plus anzeigen der 13.000 dateien 400ms.

meine zwei funktionen sehen so aus:
Delphi-Quellcode:
function getFileSizeA(const FileName: string): Int64;
var
 SR: TSearchRec;
begin
 Result := 0;

 if not FileExists(FileName) then
  Exit;

 if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then
  try
   Result := SR.Size;
  finally
   SysUtils.FindClose(SR)
  end;
end;

function getFileLastModified(const FileName: string): TDateTime;
var
 SR: TSearchRec;
 SystemTime: TSystemTime;
 NewWriteTime: TFileTime;
begin
 Result := 0;

 if FindFirst(FileName, faAnyFile, SR) = 0 then
  try
   if (Windows.FileTimeToLocalFiletime(SR.FindData.ftLastWriteTime, NewWriteTime) and
     Windows.FileTimeToSystemTime(NewWriteTime, SystemTime)) then
    Result := Encodedate(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay) +
     Encodetime(SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds);
  finally
   SysUtils.FindClose(SR)
  end;
end;

jaenicke 24. Mär 2014 12:48

AW: VirtualStringTree - Nodes hinzufügen
 
Wobei das Zeichnen usw. egal ist, da das ja nur die tatsächlich angezeigten Knoten betrifft.

Was bei dir aber noch fehlt ist ein BeginUpdate...EndUpdate, denn dadurch dauert das Hinzufügen der Knoten sehr viel länger.

Wenn man den Thread noch ändert und die Dateien häppchenweise statt einzeln übergibt plus BeginUpdate...EndUpdate sollte das ganze quasi sofort initial angezeigt werden (und dann natürlich noch kurz nachladen).

Der übliche Weg sieht übrigens so aus, dass man immer nur die Dateien der aktuellen Ebene anzeigt und beim Aufklappen die Ebene darunter einliest. So sollte es keinerlei Verzögerungen geben, es sei denn du hast Verzeichnisse mit enorm vielen Dateien.

Popov 24. Mär 2014 13:14

AW: VirtualStringTree - Nodes hinzufügen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von d7user1 (Beitrag 1253229)
hallo, popov, fügst du die daten aus C:\Windows\System32 in deine liste hinzu? und wenn ja, ist es noch immer das beispiel aus deinem anderen thread? denn das war nicht rekursiv meine ich und es waren glaube ich etwas mehr als 2000 daten.

Jajn, es ist das Beispiel, aber ich hab es zwei mal angepasst. Auch habe ich mir zum Testen ein Ordner mit 13000 Dateien erstellt. Also die 260 ms beziehen sich auf die 13000 Dateien. Ich hab alles nur verlagert (ich hänge mal das Beispiel dran).

Und ja, es ist nicht rekursiv, denn es sollte nur ein Ordner gelesen werden. Ich denke mir aber, dass es keinen besonderen Unterschied macht wenn man den gesamten Inhalt liest, inkl. Unterordner. Trotzdem, auch wenn ich das so hin gekriegt habe, es ist eher nur just4fun. Ich hab noch nie mit virtuellem ListView gearbeitet, evtl. ist es für die Aufgabe besser geeignet.

Was das Andere angeht, Total Commander hatte bis (mindestens) Version 5.5 die Möglichkeit einen Verzeichnis-Baum auszugeben. Um das zu laden hat er zum Schluss fast immer eine Minute gebraucht. Aber man läd nun mal nicht alles auf einmal, vor allem bei TreeView. Das dauert zu lange.

Popov 24. Mär 2014 13:33

AW: VirtualStringTree - Nodes hinzufügen
 
Zitat:

Zitat von d7user1 (Beitrag 1253229)
Delphi-Quellcode:
function getFileSizeA(const FileName: string): Int64;
var
 SR: TSearchRec;
begin
 Result := 0;

 if not FileExists(FileName) then
  Exit;

 if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then
  try
   Result := SR.Size;
  finally
   SysUtils.FindClose(SR)
  end;
end;

Das kannst du anders machen. Mal unabhängig davon ob es Sinn macht FindFirst für jede einzelne Datei FindFirst neu zu bemühen, sowie SR.Size maximal eine 32 Bit Größe ausgibt (also alles über 2 GByte falsch wiedergibt), womit Int64 unnötig ist, ist in Code FileExists nicht nötig.

FindFirst sucht ja bereits nach dem Namen der Datei, findet es nicht, geht sie nicht in der Block rein (hier try finally), sondern übergeht ihn. Somit kann man sich die FileExists Abfrage sparen, sie kostet nur Zeit.

SR.Size gibt u. U. die falsche Dateigröße zurück, denn es ist Integer. Solange du unter 2 GByte bleibst, merkst du nichts. Die richtige Größe steckt in FindData. Hier die Formel:

Delphi-Quellcode:
x := Int64(SR.FindData.nFileSizeLow) or (Int64(SR.FindData.nFileSizeHigh) shl 32);
//
Ansonsten wird hier jede Datei anscheinend zwei mal angepackt - zuerst um den Namen zu lesen, später noch mal um die Größe zu erfahren. Dabei enthält TSearchRec schon beim ersten Zugriff alle Infos.

jaenicke 24. Mär 2014 14:12

AW: VirtualStringTree - Nodes hinzufügen
 
Zitat:

Zitat von Popov (Beitrag 1253239)
SR.Size gibt u. U. die falsche Dateigröße zurück, denn es ist Integer.

Bei Delphi 7 war das damals so.
Heute ist es ein Int64 Wert.

Popov 24. Mär 2014 14:20

AW: VirtualStringTree - Nodes hinzufügen
 
Gibt SR.Size (bei aktuellen Delphis) auch den korrekten Wert an (> 2GB)?


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:07 Uhr.
Seite 2 von 6     12 34     Letzte »    

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