AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Arbeiten mit TTreeView

Ein Thema von Martin W · begonnen am 7. Jan 2016 · letzter Beitrag vom 8. Jan 2016
Antwort Antwort
Martin W

Registriert seit: 29. Mai 2004
Ort: Augsburg
220 Beiträge
 
Delphi XE3 Enterprise
 
#1

Arbeiten mit TTreeView

  Alt 7. Jan 2016, 18:02
Hi,

frohes neues Jahr

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 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

Geändert von Martin W ( 7. Jan 2016 um 18:06 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.582 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Arbeiten mit TTreeView

  Alt 7. Jan 2016, 18:57
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
Sebastian Jänicke
Alle eigenen Projekte sind eingestellt, ebenso meine Homepage, Downloadlinks usw. im Forum bleiben aktiv!
  Mit Zitat antworten Zitat
nahpets
(Gast)

n/a Beiträge
 
#3

AW: Arbeiten mit TTreeView

  Alt 7. Jan 2016, 19:00
Baum aufklappen geht mit treeview.FullExpand; eventuell auch 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
  Mit Zitat antworten Zitat
Benutzerbild von frankyboy1974
frankyboy1974

Registriert seit: 7. Apr 2015
Ort: SH
169 Beiträge
 
Delphi XE7 Professional
 
#4

AW: Arbeiten mit TTreeView

  Alt 7. Jan 2016, 19:05
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
Java ist auch eine Insel.
Ist Delphi von Oracle?
In meiner Buchstabensuppen fehlt das C++!
  Mit Zitat antworten Zitat
HolgerX

Registriert seit: 10. Apr 2006
Ort: Leverkusen
969 Beiträge
 
Delphi 6 Professional
 
#5

AW: Arbeiten mit TTreeView

  Alt 7. Jan 2016, 20:20
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...
  Mit Zitat antworten Zitat
Martin W

Registriert seit: 29. Mai 2004
Ort: Augsburg
220 Beiträge
 
Delphi XE3 Enterprise
 
#6

AW: Arbeiten mit TTreeView

  Alt 8. Jan 2016, 23:10
Hi,

hab folgendes TDictionary ergänzt:

FTagDict: TDictionary<string, TTreeNode>;

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

Danke & Viele Grüße
  Mit Zitat antworten Zitat
Martin W

Registriert seit: 29. Mai 2004
Ort: Augsburg
220 Beiträge
 
Delphi XE3 Enterprise
 
#7

AW: Arbeiten mit TTreeView

  Alt 8. Jan 2016, 23:27
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 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...

Geändert von Martin W ( 8. Jan 2016 um 23:30 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:45 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