Den Tree-View füllen


Nach dem Erzeugen des Tree-View wollen wir diesen nun mit Inhalt füllen. Das Beispielprogramm lädt zu dem Zweck zuerst alle vorhandenen Festplatten bzw. Partitionen, begonnen bei C, und stellt diese im Hauptmenü dar. Das Laufwerk, in dem sich das Beispielprogramm befindet, wird dann auch gleich nach Ordnern durchsucht; diese Ordner sollen im Tree-View dargestellt werden. Eine Art Mini-Explorer also ...

Das Scannen der Partition soll nur am Rande besprochen werden. Erwähnenswert ist aber die Tatsache, dass der Weg über die Shell benutzt wird. Das ist im Prinzip der gleiche Weg, den auch Microsofts Explorer verwendet. Außerdem wird ein bisschen getrickst: Damit das Scannen nicht so lange dauert, wird immer nur die jeweils oberste Ebene geladen. Erst beim Öffnen eines Knotens werden die darunter liegenden Ordner gesucht und ergänzt.

Zum Thema: Um einen Eintrag zu erstellen, benötigen wir die Nachricht "TVM_INSERTITEM", die einen Zeiger auf das TVInsertStruct-Record erwartet. Uns interessiert von diesem Record fürs Erste der Parent, der immer auf den jeweils zuvor erzeugten Eintrag zeigen sollte. Beim ersten Aufruf der Prozedur "Scan", die für das Scannen der Partition zuständig ist, wird hier der Wert nil als "hRoot" übergeben:

tvi.hParent      := hParent;

Um eine Explorer-ähnliche Sortierung zu erhalten, verwenden wir TVI_SORT als Wert für die Einfügeoperation:

tvi.hInsertAfter := TVI_SORT;

Weil wir im Augenblick nur blanke Texteinträge erstellen, reicht folgendes Flag:

tvi.item.mask    := TVIF_TEXT;

Und natürlich müssen wir dann auch den jeweils aktuellen Ordnernamen angeben. Da es beim Shell-Prinzip nicht ganz so einfach ist, verweise ich Sie an das Beispielprogramm, in dem sich der komplette Code befindet. Hier an dieser Stelle sei gesagt, dass sich der Name des Ordners in der Variablen "szCaption" befindet und übrigens auch genau dem Namen entspricht, den Sie aus dem Explorer von Windows kennen:

tvi.item.pszText := pchar(szCaption);

Jetzt muss ich allerdings doch ein wenig auf den Code eingehen, weil es darum geht, evtl. vorhandene Unterordner zu berücksichtigen. Dabei ist nur wichtig, dass der jeweilige Knoten das Plus-Symbol zum Aufklappen besitzt.
Das Shell-Prinzip funktioniert grob gesagt so: Zu einem Ordner wird eine interne ID ermittelt, die in jedem Fall eindeutig ist. Mit Hilfe des IShellFolder-Interfaces kann man nun die Attribute dieses Ordners und damit evtl. vorhandene Unterordner ermitteln, was im Beispielprogramm so aussieht:

uAttr                := SFGAO_CONTENTSMASK;
iDesktopFolder.GetAttributesOf(1,pidlNode,uAttr);
if(SFGAO_HASSUBFOLDER and uAttr <> 0) then
begin

In diesem Fall muss nun die Maske um das Flag TVIF_CHILDREN erweitert werden. Dadurch ist die cChildren-Membervariable gültig. Wenn Sie diese dann auf Eins setzen, dann zeigt der Knoten das Plus-Symbol zum Aufklappen an. Und dabei spielt es keine Rolle, dass die Unterordner zu diesem Zeitpunkt noch gar nicht bekannt sind

  tvi.item.mask      := tvi.item.mask or TVIF_CHILDREN;
  tvi.item.cChildren := 1;
end;

Das war´s. Den so erstellten Eintrag fügen wir nun unserem Tree-View hinzu, indem wir "SendMessage" aufrufen und das Record als lParam übergeben. Das Ergebnis des Aufrufs ist dann das Handle des Eintrags:

hr := HTREEITEM(SendMessage(hTV,TVM_INSERTITEM,0,LPARAM(@tvi)));


Jetzt wäre vielleicht noch die Frage, wie man die Unterordner ergänzen kann. Dazu reagiert man lediglich auf die Benachrichtigung "TVN_ITEMEXPANDING", die ausgelöst wird, wenn der Anwender auf das Plus vor dem Knoten klickt. Das Beispielprogramm ruft in diesem Fall die Prozedur "ScanTreeViewAgain" auf und übergibt dabei die notwendigen Parameter:

WM_NOTIFY:
  with PNMTreeView(lp)^ do
    case hdr.code of
      TVN_ITEMEXPANDING:
        ScanTreeViewAgain(hTreeView,TFolderId(itemNew.lParam),
          itemNew.hItem);

Speziell der TFolderId-Parameter ist wichtig. Dabei handelt es sich um eine Klasse, die zu jedem Knoten angelegt wird. Diese Klasse enthält die oben erwähnte ID des Ordners (PItemIdList) und das dazu gehörende IShellFolder-Interface.
Um ein mehrfaches Scannen zu vermeiden, prüft die Prozedur ob bereits untergeordnete Knoten vorhanden sind. Ist das nicht der Fall, wird die eigentliche Scan-Routine mit den neuen Parametern aufgerufen.

Hinweis

Neben der Nachricht gibt es auch noch ein Makro, "TreeView_InsertItem", das wir stattdessen benutzen können. Es kapselt die o.g. Nachricht einfach nur, liefert aber auch gleich den Typ HTREEITEM zurück, so dass das o.g. Typecasting entfallen kann:

hr := TreeView_InsertItem(hTV,tvi);

Ich werde noch häufiger auf solche Funktionen eingehen, da ich sie in den meisten Fällen für den eleganteren und einfacheren Weg halte.


Vordefinierte Werte zum Einfügen von Items

Wert Bedeutung
TVI_FIRST Das Item wird am Anfang der aktuellen Ebene eingefügt.
TVI_LAST Das Item wird am Ende der aktuellen Ebene angehangen.
TVI_ROOT Das Item wird als Root der aktuellen Ebene eingefügt.
TVI_SORT Das Item wird in alphabetischer Reihenfolge in die aktuelle Ebene eingefügt.