Ti mostro un esempio veloce e semplice della struttura semplificata, ovvero con una sola Tabella che consente la gerarchia a N livelli.
Quindi la struttura delle Tabelle prevede 1 Tabella Principale de una Tabella GErarchica:
Tabella Princilape che identifica un PROGETTO:
[tProgetti]
CAMPI:
IdProgetto (PK Counter)
Titolo (Testo Breve)
... altri campi non utili
Tabella Gerarchica che identifica FASI di LAVORO del PROGETTO, ongni Fase può avere SOTTOFASI(n) non finite.
[tPhases]
CAMPI:
IdProgetto (FK Intero Lungo)
IdPhaseParent (FK Intero Lungo)
IdPhase (PK Counter)
NomeFase (Testo)
La tabella [tProgetti] è in relazione 1-M con la tabella [tPhases].
La tabella [tPhases] ha una SELFREFERENCE_TABLE [tPhases_1] sul campo
[tPhases_1](IdPhase)[1]<----->[M](IdPhaseParent)[tPhases]
Il Legame(JOIN) è di tipo [3]
La Maschera principale è basata sulla Tabella [tProgetti], sicchè ho i Controlli associati ai campi [IdProgetto] e [Titolo]
Sono 2 TextBox con il NomeControllo=NomeCampo
Il Controllo TreeView si chiama [cTW]
Nella Form ho questo codice sotto...
La funzione LoadTree() carica il NOME del PROGETTO come ROOT, poi inizia a caricare i Records della Tabella [tPhases] che NON HANNO Padre, ovvero tutti i Records a LIVELLO=0.
La funzione come vedi nel LOOP, chiama la Funzione(Ricorsiva), passando il NODO che contiene la PK nella proprietà TAG del Nodo.
AddChildren sKey, ndP
Questa seconda Funzione carica i Records con IdPhaseParent=Nodo.tag, ovvero tutt i Records che hanno come Padre il nodo di LIVELLO(N-1)
Qusta funzione a sua volta nel LOOP esplora se il Record ha dei SottoRecords richiamando se setssa..., questa è la RICORSIONE.
Questo il codice(ho tolto dei pezzi non necessari, spero di non aver tolto troppo):
---------------------------------------------------------------------------------
Option Compare Database
Option Explicit
Private Const mcProcParent As String = "Form_Progetto"
Private Const mMnuName As String = "mPopupRuntime"
Private mT As MSComctlLib.TreeView
Private mND As MSComctlLib.node
Private mItem As String
Private Sub Form_Load()
Set mT = Me.cTW.Object
mT.Font.Name = "Calibri"
Call LoadTree
Me.cTW.Visible = True
End Sub
Public Sub LoadTree()
Const mcProcName = "LoadTree"
On Error GoTo Err_Handler
Dim rs As DAO.Recordset
Dim rsI As DAO.Recordset
Dim sKey As String
Dim ssKEY As String
Dim sSQL As String
Dim ndP As MSComctlLib.node
Dim ndC As MSComctlLib.node
Dim ndRoot As MSComctlLib.node
Set mT = Me.cTW.Object
mT.Nodes.Clear
Set ndRoot = mT.Nodes.Add(, , "P-" & Me.IdProgetto, Me.Titolo, 1)
sSQL = "SELECT * FROM tPhases WHERE IdProgetto=" & Me.IdProgetto & " AND IdPhaseParent Is Null ORDER BY IdPhase;"
Set rs = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
If rs.EOF = False Then
rs.MoveFirst
Do Until rs.EOF
sKey = UniqueKey
Set ndP = mT.Nodes.Add("P-" & Me.IdProgetto, tvwChild)
ndP.Key = sKey
ndP.Text = rs.Fields("NomeFase").Value
ndP.Image = 2
ndP.Tag = rs.Fields("IdPhase").Value
'add sub levels
AddChildren sKey, ndP
rs.MoveNext
Loop
End If
Exit_Here:
On Error Resume Next
ndRoot.Expanded = True
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
Set ndP = Nothing
Set ndRoot = Nothing
Err.Clear
Exit Sub
Err_Handler:
Select Case Err.Number
Case Else
MsgBox Err.Number & " in " & mcProcParent & vbNewLine & Err.Description, vbCritical, mcProcName
End Select
Resume Exit_Here
End Sub
Sub AddChildren(ByVal Parent_Key As String, Optional mNode As MSComctlLib.node)
Const mcProcName = "AddChildren"
On Error GoTo Err_Handler
Dim rsC As DAO.Recordset
Dim sSQL As String
Dim sKey As String
Dim nd As MSComctlLib.node
sSQL = "SELECT * FROM tPhases WHERE IdProgetto=" & Me.IdProgetto & " AND IdPhaseParent = " & mNode.Tag & " ORDER BY IdPhase"
Set rsC = DBEngine(0)(0).OpenRecordset(sSQL, dbOpenDynaset, dbReadOnly)
If rsC.RecordCount > 0 Then
With rsC
.MoveFirst
Do While .EOF = False
sKey = UniqueKey
Set nd = cTW.Nodes.Add(Parent_Key, tvwChild)
nd.Key = sKey
nd.Text = .Fields("NomeFase").Value
nd.Image = 4
nd.Tag = .Fields("IdPhase").Value
AddChildren sKey, nd
Set nd = Nothing
.MoveNext
Loop
End With
End If
Exit_Here:
On Error Resume Next
If Not rsC Is Nothing Then rsC.Close: Set rsC = Nothing
Err.Clear
Exit Sub
Err_Handler:
Select Case Err.Number
Case Else
MsgBox Err.Number & " in " & mcProcParent & vbNewLine & Err.Description, vbCritical, mcProcName
End Select
Resume Exit_Here
End Sub
Public Function UniqueKey() As String
'***************************************************************************
'Purpose: Generate a unique key for a Treeview. Actually there is a one in
' 10 million chance of the key *not* being unique, but the error
' handling code in the calling Sub takes care of that.
'Inputs : None
'Outputs: The key for the Treeview
'***************************************************************************
UniqueKey = "K" & 1 + Int(Rnd() * 10000000)
End Function
Secondo me questo è un Esempio abbastanza banale e semplice.
Se vuoi applicare il concetto alla evoluzione con le 2 Tabelle, la logica è esattamente la stessa...
Ciao
@Alex