Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Arbeiten mit TTreeView (https://www.delphipraxis.net/187840-arbeiten-mit-ttreeview.html)

Martin W 7. Jan 2016 17:02


Arbeiten mit TTreeView
 
Hi,

frohes neues Jahr :cheer:

Folgende Problemstellung: Ich habe eine Klassse TTestObjects:
Delphi-Quellcode:
type TTestObjects = class(TObject)
public
  // z.B. "Kamel" oder "Auto" oder "Haus"
  ItemCaption: string;
  // Semikolon getrennte Liste, z.B. "Objekt;Mensch;Maschine"
  Tags: string;
end;

var
  FObjects: TList<TTestObjects>;
Diese Klasse ist über
Delphi-Quellcode:
FObjects
ansprechbar.

Ich möchte nun alle Objekte, gruppiert nach den einzelnen Tags, in einem TTreeView anzeigen lassen. Hat also ein Objekt 3 Tags, erscheint es unter 3 Nodes im TTreeView.
Delphi-Quellcode:
procedure UpdateList;
var
  i, k, l: integer;

  varItemAdded: boolean;
  varStringArray: TStringDynArray;
  varTreeNodeItem: TTreeNode;
begin
 
  TreeView.Items.BeginUpdate;
 
  TreeView.Items.Clear;

  for i := 0 to FObjects.Count-1 do
    begin
     
      // Wurde ein Tag gesetzt?
      if (trim(FObjects[i].tags) <> '') then
        begin
          varStringArray := SplitString(FObjects[i].tags, ';');
        end
      else
        begin
          setlength(varStringArray, 1);
          varStringArray[0] := 'Untagged Objects';
        end;

      for k := 0 to length(varStringArray)-1 do
        begin
         
          // Leere Tags ignorieren
          if varStringArray[k] = '' then Continue;
         
          varItemAdded := False;
         
          for l := 0 to TreeView.Items.Count -1 do
            begin
           
              if TreeView.Items[l].Text = varStringArray[k] then
                begin
                  // Objekt zu bereits existierendem Tag hinzufügen
                  TreeView.Items.AddChild(TreeView.Items[l], FObjects[i].ItemCaption);
                  varItemAdded := True;
                  Break;
                end;
               
            end;

          if not varItemAdded then
            begin
              // Neues Tag anlegen und Objekt hinzufügen
              varTreeNodeItem := TreeView.Items.Add(nil, varStringArray[k]);        
              TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
            end;
     
        end;
       
    end;

  TreeView.Items.EndUpdate;

end;
Der Code selbst funktioniert. Sobald die Anzahl der Objekte jedoch größer werden, gibt es Probleme mit der Performance. Wie kann man den Code optimieren?

Weitere Frage: Wie kann man verhindern, dass sich das TTreeView nach jeder Änderung zusammenklappt?

Danke & Viele Grüße

jaenicke 7. Jan 2016 17:57

AW: Arbeiten mit TTreeView
 
Zitat:

Zitat von Martin W (Beitrag 1326319)
Weitere Frage: Wie kann man verhindern, dass sich das TTreeView nach jeder Änderung zusammenklappt?

Indem du nur die Änderungen einträgst und nicht alle Einträge löschst und neu erstellst. Wenn du die Einträge löschst, löschst du auch den Status (eingeklappt, ausgeklappt, ...).
Das geht auch viel schneller.

Für eine bessere Performance würde ich die VirtualStringTree empfehlen:
http://www.soft-gems.net/index.php/c...rtual-treeview

nahpets 7. Jan 2016 18:00

AW: Arbeiten mit TTreeView
 
Baum aufklappen geht mit
Delphi-Quellcode:
treeview.FullExpand;
eventuell auch
Delphi-Quellcode:
TreeView.AutoExpand := True;

Bei jedem neuen Objekt durchläufst Du den ganzen Baum, das erhöht mit wachsendem Baum automatisch die Laufzeit, weil ja die zu durchlaufende Menge ständig wächst und neue Zweige "irgendwo" in den Baum eingefügt werden müssen.

'ne andere Suchmöglichkeit für den TreeView ist mir allerdings nicht bekannt :-(

Kannst Du Dir eventuell hieraus etwas schnelleres bauen? http://delphi.about.com/od/vclusing/l/aa010703a.htm

frankyboy1974 7. Jan 2016 18:05

AW: Arbeiten mit TTreeView
 
hallo,

mit diesem Codeschnipsel

Delphi-Quellcode:
          varItemAdded := False;
         
          for l := 0 to TreeView.Items.Count -1 do
            begin
           
              if TreeView.Items[l].Text = varStringArray[k] then
                begin
                  // Objekt zu bereits existierendem Tag hinzufügen
                  TreeView.Items.AddChild(TreeView.Items[l], FObjects[i].ItemCaption);
                  varItemAdded := True;
                  Break;
                end;
               
            end;

          if not varItemAdded then
            begin
              // Neues Tag anlegen und Objekt hinzufügen
              varTreeNodeItem := TreeView.Items.Add(nil, varStringArray[k]);
              TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
            end;
überprüfst du also, ob ein Tag bereits vorhanden ist. Wenn Ja, wird das Objekt an der Stelle angefügt, wenn nein hinten drangehängt. Dazu muss du aber Worst-Case die gesamte Treeview durchlaufen, bevor du weisst ob ein Tag bereits vorhanden ist oder nicht. Wenn ich aber eine Liste nicht komplett durchlaufen möchte, um zu wissen ob ein Element bereits vorhanden ist, nimmt man für gewöhnlich (zusätzlich) ein Dictonary (Hashmap oder wie auch immer). Ich würde also parallel zur Treeview, ein Dictonary mitpflegen, indem ich jeweils den Tag plus die Position in der Treeview speichere. Wenn ich ein Tag nun prüfe, schaue ich im Dictonary nach, ob das Tag vorhanden ist, wenn ja, erhalte ich die Position an der ich das Objekt einfüge, wenn das Tag noch nicht in meinem Dictonary vorhanden ist, weiss ich, dass ich das neue Tag am Ende in die Treeview einfügen muss.

Zum Codieren hatte ich gerade keine Lust, aber vielleicht hilft es zumindestens als Denkanregung.

mfg

HolgerX 7. Jan 2016 19:20

AW: Arbeiten mit TTreeView
 
Hmm..

oder man nutzt die möglichkeiten des TreeNodes:

Delphi-Quellcode:
      for k := 0 to length(varStringArray)-1 do
      begin
        // Leere Tags ignorieren
        if varStringArray[k] = '' then Continue;

        // erstes Node holen
        varTreeNodeItem := TreeView.TopItem;

        // Vergleichen, ob Tag vorhanden
        while Assigned(varTreeNodeItem) do begin
          if varTreeNodeItem.Text = varStringArray[k] then begin
            // Objekt zu bereits existierendem Tag hinzufügen
            TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
            Break;
          end;
          // Nächstes Node der 'gleich' Ebene
          varTreeNodeItem := TreeView.TopItem.getNextSibling;
        end;

        // Wenn kein existierender Zweig gefunden, dannn anhängen
        if not Assigned(tmpTreeNode) then begin
          varTreeNodeItem := TreeView.Items.Add(nil, varStringArray[k]);
          TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
        end;
      end;
Damit wird nur noch durch die HauptNodes durchgegangen, ohne alle Unternodes zu Prüfen!

Vor allem wird nun wirklich nur nach den Tags gesucht, da beim Durchgehen der ganzen Liste auch TreeNode.Text von den untergeordneten FObjects[i].ItemCaption mit verglichen wird und somit bei einem ItemCaption = Tag dort ein Unter-Node eingefügt wird...

Martin W 8. Jan 2016 22:10

AW: Arbeiten mit TTreeView
 
Hi,

hab folgendes TDictionary ergänzt:

Delphi-Quellcode:
FTagDict: TDictionary<string, TTreeNode>;
:-D

Daraus ergibt sich folgender Code:
Delphi-Quellcode:
procedure UpdateList;
var
  i, k: integer;

  varStringArray: TStringDynArray;
  varTreeNodeItem: TTreeNode;
begin
 
  FTagDict.Clear;

  TreeView.Items.BeginUpdate;
 
  TreeView.Items.Clear;

  for i := 0 to FObjects.Count-1 do
    begin
     
      // Wurde ein Tag gesetzt?
      if (trim(FObjects[i].tags) <> '') then
        begin
          varStringArray := SplitString(FObjects[i].tags, ';');
        end
      else
        begin
          setlength(varStringArray, 1);
          varStringArray[0] := 'Untagged Objects';
        end;

      for k := 0 to length(varStringArray)-1 do
        begin
         
          // Leere Tags ignorieren
          if varStringArray[k] = '' then Continue;
         
          if FTagDict.ContainsKey(varStringArray[k]) then
            varTreeNodeItem := FTagDict.Items[varStringArray[k]]
          else
            varTreeNodeItem := nil;

          if Assigned(varTreeNodeItem) then
            begin
              TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
            end
          else
            begin
              varTreeNodeItem := TreeViewBusinessObjects.Items.Add(nil, varStringArray[k]);
              FTagDict.Add(varStringArray[k], varTreeNodeItem);
              TreeView.Items.AddChild(varTreeNodeItem, FObjects[i].ItemCaption);
            end;
     
        end;
       
    end;

  TreeView.Items.EndUpdate;

end;
Es wird besser :bouncing4:

Danke & Viele Grüße

Martin W 8. Jan 2016 22:27

AW: Arbeiten mit TTreeView
 
Wenn wir schon dabei sind... Habe ich mal eine Funktion geschrieben, um die Duplikate rauszubekommen aus dem TStringDynArray. Funktioniert zwar, wirkt aber doch arg zweckentfremdet :duck: Geht das eleganter bei gleicher Performance?

Delphi-Quellcode:
function TfoPersistentModeller.RemoveDuplicateTags(
  const aTags: TStringDynArray): TStringDynArray;
var
  i,k: integer;
  varTagDict: TDictionary<string, string>;
begin                                  

  k := 0;

  varTagDict := TDictionary<string, string>.Create(Length(aTags));

  for i := 0 to length(aTags)-1 do
    begin
      if varTagDict.ContainsKey(aTags[i]) then Continue;
      varTagDict.Add(aTags[i], '');
      inc(k);
      setlength(Result, k);
      Result[k-1] := aTags[i];
    end;

  varTagDict.Free;
     
end;
Mal abgesehen von den klassischen zwei Schleifen-Lösung des Problems...


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:03 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