|
Ein für den Anwender komfortables Feature ist es sicherlich, wenn er die Knotenzweige in einem TreeView-Steuerelement beim nächsten Start Ihrer Anwendung im gleichen Öffnungszustand vorfindet wie beim letztmaligen Beenden der Anwendung. Auf den ersten Blick sieht das nach einer eher trivialen Aufgabe aus. Sie bräuchten beim Beenden der Anwendung nur die Schlüssel sämtlicher geöffneter ("expandierter") Knoten zu speichern und beim nächsten Start alles diese Knoten wieder zu expandieren.
Doch zum einen wäre das ein sehr ineffizientes Vorgehen - vor allem dann, wenn das TreeView-Steuerelement sehr viele Knoten enthalten sollte, und zudem noch sehr viele dieser Knoten in einer tief verzweigten Struktur geöffnet sein sollten.
Zum anderen wird dieses Verfahren fehl schlagen, wenn die Knotenstruktur noch nicht oder noch unvollständig gefüllt ist. Denn gerade bei umfangreichen und tiefen Strukturen empfiehlt es sich, einen Zweig erst beim erstmaligen Öffnen aktuell zu füllen (siehe auch "Baum mit hohlen Früchten"). Einen tiefer in der Struktur liegenden Knoten anhand seines Schlüssels zu identifizieren und zu öffnen, obwohl er beim anfangs noch vollständig geschlossenen Baum noch nicht vorhandenen ist, wird nicht funktionieren. Sie müssten für diesen Knoten erst den gesamten Zweig bis hin zur Wurzel nachladen - und dazu natürlich auch alle seine "Geschwister" auf der gleichen Ebene. Sicher werden Sie innerhalb der Datenbasis, auf der der Inhalt des TreeViews beruht, auch die dazu benötigten Eltern-Kind-Verhältnisse in irgend einer Form abgelegt haben und so einen Zweig rekonstruieren können. Doch wahrscheinlich wird auch das relativ aufwändig sein.
Erheblich effizienter bezüglich des ersten Punktes wäre, nur die Schlüssel der jeweils letzten Knoten eines Zweiges zu speichern, die noch Kind-Knoten haben und expandiert sind (die Expanded-Eigenschaft alleine ist nicht genügend aussagekräftig, da sie auch auf True gesetzt sein kann, ohne dass der betreffende Knoten Kind-Knoten hätte) und dann die Zweige zu diesen Knoten zu öffnen. Dieser Ansatz ist brauchbar, es ist aber immer noch das angeführten zweite Problem zu lösen, falls Sie die empfehlenswerte Technik der "hohlen" Knoten einsetzen.
Beschränken wir uns nun zunächst einmal darauf, den Schlüssel eines einzelnen Knotens zu speichern und anschließend seinen Zweig möglichst effizient wieder zu öffnen. Die Knoten zu speichern und die Zweige zu ihnen wieder zu öffnen, ist kein Problem, wenn die Zweigstruktur schon komplett geladen ist. Sie brauchen nur die EnsureVisible-Methode der anhand der Schlüssel identifizierten Knoten aufzurufen. Doch wenn die Struktur noch nicht geladen ist, haben Sie das bereits beschriebene Problem.
Der Gedanke ist daher nahe liegend, gleich die Schlüssel des gesamten oberhalb des Knotens liegenden Zweigpfades mit zu speichern und so beim Rekonstruieren gleich bequem zur Hand zu haben. Doch der Schlüsselpfad wird Ihnen leider nicht frei Haus geliefert, im Gegensatz zum Pfad der Knotentexte, die Ihnen von der Eigenschaft FullPath eines Knotens zur Verfügung gestellt wird.
Die folgende Funktion FullKeyPath liefert Ihnen den gewünschten Pfad der Schlüssel (Keys). Es empfiehlt sich, ein anderes Trennzeichen als beim FullPath zu verwenden. Denn falls Sie diesen in den Schlüsseln ablegen, wäre das Chaos perfekt (siehe "Knotenpfade knüpfen").
Public Function FullKeyPath(Node As Node, _
Optional Separator As String = "^") As String
Dim nPath As String
Dim nNodeParent As Node
nPath = Node.Key
Set nNodeParent = Node.Parent
Do While Not (nNodeParent Is Nothing)
With nNodeParent
nPath = .Key & Separator & nPath
Set nNodeParent = .Parent
End With
Loop
FullKeyPath = nPath
End Function
Das Expandieren eines Knoten anhand eines solchen Schlüsselpfades erledigt die folgende Funktion ExpandFullKeyPath.
Public Function ExpandFullKeyPath(TreeView As TreeView, _
KeyPath As String, _
Optional ByVal EnsureVisible As Boolean = True, _
Optional ByVal SelectNode As Boolean, _
Optional Separator As String = "^") As Node
Dim nNode As Node
Dim nKeys() As String
Dim l As Long
With TreeView
With .Nodes
Zunächst wird geprüft, ob es sich die Mühe überhaupt "lohnt" - wenn der Pfad leer ist, oder überhaupt keine Knoten (also auch keine Wurzelknoten) vorhanden sind, wäre die weitere Ausführung witzlos.
Select Case True
Case Len(KeyPath) = 0, .Count = 0
Exit Function
End Select
Nun zerlegen wir mit der Split-Funktion den Pfad in seine Bestandteile (da es in Visual Basic 5 diese Funktion noch nicht gibt, können sie einen Ersatz dafür verwenden - siehe "Teile-Haberschaft").
nKeys = Split(KeyPath, Separator)
Jetzt expandieren wir den Pfad von der Wurzel aufwärts - und hierbei kommen eben die Bestandteile des Schlüsselpfades ins Spiel. "Hohle" Knoten werden dabei der Reihe nach gefüllt - und damit sollte auch der jeweils nächste Knoten im Pfad vorhanden sein. Sollte dieser jedoch aus irgendeinem Grunde jedoch nicht mit geladen worden sein, können wir den Rest vergessen und verlassen die Funktion.
On Error Resume Next
For l = UBound(nKeys) To 0 Step -1
Set nNode = .Item(nKeys(l))
If Err.Number Then
Exit Function
Else
nNode.Expanded = True
End If
Next 'l
End With
Falls der letzte Knoten auch noch gleich selektiert werden soll (Übergabe von True im Optionalen Parameter SelectNode), setzen wir seine Selected-Eigenschaft auf True.
If SelectNode Then
nNode.Selected = True
Anderenfalls könnte er immerhin noch ins Bild gerollt werden (Übergabe von True im Optionalen Parameter EnsureVisible).
ElseIf EnsureVisible Then
nNode.EnsureVisible
End If
Schließlich geben wir den Knoten zur weiteren Verwendung als Rückgabewert der Funktion zurück.
Set ExpandFullKeyPath = nNode
End Function
Die oben angeführte Aufgabe, nur die Schlüssel(-pfade) derjenigen Knoten zu speichern, die noch Kind-Knoten haben und expandiert sind, erledigt die Funktion AllExpandedFullKeyPaths.
Public Function AllExpandedFullKeyPaths(TreeView As TreeView, _
Optional KeySeparator As String = "^", _
Optional NodeSeparator As String = "´") As String
Dim nNode As Node
Dim nNodeParent As Node
Dim nNodeNext As Node
Dim nNodes As Collection
Dim nPaths As String
Zunächst ermitteln wir den Wurzelknoten des ersten in der Nodes-Collection enthaltenen Knotens. Dieser erste Knoten ist nicht unbedingt ein Wurzelknoten, und auch nicht oberste Wurzelknoten, da die Reihenfolge in der Nodes-Collection relativ willkürlich ist und von der Reihenfolge des Einfügens der Knoten in die Collection abhängt.
Set nNode = TreeView.Nodes(1)
Set nNodeParent = nNode.Parent
Do While Not (nNodeParent Is Nothing)
Set nNode = nNodeParent
Set nNodeParent = nNode.Parent
Loop
Von diesem Knoten brauchen wir schließlich den obersten Knoten.
Set nNodeNext = nNode.FirstSibling
Mittels der im weiteren sich selbst rekursiv aufrufenden privaten Hilfsprozedur zFollowNode sammeln wir in einer Collection alle Knoten, deren Schlüsselpfade gespeichert werden sollen. Wir übergeben ihr der Reihe nach alle Wurzelknoten.
Set nNodes = New Collection
Do
zFollowNode nNodeNext, nNodes
Set nNodeNext = nNodeNext.Next
Loop While Not (nNodeNext Is Nothing)
Schließlich ermitteln wir mit der bereits bekannten Funktion FullKeyPath die Schlüsselpfade der gesammelten Knoten und fügen sie zu einem einzigen, bequem speicherbaren String zusammen.
For Each nNode In nNodes
With nNode
nPaths = nPaths & NodeSeparator & _
FullKeyPath(nNode, KeySeparator)
End With
Next
If Len(nPaths) Then
AllExpandedFullKeyPaths = Mid$(nPaths, 2)
End If
End Function
Private Sub zFollowNode(Node As Node, Nodes As Collection)
Dim nNodeChild As Node
Dim nNodeParent As Node
On Error Resume Next
With Node
If .Expanded Then
If .Children Then
Nodes.Add Node, .Key
Set nNodeParent = .Parent
If Not (nNodeParent Is Nothing) Then
Nodes.Remove nNodeParent.Key
End If
Set nNodeChild = .Child
Do
zFollowNode nNodeChild, Nodes
Set nNodeChild = nNodeChild.Next
Loop While Not (nNodeChild Is Nothing)
End If
End If
End With
End Sub
Der Trick in der Hilfsprozedur zFollowNode besteht darin, zunächst jeden Knoten in die Collection einzufügen, wenn er expandiert ist und Kind-Knoten hat. Nur solche Knoten wollen wir ja überhaupt speichern und somit einsammeln. Zugleich kann der gegebenenfalls auf höherer Ebene der rekursiven Aufrufe bereits eingefügter Elternknoten des Knotens aus der Sammlung wieder herausfallen. Die Kind-Knoten des Knotens werden auch nur dann untersucht, wenn er selbst expandiert ist.
Der letzte Schritt zur Wiederherstellung des Expansionszustandes der Zweigstruktur ist nun einfach. In der Prozedur ExpandAllKeyPaths wird der Sammelstring der zu expandierenden Knoten in einzelne Schlüsselpfade zerlegt und der Knoten zu jedem Schlüsselpfad wird expandiert.
Public Sub ExpandAllKeyPaths(TreeView As TreeView, _
KeyPaths As String, Optional KeySeparator As String = "^", _
Optional NodeSeparator As String = "´")
Dim nPaths() As String
Dim l As Long
Select Case True
Case Len(KeyPaths) = 0, TreeView.Nodes.Count = 0
Exit Sub
End Select
nPaths = Split(KeyPaths, NodeSeparator)
For l = 0 To UBound(nPaths)
ExpandFullKeyPath TreeView, nPaths(l), , , , KeySeparator
Next 'l
End Sub
|