PDA

Vollständige Version anzeigen : Intelligentes Treeview mit Checkboxen


Toast78
04.05.2011, 12:48
Hallo,
ich katalogisiere gerade, in welchen Abteilungen, welche Berechnungsprogramme benutzt werden.
Die Abteilungshierarchie ist dabei in einer Baumstruktur aufgebaut, die bis zu 4 Ebenen tief sein kann.
Nun stelle ich diese Abteilungshierarchie auch in einem TreeView mit Checkboxen dar. Ist eine Checkbox ausgewählt, so findet die momentan angezeigte Berechnung dort Verwendung, ansonsten nicht.
Was ich jetzt noch gerne hätte, wäre der Punkt, dass der übergeordnete Knoten auch ausgewählt werden soll, wenn mit dem letzten Knoten aus der unteren Hierarchie-Ebene alle Knoten ausgewählt wurden.
Umgekehrt soll, wenn ein übergeordneter Knoten ausgewählt ist und deren Kinder natürlich auch, beim De-Selektieren eines Kindes natürlich auch aus dem übergeordneten Knoten der Haken entfernt werden.
So, und dies soll jetzt allgemein gültig für jede Tiefe sein.

Hat sowas schonmal jemand programmiert?

Josef P.
04.05.2011, 12:49
Hallo!

Hat sowas schonmal jemand programmiert?
Ja. :D

mfg
Josef

Toast78
04.05.2011, 12:52
Grr! Ich hätte wissen müssen, dass bei dieser dämlichen Frage eine genauso dämliche Antwort kommt. Wenn mich jemand fragt, ob ich weiß, wie spät es ist, sag ich auch manchmal einfach nur "Ja!" :D

Kannst du mir vielleicht den Source-Code geben?

Atrus2711
04.05.2011, 12:53
*handheb* :)

Vielleicht hilft dir schon meine "Treeview-statt-Kombifeld"-Demo auf die Sprünge. Die macht im Multiselect-Modus ähnliches und ließe sich ausschlachten.

Josef P.
04.05.2011, 12:57
@Toast: diese Antwort konnte ich mir einfach nicht verkneifen. ;)

Souce-Code kopieren finde ich langweilig (außerdem müsste ich den dann suchen).
Ich betrachte das Forum als Plattform zur Lösungsfindung und nicht als Quellcode-Erstellungsplattform.

Das Prinzip ist aber schnell beschrieben:
Bei jeder Änderung eines Nodes prüfen, ob die "Geschwister" den gleichen Checked-Wert haben und enstprechend den Parent-Node einstellen.
Der Parent-Node prüft dann seine Geschwister (bzw. lässt seinen Parent dessen Childs-Auflistung prüfen) usw.

mfg
Josef

Toast78
04.05.2011, 13:14
@Toast: diese Antwort konnte ich mir einfach nicht verkneifen. ;)
...
Das Prinzip ist aber schnell beschrieben:
Bei jeder Änderung eines Nodes prüfen, ob die "Geschwister" den gleichen Checked-Wert haben und enstprechend den Parent-Node einstellen.
Der Parent-Node prüft dann seine Geschwister (bzw. lässt seinen Parent dessen Kinder prüfen) usw.Brauchst du dir bei mir auch nicht verkneifen. Bin ja jetzt auch schon ein paar Tage hier.

Da ich aber Klugscheißer bin: Bei den Geschwistern einer jeden Ebene muss nicht nur überprüft werden, ob sie den gleichen Wert haben wie der aktualisierte, sondern auch die Kinder der Geschwister.

@Martin: Wo finde ich denn dieses Beispiel? Du hast ja da einiges an Links in deiner Signatur stehen.

Atrus2711
04.05.2011, 13:16
Hier: http://ms-office-forum.de/forum/showthread.php?t=261835

Josef P.
04.05.2011, 13:18
Bei den Geschwistern einer jeden Ebene muss nicht nur überprüft werden, ob sie den gleichen Wert haben wie der aktualisierte, sondern auch die Kinder der Geschwister.
Das sollte nicht notwendig sein, da bei Änderungen der Kinder der Geschwister diese Änderungen bereits in den Geschwistern bekannt sein müssten und die Geschwister bereits den richtigen Wert haben müssten.
Tipp: Eigenschaft für Ja und Nein bei den Kindern im Node ergänzen.
(Wird oft als ausgegrautes Häkchen dargestellt.)

mfg
Josef

Toast78
04.05.2011, 13:29
Ich verwende das Treeview aus den CommonControls. Meinst du etwa so ein graues Häkchen bei einem übergeordneten Knoten, wenn nur einige Kinder ausgewählt sind?
Geht das überhaupt bei diesem Control?
Ach ja, du hattest recht. Ich habe zu kompliziert gedacht.

@Martin: Ja, genau so weit bin ich jetzt gerade auch, wie die Mehrfachauswahl :D. Josef lenkt mich ja gerade auf den Pfad der Erleuchtung.

Josef P.
04.05.2011, 13:58
Meinst du etwa so ein graues Häkchen bei einem übergeordneten Knoten, wenn nur einige Kinder ausgewählt sind?
Ja, das meinte ich.

Geht das überhaupt bei diesem Control?
Eingebaut ist es nicht, aber man könnte zur Not die Checkbox durch ein Icon ersetzen.

Oder: man legt einfach fest, dass der Parent-Node auf checked=True gesetzt wird, sobald ein Child checked ist.
(Diese Variante würde ich am Anfang nutzen, da die graphische Darstellung nicht so wichtig wie die Funktionsweise ist. ;))

mfg
Josef

Toast78
04.05.2011, 16:39
Naja, momentan finde ich die grafische Darstellung wichtiger als die Funktionsweise. Problem ist momemtan nämlcih, dass man die ausgewählten Einträge im Baum erstmal suchen muss. Dazu wollte ich schon ein gesperrtes Textfeld erstellen, um eine Zusammenfassung der ausgewählten Nodes darzustellen

Josef P.
04.05.2011, 18:22
Problem ist momemtan nämlcih, dass man die ausgewählten Einträge im Baum erstmal suchen muss.
.. und die findest man nicht, wenn die Eltern angehakt sind?

mfg
Josef

Toast78
04.05.2011, 18:47
Genau umgekehrt! Man findet untergeordnete Knoten nicht, wenn die übergeordneten nicht ausgewählt sind. Deswegen wäre das mit den "halb" ausgewählten keine schlechte Sache.

Toast78
05.05.2011, 08:05
Ich habe mich jetzt für die ImageList mit dem zusätzlichen halbgrauen Haken im Kontrollkästchen entschieden und stehe jetzt vor einem wesentlich besch...eidenerem Problem: Wie setze ich denn Haken für jeden Knoten anständig?
Ich habe momentan nämlich nur ein Recordset, was in einer Schleife völlig rücksichtslos die Checked-Eigenschaft aller Knoten setzte.

Atrus2711
05.05.2011, 08:14
Hi,


"ganz" angehakt werden muss jeder gewählte Wert.
"halb" angehakt werden müssen sämtliche Elternknoten der angehakten Werte. Das dürfte am einfachsten per Rekursion oder per Schleife gehen (Do Until nde.Parent is Nothing)

Toast78
05.05.2011, 08:36
Okay, der erste Punkt ließe sich in meiner bestehenden Schleife einfach abackern. Da müsste ich soweit fast gar nichts ändern.
Nur wie funktioniert das mit dem zweiten Punkt? Ich wollte zuerst den ersten Schritt mit dem zweiten kombinieren. Hatte dabei sogar überlegt, ob ich für jede Rekursions-Ebene ein Recordset öffne. Zum einen war das aber für mich Laufzeit-Verschwendung und zum anderen.
Wo fange ich an? Durch die ganze Nodes-Collection zu wandern halte ich auch nicht gerade für sinnvoll. Vor allem will ich ja erstmal nur die "nicht-ausgewählten" Hauptknoten haben und der Rest soll durch die Rekursion erledigt werden.
Boah, entweder stehe gerade auf dem Schlauch, oder das ist mal ne kompliziertere Aufgabe. :rolleyes:

Atrus2711
05.05.2011, 08:49
Hi,

du könntest das direkt im Treeview machen und brauchst dann kein Recordset. Jeder Node weiß direkt, wer sein Elternknoten ist. Du brauchst dich lediglich von jedem selektierten Node nach oben durchzuhangeln. Im Recordset müsstest du das erst per ParentID rauskriegen.

Optimierungspotential besteht vielleicht noch, wenn man berücksichtigt, dass ein Elternknoten halbzufärben ist, sobald auch nur eines seiner Kinder halb- oder ganzsselektiert ist. Sprich: man müsste nicht unbedingt von jedem gewähltem Node nach oben durchgehen, sondern man kann die Geschwister auf gleicher Ebene (per nde.Previous und nde.Next erreichbar) getrost ignorieren. Aber das dürfte eh nur ein paar Prozessortakte ausmachen, da ein Treeview von Abteilungen wohl eher klein sein wird.

Toast78
05.05.2011, 09:11
Hurra! Ich habs hinbekommen! Zumindest das Laden.

Ach ja: Nochmal was zu den Abteilungen. Unser Firmentelefonbuch hat über 400 Abteilungen eingetragen.
Für dieses Projekte konnte ich den Datenbestand auf etwa 100 Abteilungen eingrenzen.

Toast78
05.05.2011, 10:06
Ich weiß nicht warum, aber es funktioniert hervorragend! http://www.my-smileys.de/smileys3/ylsuper.gif
'this sub is used to fill the whole tree on loading the form
Private Sub fillUserDepartments()
Dim rstUserDepartments As DAO.Recordset

If Not isDebugMode Then
On Error GoTo fillUserDepartments_Error
End If

Set rstUserDepartments = CodeDb.OpenRecordset("SELECT DepartmentNo, " & _
"DepartmentID, SuperiorDepartment FROM Departments " & _
"ORDER BY DepartmentID;", dbOpenDynaset, dbForwardOnly)

With rstUserDepartments
While Not .EOF
If Nz(!SuperiorDepartment, 0) = 0 Then
UserDepartmentsTree.Nodes.Add , , "DepartmentNo=" & !DepartmentNo, !DepartmentID
Else
UserDepartmentsTree.Nodes.Add "DepartmentNo=" & !SuperiorDepartment, tvwChild, _
"DepartmentNo=" & !DepartmentNo, !DepartmentID
End If
.MoveNext
Wend
End With

fillUserDepartments_Exit:
On Error Resume Next
rstUserDepartments.Close
Set rstUserDepartments = Nothing
On Error GoTo 0
Exit Sub

fillUserDepartments_Error:
MsgBox Err.Description, vbCritical, "ComVersion"
Resume fillUserDepartments_Exit
End Sub

'update database
'the structure is based upon existing records.
'an existing records means, that the according node is checked
Private Sub updateUserDepartment(DaCToolNo As Long, Version As String, _
UserDepartment As String, newValue As Boolean)
Dim strSQL As String
Dim lngID As Long

If Not isDebugMode Then
On Error GoTo updateUserDepartment_Error
End If

lngID = CLng(Mid(UserDepartment, InStr(UserDepartment, "=") + 1))
If newValue Then
strSQL = "INSERT INTO DaCToolVersionUserDepartments (DaCToolNo, Version, " & _
"UserDepartment) " & _
"VALUES (" & Me.DaCToolNo & ", '" & Me.Version & "', " & _
lngID & ");"
Else
strSQL = "DELETE FROM DaCToolVersionUserDepartments " & _
"WHERE DaCToolNo = " & Me.DaCToolNo & _
" AND Version = '" & Me.Version & "' AND UserDepartment = " & lngID & ";"
End If
CodeDb.Execute strSQL, dbFailOnError
On Error GoTo 0
Exit Sub

updateUserDepartment_Error:
If Err.Number <> 3022 Then
MsgBox Err.Description, vbCritical, "ComVersion"
End If
End Sub

'update the sub data, because the record of the form has changed
Private Sub updateUserDepartmentsTree()
Dim NodX As MSComctlLib.Node
Dim rst As DAO.Recordset

If Not isDebugMode Then
On Error GoTo updateUserDepartments_Error
End If

'clear checkboxes of last record
For Each NodX In UserDepartmentsTree.Nodes
'NodX.Checked = False
NodX.Image = "Unchecked"
Next NodX

If Not Me.NewRecord Then
Set rst = CodeDb.OpenRecordset("SELECT UserDepartment " & _
"FROM DaCToolVersionUserDepartments " & _
"WHERE DaCToolNo = " & Me.DaCToolNo & " AND Version = '" & Me.Version & "'", _
dbOpenDynaset, dbSeeChanges)
With rst
While Not .EOF
'UserDepartmentsTree.Nodes("DepartmentNo=" & !UserDepartment).Checked = True
UserDepartmentsTree.Nodes("DepartmentNo=" & !UserDepartment).Image = "Checked"
Set NodX = UserDepartmentsTree.Nodes("DepartmentNo=" & _
!UserDepartment).Parent
While Not NodX Is Nothing
If NodX.Image = "Unchecked" Then
NodX.Image = "Halfchecked"
End If
Set NodX = NodX.Parent
Wend
.MoveNext
Wend
End With
End If

updateUserDepartments_Exit:
On Error Resume Next
rst.Close
Set rst = Nothing
On Error GoTo 0
Exit Sub

updateUserDepartments_Error:
MsgBox Err.Description, vbCritical, "ComVersion"
Resume updateUserDepartments_Exit
End Sub

Private Sub UserDepartmentsTree_NodeClick(ByVal Node As Object)
If Me.NewRecord Then
If MsgBox("This is a new record. Save changes first?", vbQuestion + vbYesNo, _
"ComVersion") = vbYes Then
DoCmd.RunCommand acCmdSaveRecord
Select Case Node.Image
Case "Checked"
Call checkUserDepartmentNodes(Node, "Unchecked")
Case "Unchecked"
Call checkUserDepartmentNodes(Node, "Checked")
Case "Halfchecked"
Call checkUserDepartmentNodes(Node, "Checked")
End Select
End If
Else
Select Case Node.Image
Case "Checked"
Call checkUserDepartmentNodes(Node, "Unchecked")
Case "Unchecked"
Call checkUserDepartmentNodes(Node, "Checked")
Case "Halfchecked"
Call checkUserDepartmentNodes(Node, "Checked")
End Select
End If
End Sub

'Set the checkboxe in the whole path
Private Sub checkUserDepartmentNodes(SuperNode As MSComctlLib.Node, newValue As String)
Dim NodX As MSComctlLib.Node

If Not isDebugMode Then
On Error GoTo checkUserDepartmentNodes_Error
End If

'Set the children of the selected nodes
If SuperNode.Children > 0 Then
DoCmd.Hourglass True
Set NodX = SuperNode.Child
While Not NodX Is Nothing
NodX.Image = newValue
Call checkUserDepartmentNodes(NodX, newValue)
Set NodX = NodX.Next
Wend
DoCmd.Hourglass False
End If

UserDepartmentsTree.Nodes(SuperNode.Key).Image = newValue
'Update data base
Call updateUserDepartment(Me.DaCToolNo, Me.Version, SuperNode.Key, newValue = "Checked")
'Set the checkboxes of the superior nodes
If Not SuperNode.Parent Is Nothing Then
Call setSuperiorDepartments(SuperNode, newValue)
End If

On Error GoTo 0
Exit Sub

checkUserDepartmentNodes_Error:
MsgBox Err.Description, vbCritical, "ComVersion"
End Sub

'Set the checkboxes of the superior nodes
Private Sub setSuperiorDepartments(SuperNode As MSComctlLib.Node, newValue As String)
Dim NodX As MSComctlLib.Node
Dim cancel As Boolean

'Analyse siblings
cancel = False
Set NodX = SuperNode.FirstSibling
While Not NodX Is Nothing And Not cancel
If NodX.Image <> newValue Then
cancel = True
End If
Set NodX = NodX.Next
Wend

If Not SuperNode.Parent Is Nothing Then
If Not cancel Then
'All nodes in the current level (=siblings) have the same value.
'so the parent node gets the same value
SuperNode.Parent.Image = newValue
Call updateUserDepartment(Me.DaCToolNo, Me.Version, SuperNode.Parent.Key, _
newValue = "Checked")
Call setSuperiorDepartments(SuperNode.Parent, newValue)
Else
If SuperNode.Parent.Image = "Checked" Then
'update data base
Call updateUserDepartment(Me.DaCToolNo, Me.Version, SuperNode.Parent.Key, False)
End If
Call setSuperiorDepartments(SuperNode.Parent, newValue)
SuperNode.Parent.Image = "Halfchecked"
End If
End If
End Sub

Claypool
05.05.2011, 10:20
Hallo!

Ich habe das vor kurzer Zeit auch gehabt, dieses Problem. Ausgangsbasis war das hier: http://www.aboutvb.de/khw/artikel/khwtvwsemichecks.htm. Da ich aber eigene Images für die verschiedenen Ebenen verwende, konnte ich auf die Lösung der ausgegrauten Checked-Eigenschaft nicht zurückgreifen und habe den Code entsprechend umgebaut.

HTH, auch wenn du schon eine Lösung gefunden hast.

Grüße
Ingo

PS: Such' auf den Seiten mal nach TreeView und Knoten. Da ist einiges zu finden, was man für VBA anpassen kann.