Das TreeView-Steuerelement aus den Microsoft Common Controls bietet von Hause aus keine Möglichkeit der Mehrfachauswahl von Knoten an - wenn Sie von den ab Version 6 möglichen Anzeige von CheckBoxen zu jedem Knoten absehen. Eine Mehrfachauswahl ist eher in dem Sinne gemeint, dass der Anwender vorübergehend mehrere Knoten nacheinander markieren kann, etwa für Drag&Drop-Operationen oder Mehrfachbearbeitungen und ähnlichem. Die CheckBoxen hingegen repräsentieren eher eine dauerhafte Auswahl von Optionen.
Wie üblich sollten die Markierungen in der Art und Weise dargestellt werden, wie sie es auch im ListView-Steuerelement oder in einer einfachen ListBox werde - nämlich durch Darstellung eines markierten Elements in den im System eingestellten Standard-Hintergrund- und -Vordergrundfarben für Markierungen. Allerdings steht die Möglichkeit der Farbänderungen eines Knotens erst ab Version 6 der Microsoft Common Controls zur Verfügung.
Das Markieren selbst sollte dabei ähnlich dem Markieren bzw. Aufheben einer Markierung im ListView oder in einer ListBox (erweiterte Mehrfachauswahl) erfolgen: durch Anklicken bei gleichzeitig gedrückter Strg-Taste. Eine Mehrfachauswahl über einen Bereich von einem Startelement bis zum aktuell angeklickten Element, wie sie ansonsten bei gleichzeitig gedrückter Umschalt-Taste erfolgen würde, macht bei einem TreeView-Steuerelement wegen der Verzweigungen der Knoten nur wenig Sinn.
Dagegen ist vielleicht das Markieren bzw. Freigeben eines Knoten samt des ganzen Astes unterhalb dieses Knotens interessant (Umschalt- und Strg-Taste gleichzeitig beim Anklicken gedrückt). Die Markierung per Tastatur bzw. die Aufhebung einer Markierung erfolgt über die Leertaste (wie bei einer ListBox mit einfacher Mehrfachauswahl), die Markierung bzw. Freigabe eines Knotens samt des ganzen Astes darunter mit Umschalt-Leertaste. Die komplette Aufhebung einer Mehrfachauswahl erfolgt durch einen Klick in den freien Raum außerhalb einer Knoten-Beschriftung oder per Tastatur über die Kombination Umschalt-Escape-Taste. An weiteren Tastenkombinationen stehen zur Verfügung: Strg-A markiert den gesamten Inhalt, während Strg-T alle Markierungen umkehrt.
Das alles haben wir in die Klasse TreeViewMultiSelect verpackt. Sie brauchen sie nur für jeweils jedes TreeView-Steuerelement, das Sie mit der Fähigkeit der Mehrfachauswahl versehen wollen, einmal zu instanzieren. Das betreffende TreeView-Steuerelement übergeben Sie dann als Parameter eines Aufrufs der Init-Methode.
Auf die normale Selected-Eigenschaft eines Knotens bzw. auf die SelectedItem-Eigenschaft des TreeViews sollten Sie nicht mehr zugreifen. Die Klasse selbst repräsentiert die aktuelle Auswahl, die Sie in einer For...Each-Schleife durchlaufen können. Auf die einzelnen Elemente der Auswahl greifen Sie über die Eigenschaft SelectedNode mit einer nummerischen Indexangabe als Parameter zu. Den Knoten, der die Fokus-Markierung (dies ist der gepunktete Rahmen um einen Knoten) trägt, repräsentiert die Eigenschaft FocusNode. Sie entspricht weitestgehend der ursprünglichen Eigenschaft SelectedItem. Das setzen dieses Fokus-Knotens erledigen Sie über die Methode SetFocusNode mit einem Index oder einem Schlüssel eines Knotens.
Abweichend von den Standardfarben des TreeViews und den Standard-Markierungsfarben des Systems können Sie auch eigene Farbkombinationen in den Eigenschaften der Klasse setzen: BackColor und ForeColor für nicht markierte Knoten, und SelBackColor und SelForeColor für markierte Knoten. Zusätzlich können Sie über SelBold festlegen, ob markierte Knoten fett dargestellt werden sollen.
Mit der Eigenschaft NoDefaultSel legen Sie fest, ob der normale Selektions-Mechanismus des TreeViews übergangen werden soll. Sie ist auf True voreingestellt. Wird sie auf False gesetzt, wird die normale Knotenmarkierung sichtbar, was vor allem bei eigenen Farbkombinationen für die Markierungen störend wirken dürfte.
Ist die Eigenschaft NoClearOnSpaceKlick gesetzt, werden die Markierungen nicht bei einem Klick in den freien Raum aufgehoben.
Die meisten Ereignisse des TreeViews stehen über die Klasse zur Verfügung. Sie sollten sie anstelle der Original-Ereignisse nutzen, um Störungen der Funktion der Klasse und Seiteneffekte zu vermeiden. Beim MouseDown-Ereignis ist der Parameter Cancel hinzugekommen. Geben Sie in Cancel den Wert zurück, erfolgt keine Markierung, falls die Strg-Taste oder die Kombination Umschalt-Strg beim Anklicken eines Knotens gedrückt gewesen sind. Hinzugekommen ist zum einen das Ereignis SelChange, das bei jeder Änderung der Auswahl ausgelöst wird. Zum anderen informiert das praktische Ereignis NodeDblClick darüber, ob ein Doppelklick auf einen Knoten erfolgt ist.
Die Auswahl der Knoten bzw. die Aufhebung von Auswahlen können Sie auch über einige Methoden vornehmen. ClearSelection hebt die gesamte Auswahl auf. Übergeben Sie einen bestimmten Knoten als Parameter, wird nur die Markierung dieses Knotens und des Astes darunter aufgehoben. Mit SelectAllNodes markieren Sie alle Knoten im TreeView. Bei Übergabe eines bestimmten Knotens wird nur der Knoten samt des Astes darunter ausgewählt. SelectNode markiert einen einzelnen Knoten bzw. hebt die Markierung auf, je nach Wert des Parameters Selected. Mit ToggelNode kehren Sie die Markierung eines einzelnen Knotens um. Setzen Sie den optionalen Parameter Children auf True, werden auch die Markierungen aller knoten unterhalb des betreffenden Knotens umgekehrt. Mit ToggleSelection schließlich kehren Sie die Markierungen aller Knoten im TreeView um. Analog zu ClearSelection und SelectAllNodes betrifft der Aufruf auch wieder nur einen einzelnen Knoten samt des Astes darunter, wenn Sie diesen als Parameter übergeben.
Private WithEvents eTreeView As TreeView
Private mKbdMode As Boolean
Private mMultiSelectMode As Boolean
Private mSelectedNodes As Collection
Public Event AfterLabelEdit(Cancel As Integer, NewString As String)
Public Event BeforeLabelEdit(Cancel As Integer)
Public Event Click()
Public Event Collapse(ByVal Node As MSComctlLib.Node)
Public Event DblClick()
Public Event Expand(ByVal Node As MSComctlLib.Node)
Public Event KeyDown(KeyCode, Shift)
Public Event KeyPress(KeyAscii)
Public Event KeyUp(KeyCode, Shift)
Public Event MouseDown(Button As Integer, Shift As Integer, _
ByVal x As Single, ByVal y As Single, Cancel As Boolean)
Public Event MouseMove(Button As Integer, Shift As Integer, _
ByVal x As Single, ByVal y As Single)
Public Event MouseUp(Button As Integer, Shift As Integer, _
ByVal x As Single, ByVal y As Single)
Public Event NodeClick(ByVal Node As MSComctlLib.Node)
Public Event NodeDblClick(ByVal Node As MSComctlLib.Node)
Public Event SelChange()
Private pBackColor As OLE_COLOR
Private pFocusNode As Node
Private pForeColor As OLE_COLOR
Private pNoClearOnSpaceClick As Boolean
Private pNoDefaultSel As Boolean
Private pSelBackColor As OLE_COLOR
Private pSelForeColor As OLE_COLOR
Private pSelBold As Boolean
Public Property Get BackColor() As OLE_COLOR
BackColor = pBackColor
End Property
Public Property Let BackColor(New_BackColor As OLE_COLOR)
Dim nNode As Node
Dim nSelNode As Node
Select Case New_BackColor
Case pBackColor
Case Else
pBackColor = New_BackColor
On Error Resume Next
For Each nNode In mSelectedNodes
Set nSelNode = mSelectedNodes(CStr(ObjPtr(nNode)))
If Err.Number Then
Err.Clear
nNode.BackColor = pBackColor
If Err.Number Then
Exit Property
End If
End If
Next
End Select
End Property
Public Property Get Count() As Long
Count = mSelectedNodes.Count
End Property
Public Property Get FocusNode() As Node
Set FocusNode = pFocusNode
End Property
Public Property Get ForeColor() As OLE_COLOR
ForeColor = pForeColor
End Property
Public Property Let ForeColor(New_ForeColor As OLE_COLOR)
Dim nNode As Node
Dim nSelNode As Node
Select Case New_ForeColor
Case pForeColor
Case Else
pForeColor = New_ForeColor
For Each nNode In mSelectedNodes
Set nSelNode = mSelectedNodes(CStr(ObjPtr(nNode)))
If Err.Number Then
Err.Clear
nNode.ForeColor = pForeColor
If Err.Number Then
Exit Property
End If
End If
Next
End Select
End Property
Public Property Get NoClearOnSpaceClick() As Boolean
NoClearOnSpaceClick = pNoClearOnSpaceClick
End Property
Public Property Let NoClearOnSpaceClick _
(New_NoClearOnSpaceClick As Boolean)
pNoClearOnSpaceClick = New_NoClearOnSpaceClick
End Property
Public Property Get NoDefaultSel() As Boolean
NoDefaultSel = pNoDefaultSel
End Property
Public Property Let NoDefaultSel(New_NoDefaultSel As Boolean)
pNoDefaultSel = New_NoDefaultSel
End Property
Public Property Get SelBackColor() As OLE_COLOR
SelBackColor = pSelBackColor
End Property
Public Property Let SelBackColor(New_SelBackColor As OLE_COLOR)
Dim nNode As Node
Select Case New_SelBackColor
Case pSelBackColor
Case Else
pSelBackColor = New_SelBackColor
On Error Resume Next
For Each nNode In mSelectedNodes
nNode.BackColor = pSelBackColor
If Err.Number Then
Exit Property
End If
Next
End Select
End Property
Public Property Get SelBold() As Boolean
SelBold = pSelBold
End Property
Public Property Let SelBold(New_SelBold As Boolean)
Dim nNode As Node
Select Case New_SelBold
Case pSelBold
Case Else
pSelBold = New_SelBold
On Error Resume Next
For Each nNode In mSelectedNodes
nNode.Bold = pSelBold
If Err.Number Then
Exit Property
End If
Next
End Select
End Property
Public Property Get SelectedNode(Index As Long) As Collection
If mSelectedNodes.Count Then
Set SelectedNode = mSelectedNodes(Index)
End If
End Property
Public Property Get SelForeColor() As OLE_COLOR
SelForeColor = pSelForeColor
End Property
Public Property Let SelForeColor(New_SelForeColor As OLE_COLOR)
Dim nNode As Node
Select Case New_SelForeColor
Case pSelForeColor
Case Else
pSelForeColor = New_SelForeColor
On Error Resume Next
For Each nNode In mSelectedNodes
nNode.ForeColor = pSelForeColor
If Err.Number Then
Exit Property
End If
Next
End Select
End Property
Public Sub ClearSelection(Optional Node As Node)
Dim nNode As Node
Dim nChild As Node
If Node Is Nothing Then
Set nNode = zGetFirstNode()
Else
Set nNode = Node
End If
If nNode Is Nothing Then
Exit Sub
End If
With nNode
.BackColor = pBackColor
.ForeColor = pForeColor
.Bold = False
On Error Resume Next
mSelectedNodes.Remove CStr(ObjPtr(nNode))
If .Children Then
Set nChild = .Child
Do While Not (nChild Is Nothing)
Me.ClearSelection nChild
Set nChild = nChild.Next
Loop
End If
End With
RaiseEvent SelChange
End Sub
Public Sub SelectAllNodes(Optional Node As Node)
Dim nNode As Node
Dim nChild As Node
If Node Is Nothing Then
Set nNode = zGetFirstNode()
Else
Set nNode = Node
End If
If nNode Is Nothing Then
Exit Sub
End If
With nNode
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
On Error Resume Next
mSelectedNodes.Add nNode, CStr(ObjPtr(nNode))
If .Children Then
Set nChild = .Child
Do While Not (nChild Is Nothing)
Me.SelectAllNodes nChild
Set nChild = nChild.Next
Loop
End If
RaiseEvent SelChange
End With
End Sub
Public Sub SelectNode(Node As Node, ByVal Selected As Boolean)
If Node Is Nothing Then
Exit Sub
End If
With Node
Select Case Selected
Case True
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
On Error Resume Next
mSelectedNodes.Add Node, CStr(ObjPtr(Node))
Case False
.BackColor = pBackColor
.ForeColor = pForeColor
.Bold = False
On Error Resume Next
mSelectedNodes.Remove CStr(ObjPtr(Node))
End Select
End With
RaiseEvent SelChange
End Sub
Public Sub SetFocus()
On Error Resume Next
eTreeView.SetFocus
End Sub
Public Sub SetFocusNode(KeyIndex As Variant)
With eTreeView.Nodes(KeyIndex)
.Selected = True
.Selected = False
End With
End Sub
Public Sub ToggleNode(Optional Node As Variant, _
Optional ByVal Children As Boolean)
Dim nNode As Node
Dim nChild As Node
Dim nSelected As Boolean
If IsMissing(Node) Then
Set nNode = pFocusNode
Else
Set nNode = Node
End If
If nNode Is Nothing Then
Exit Sub
End If
With nNode
Select Case .BackColor
Case pSelBackColor
.BackColor = pBackColor
.ForeColor = pForeColor
.Bold = False
On Error Resume Next
mSelectedNodes.Remove CStr(ObjPtr(nNode))
Case pBackColor
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
mSelectedNodes.Add nNode, CStr(ObjPtr(nNode))
nSelected = True
End Select
If CBool(.Children) And Children Then
Set nChild = .Child
Do While Not (nChild Is Nothing)
zSelectChildren nChild, nSelected
Set nChild = nChild.Next
Loop
End If
End With
RaiseEvent SelChange
End Sub
Private Sub zSelectChildren(Node As Node, _
ByVal Selected As Boolean)
Dim nChild As Node
With Node
Select Case Selected
Case False
.BackColor = pBackColor
.ForeColor = pForeColor
.Bold = False
On Error Resume Next
mSelectedNodes.Remove CStr(ObjPtr(Node))
Case True
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
On Error Resume Next
mSelectedNodes.Add Node, CStr(ObjPtr(Node))
End Select
Set nChild = .Child
Do While Not (nChild Is Nothing)
zSelectChildren nChild, Selected
Set nChild = nChild.Next
Loop
End With
End Sub
Public Sub ToggleSelection(Optional Node As Node)
Dim nNode As Node
Dim nChild As Node
If Node Is Nothing Then
Set nNode = zGetFirstNode()
Else
Set nNode = Node
End If
If nNode Is Nothing Then
Exit Sub
End If
With nNode
Select Case .BackColor
Case pSelBackColor
.BackColor = pBackColor
.ForeColor = pForeColor
.Bold = False
On Error Resume Next
mSelectedNodes.Remove CStr(ObjPtr(nNode))
Case pBackColor
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
On Error Resume Next
mSelectedNodes.Add nNode, CStr(ObjPtr(nNode))
End Select
If .Children Then
Set nChild = .Child
Do While Not (nChild Is Nothing)
Me.ToggleSelection nChild
Set nChild = nChild.Next
Loop
End If
End With
RaiseEvent SelChange
End Sub
Public Function NewEnum() As IUnknown
Set NewEnum = mSelectedNodes.[_NewEnum]
End Function
Public Sub Init(TreeView As TreeView)
Set eTreeView = TreeView
With eTreeView.Nodes(1)
.Selected = True
.Selected = False
End With
Set pFocusNode = eTreeView.Nodes(1)
End Sub
Private Sub eTreeView_AfterLabelEdit(Cancel As Integer, _
NewString As String)
RaiseEvent AfterLabelEdit(Cancel, NewString)
End Sub
Private Sub eTreeView_BeforeLabelEdit(Cancel As Integer)
If mMultiSelectMode Then
Cancel = True
Else
RaiseEvent BeforeLabelEdit(Cancel)
End If
End Sub
Private Sub eTreeView_Click()
RaiseEvent Click
End Sub
Private Sub eTreeView_Collapse(ByVal Node As MSComctlLib.Node)
If mMultiSelectMode Then
Node.Selected = False
End If
RaiseEvent Collapse(Node)
End Sub
Private Sub eTreeView_DblClick()
Dim nNode As Node
Set nNode = pFocusNode
If Not (nNode Is Nothing) Then
RaiseEvent NodeDblClick(nNode)
End If
RaiseEvent DblClick
End Sub
Private Sub eTreeView_Expand(ByVal Node As MSComctlLib.Node)
RaiseEvent Expand(Node)
End Sub
Private Sub eTreeView_KeyDown(KeyCode As Integer, Shift As Integer)
RaiseEvent KeyDown(KeyCode, Shift)
mKbdMode = True
End Sub
Private Sub eTreeView_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case vbKeyEscape
KeyAscii = 0
Case vbKeySpace
KeyAscii = 0
End Select
If KeyAscii Then
RaiseEvent KeyPress(KeyAscii)
End If
End Sub
Private Sub eTreeView_KeyUp(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEscape
If Shift = vbShiftMask Then
Me.ClearSelection
KeyCode = 0
End If
Case vbKeySpace
Select Case Shift
Case 0
Me.ToggleNode , CBool(Shift = vbShiftMask)
KeyCode = False
Case vbShiftMask
Me.ToggleNode pFocusNode, CBool(Shift = vbShiftMask)
KeyCode = False
End Select
Case vbKeyA
If Shift = vbCtrlMask Then
Me.SelectAllNodes
KeyCode = 0
End If
Case vbKeyT
If Shift = vbCtrlMask Then
Me.ToggleSelection
KeyCode = 0
End If
End Select
If KeyCode Then
RaiseEvent KeyUp(KeyCode, Shift)
End If
End Sub
Private Sub eTreeView_MouseDown(Button As Integer, _
Shift As Integer, x As Single, y As Single)
Dim nNode As Node
Dim nCancel As Boolean
mMultiSelectMode = False
mKbdMode = False
RaiseEvent MouseDown(Button, Shift, x, y, nCancel)
If Not nCancel Then
Set nNode = eTreeView.HitTest(x, y)
If (Shift And vbCtrlMask) = vbCtrlMask Then
mMultiSelectMode = True
ElseIf Not pNoClearOnSpaceClick Then
Me.ClearSelection
RaiseEvent SelChange
End If
End If
End Sub
Private Sub eTreeView_MouseMove(Button As Integer, _
Shift As Integer, x As Single, y As Single)
RaiseEvent MouseMove(Button, Shift, x, y)
End Sub
Private Sub eTreeView_MouseUp(Button As Integer, _
Shift As Integer, x As Single, y As Single)
Dim nNode As Node
Set nNode = eTreeView.HitTest(x, y)
Select Case Button
Case vbLeftButton
If Not (nNode Is Nothing) Then
If (Shift And vbCtrlMask) = vbCtrlMask Then
Me.ToggleNode nNode, CBool((Shift And vbShiftMask) _
= vbShiftMask)
nNode.Selected = False
End If
End If
Case Else
If mMultiSelectMode Then
If Not (nNode Is Nothing) Then
nNode.Selected = False
End If
End If
End Select
Set pFocusNode = nNode
RaiseEvent MouseUp(Button, Shift, x, y)
End Sub
Private Sub eTreeView_NodeClick(ByVal Node As MSComctlLib.Node)
If Not mMultiSelectMode And Not mKbdMode Then
If pNoDefaultSel Then
Node.Selected = False
End If
With Node
.BackColor = pSelBackColor
.ForeColor = pSelForeColor
.Bold = pSelBold
End With
Set mSelectedNodes = New Collection
mSelectedNodes.Add Node, CStr(ObjPtr(Node))
RaiseEvent SelChange
End If
If mKbdMode And pNoDefaultSel Then
Node.Selected = False
End If
Set pFocusNode = Node
RaiseEvent NodeClick(Node)
End Sub
Private Sub Class_Initialize()
pBackColor = vbWindowBackground
pForeColor = vbWindowText
pNoDefaultSel = True
pSelBackColor = vbHighlight
pSelForeColor = vbHighlightText
Set mSelectedNodes = New Collection
End Sub
Private Sub Class_Terminate()
Set mSelectedNodes = Nothing
Set pFocusNode = Nothing
Set eTreeView = Nothing
End Sub
Private Function zGetFirstNode() As Node
With eTreeView.Nodes
If .Count Then
Set zGetFirstNode = .Item(1).Root.FirstSibling
End If
End With
End Function
|