Kontakt-Formular   Inhaltsverzeichnis   Druckansicht  

VisualBasic.tips

Startseite > Microsoft Access VBA > ActiveX Treeview > Teil 3 - Datenerfassung

Teil 3 - Datenerfassung

Ausgabe 01/2001

Download Beispieldatei

Nachdem wir im zweiten Teil dieser Artikelfolge erläutert haben, wie man den Inhalt eines TreeView dynamisch mit den Einträgen einer Tabelle füllt, möchten wir Ihnen jetzt die Handhabung zur Laufzeit erläutern. Denn ein TreeView eignet sich nicht nur dazu, schon vorhandene Daten anzuzeigen, sondern auch, um Daten unmittelbar zur Laufzeit einzugeben. Natürlich gibt es auch hierzu mehrere Möglichkeiten, die zum selben Ergebnis führen.
Grundsätzlich stellt sich die Frage:

  • soll man zuerst die Daten in einem Formular erfassen und im Anschluss den Eintrag im TreeView ergänzen lassen?
  • oder fügt man den Eintrag direkt ins TreeView ein und ergänzt dann erst die Daten im Formular?

Um das Handling mit dem TreeView zu erläutern, möchten wir Ihnen letztere Variante näher bringen ohne diese zu favorisieren. Letztendlich sollten die Anforderungen oder persönliche Vorlieben die Vorgehensweise beeinflussen.

Konzept und Vorgehen

Auch in diesem Folge soll der Code allgemeingültig und wiederverwendbar gestaltet werden, was allerdings eine gewisse Fragmentierung des Quellcodes nach sich zieht. Fragmentierung soll heißen, dass für jeden kleinen Schritt eine eigene Funktion zuständig sein soll. Dabei kann man leicht den Überblick verlieren. Die Namensgebung spielt dabei eine wichtige Rolle. Schon im 2. Teil haben wir ein globales Modul angelegt, welches den allgemeingültigen Code zum Füllen (bzw. Initialisieren) des TreeView enthält. Dieses Modul wird nun um die Funktionen zum Einfügen (AddNew) bzw. Löschen (Delete) ergänzt. In Abbildung 1 ist der logische Aufbau dargestellt. Der linke „Ast“ wurde bereits in der letzten Folge implementiert.

Bei der Neuanlage (mittlerer Ast) und beim Löschen (rechter Ast) von Daten sind jeweils zwei elementare Schritte nötig: die Eintragung muss in das TreeView und in die Tabelle vorgenommen werden. Die Reihenfolge spielt dabei grundsätzlich keine Rolle. Allerdings hat es sich gezeigt, dass es sinnvoll ist, zuerst die Speicherung in die Tabelle vorzunehmen, bevor das TreeView angepasst wird. Eventuelle Probleme (z.B. beim Speichern), die einen Abbruch des Vorgangs nötig machen, treten auf bevor das TreeView angepasst wird und können so frühzeitig abgefangen werden - etwa durch Fehlerbehandlung oder Fehlermeldung. Meist entfällt so das Rückgängig machen der Aktion, was z.B. beim Löschen relativ aufwendig ist.

Eine jeweilige „Basis“-Funktion koordiniert dabei diverse „Unter“-Funktionen, die ihrerseits eine bestimmte Teilaufgaben erledigen. Dadurch muss beim Klick auf eine Schaltfläche nur eine einzelne Funktion aufgerufen werden. Die Bereitstellung der jeweiligen Parameter bzw. eventuelle Modifikationen für nachgelagerte Funktionen übernimmt die Basis-Funktion. Außerdem werden einige Parameter erst durch den Aufruf einer vorgelagerten Funktion erzeugt.
Folgendes Beispiel soll das verdeutlichen: Ein neuer Eintrag soll angelegt werden: Dafür muss die Basis-Funktion [TreeView_AddNew] wissen welches TreeView-Steuerelement (Formular- und Steuerelementname) und welche Tabelle (die den Eintrag speichern soll) von der Aktion betroffen sind. Außerdem spielt die Beziehung des neuen Eintrags zum aktuell markierten Knoten (Node) eine entscheidende Rolle. Sie erinnern sich vielleicht, dass wir uns darauf geeinigt haben jeden Eintrag im TreeView einen eindeutigen Schlüssel für geben, den wir der auf die Node-Eigenschaft Key schreiben. Dieser soll mit einem Buchstaben beginnen (hier: A) und mit dem Index des Tabelleneintrages enden. Der Index existiert aber erst ab dem Zeitpunkt, an dem Datensatz angelegt wird. Aus diesem Grund ist es in unserem Beispiel nötig den Tabelleneintrag zuerst vorzunehmen, den neuen Index zu ermitteln und der nachgelagerten Funktion zum Anpassen des TreeView zur Verfügung zu stellen.

Abbildung 1: Aufbau des globalen Modul

Abbildung 1: Aufbau des globalen Modul

Einen neuen Eintrag vornehmen

In Abbildung 2 sehen Sie unser altes Formular, welches durch diverse Schaltflächen um einige Handling-Funktionen erweitert wurde. Um einen neuen Eintrag vorzunehmen, stehen zwei Optionen offen; einen Eintrag auf derselben Ebene, wie der zum Klick-Zeitpunkt markierte Knoten, oder einen untergeordneten Eintrag (Child). Die Quellcode für beide Aktionen weicht nur geringfügig voneinander ab und kann in den Funktionen leicht berücksichtigt werden. Denn in beiden Fällen müssen sie den übergeordneten Knoten (Parent) ermitteln. Wenn sie einen Child-Knoten einfügen möchten, ist der aktuell markierte Eintrag  der Parent-Knoten. Auch wenn sie einen Eintrag auf derselben Ebene vornehmen möchten, müssen Sie trotzdem ermitteln in welcher Ebene sie sich gerade. Gibt es keinen Parent-Knoten, dann ist entweder kein Knoten markiert oder sie befinden sich gerade auf der obersten Ebene.  Um diesen Fall abzufangen, muss eine Fehlerbehandlungsroutine implementiert werden, da der Zugriff auf einen nicht existierenden Parent-Knoten einen Fehler verursacht (Fehler-Nr. 91).

Select Case ...
   Case ...
   Case 91:
      v_ParentIndex = Null
      Resume Next
   Case ...
End Select

Ist ein Parent-Knoten vorhanden, wird die Key-Eigenschaft ausgelesen, da diese den Index des entsprechenden Datensatzes aus der Tabelle enthält. Dieser Index muss noch extrahiert werden:

v_ParentIndex = Right(v_ParentIndex, Len(v_ParentIndex) - 1)

Erst jetzt kann die Funktion zum Anlegen eines neuen Datensatzes in der Tabelle aufgerufen werden:

v_AddedIndex = TreeView_AddNew_Table(p_TableName, v_ParentIndex)

Diese Funktion braucht ihrerseits nur den Namen der betreffenden Tabelle und die Information für den Eintrag in das Feld [Gruppe#], d.h. welchem Datensatz der neue Eintag untergeordnet ist. Ganz wichtig hierbei ist das Ergebnis der Funktion. Der Rückgabewert stellt den Index des neuen Datensatzes dar. Nur wenn dieser ungleich 0 ist, war die Neuanlage erfolgreich.
Der Index (ungleich 0) wird anschließend an die nachgelagerte „Teil“-Funktion, zum Hinzufügen eines Knoten ins TreeView, übergeben:

TreeView_AddNew = TreeView_AddNew_Node(TreeView, v_AddedIndex, v_ParentIndex)

Das Ergebnis dieser Funktion, der Key des neuen Nodes, kann für weiter-führende Zwecke verwendet werden. In unserem Beispiel, um die Markierung auf den neu angefügten Knoten zu setzen, und um eventuell nötige Bildläufe (bzw. Ast aufklappen) durchzuführen.

Ähnlich wie beim Anlegen verhält sich das Vorgehen beim Löschen eines Datensatzes. Erst sollte man sicherstellen, dass die Löschung überhaupt erfolgreich war, bevor man den Eintrag aus dem TreeView entfernt (Listing 2).

Im Listing 3 sehen sie die Klick-Ereignis-Prozeduren der Schaltflächen des Formulars. Die beiden Prozeduren zum Anfügen eines Knoten zeigen außerdem, wie man den Rückgabewert (den Key) sinnvoll weiterverwenden kann, um z.B. nach der Aktualisierung des Formularinhalts ein Node-Klick-Ereignis zu simulieren, damit der neue Datensatz sofort im Formular angezeigt wird.  Anschließend wechselt das TreeView in den Editiermodus.

Me.Requery
Call TreeView_NodeClick(Me![TreeView].SelectedItem)
TreeView.StartLabelEdit

Das Betätigen der Eingabetaste nach der Änderung des Knoten löst zwei Ereignisse aus: BeforeLabelEdit und AfterLabelEdit. Diese Ereignisse können weiter Aktionen beinhalten.
Die LabelEdit-Eigenschaft (siehe Listing 1 des 2. Teils - Funktion TreeView_Initialize) des TreeView regelt dabei, ob der Editiermodus für den Knoten manuell [tvwManual] oder automatisch [tvwAutomatic], durch zwei einfache Mausklicks, ausgelöst werden muss bzw. kann.

Abbildung 2: neuen Einträge direkt im TreeView vornehmen

Abbildung 2: neuen Einträge direkt im TreeView vornehmen

Listing 1: Globale Funktionen zum Anfügen eines neuen Eintrags ins Treeview
'*** Basis-Funktion: neuen Eintrag vornehmen ***
'***********************************************
Public Function TreeView_AddNew(ByVal TreeView As Object, _
                                ByVal p_TableName As String, _
                       Optional ByVal p_Relation As Long = tvwLast) As String
On Error GoTo RunError
   Dim v_ParentIndex As Variant  '* Index des übergeordneten Nodes
   Dim v_AddedIndex  As Long     '* Index des angefügten Datensatzes

   Select Case p_Relation   '* ParentNode ermitteln,
      Case tvwLast   '* Key des übergeordnete Node ermitteln
         v_ParentIndex = TreeView.Nodes(TreeView.SelectedItem.Key).Parent.Key
      Case tvwChild   '* Key des aktuell markierten Nodes ermitteln.
         v_ParentIndex = TreeView.SelectedItem.Key
   End Select

   If Not IsNull(v_ParentIndex) Then
      '* ... dann im Key enthaltenen Index extrahieren,
      v_ParentIndex = Right(v_ParentIndex, Len(v_ParentIndex) - 1)
   End If

   '* Neuen Datensatz in Tabelle einfügen,
   v_AddedIndex = TreeView_AddNew_Table(p_TableName, v_ParentIndex)
   If v_AddedIndex = 0 Then GoTo RunError

   '* Neuen Datensatz im TreeView anfügen
   TreeView_AddNew = TreeView_AddNew_Node(TreeView, v_AddedIndex, v_ParentIndex)
   
   TreeView.Nodes(TreeView_AddNew).Selected = True    '* Markierung setzen
   TreeView.Nodes(TreeView_AddNew).EnsureVisible      '* Bildlauf durchführen.

RunError:
   Select Case Err.Number
   Case 0   '* kein Fehler
   Case 91: '* - nichts markiert oder kein übergeordneter Node (Parent) vorhanden
      v_ParentIndex = Null
      Resume Next
   Case Else
      MsgBox Err.Description, vbCritical, "No." & Err.Number
   End Select
End Function

'*** Sub-Funktion: neuen Eintrag in Tabelle vornehmen      ***
'*************************************************************
Public Function TreeView_AddNew_Table(ByVal p_TableName As String, _
                             Optional ByVal p_ParentIndex As Variant = Null) As Long
   Dim rstZiel As Recordset
   Dim v_Index As Long

   TreeView_AddNew_Table = 0

   Set dbs = CurrentDb()
   Set rstZiel = dbs.OpenRecordset(p_TableName, dbOpenDynaset)
      rstZiel.AddNew
         '* Index des neuen Datensatzes auslesen
         v_Index = rstZiel![Index#]
         rstZiel![Eintrag] = "Neuer Eintrag"
         rstZiel![Gruppe#] = p_ParentIndex
      rstZiel.Update

   rstZiel.Close
   dbs.Close

   TreeView_AddNew_Table = v_Index

   Set rstZiel = Nothing
   Set dbs = Nothing

End Function

'*** Sub-Funktion: Eintrag in TreeView vornehmen ***
'***************************************************
Public Function TreeView_AddNew_Node(ByVal TreeView As Object, _
                                     ByVal v_NewIndex As Long, _
                            Optional ByVal p_ParentIndex As Variant = Null, _
                            Optional ByVal p_Text As String = "Neuer Eintrag") As Variant

   TreeView_AddNew_Node = Null

   Set NodeX = TreeView.Nodes.Add(("A" + p_ParentIndex), tvwChild)
      NodeX.Key = CStr("A" & v_NewIndex)
      NodeX.Text = p_Text

   TreeView_AddNew_Node = NodeX.Key

End Function
Listing 2: Globale Funktionen zum Löschen eines Eintrags im Treeview
'*** Basis-Funktion: markierten Eintrag löschen ***
'**************************************************
Public Function TreeView_Delete(ByVal TreeView As Object, _
                                ByVal p_TableName As String, _
                       Optional ByVal p_NodeKey As Variant = Null) As Boolean
On Error GoTo RunError
   Dim v_DelIndex As Long    '* Index des zu löschenden Datensatzes
   Dim tof        As Boolean '* TrueOrFalse

   TreeView_Delete = False

   '* Wenn Node-Key nicht übergeben wurde
   If IsNull(p_NodeKey) Then
      DoCmd.Beep
      GoTo RunError
   Else   '* ... sonst im Key enthaltenen Index extrahieren,
      v_DelIndex = Right(p_NodeKey, Len(p_NodeKey) - 1)
   End If

   '* Eintrag zuerst aus Tabelle löschen
   tof = TreeView_Delete_Table(p_TableName, v_DelIndex)
   If tof = False Then GoTo RunError

   '* Löschung des Node aus dem TreeView
   tof = TreeView_Delete_Node(TreeView, p_NodeKey)

   TreeView_Delete = tof

RunError:
   If Err Then MsgBox Err.Description, vbCritical, "No." & Err.Number
End Function

'*** Sub-Funktion: Eintrag im TreeView löschen ***
'*************************************************
Public Function TreeView_Delete_Table(ByVal p_TableName As String, _
                                      ByVal p_Index As Long) As Boolean
On Error GoTo RunError
   Dim sql As String

   TreeView_Delete_Table = False

   sql = ""
   sql = sql & "DELETE"
   sql = sql & " [Index#]"
   sql = sql & " FROM"
   sql = sql & " [" & p_TableName & "]"
   sql = sql & " WHERE"
   sql = sql & " [Index#]=" & p_Index
   sql = sql & ";"

   DoCmd.SetWarnings False    '* Systemmeldungen deaktivieren
   DoCmd.RunSQL sql, True     '* SQL-Befehl ausführen

   TreeView_Delete_Table = True

RunError:
   DoCmd.SetWarnings False    '* Systemmeldungen wieder aktivieren
   If Err Then MsgBox Err.Description, vbCritical, "No." & Err.Number
End Function

'*** Sub-Funktion: Eintrag im TreeView löschen ***
'*************************************************
Public Function TreeView_Delete_Node(ByVal TreeView As Object, _
                            Optional ByVal p_NodeKey As Variant = Null) As Boolean

   TreeView_Delete_Node = False

   TreeView.Nodes.Remove p_NodeKey
   '*' oder: '*'  Call TreeView.Nodes.Remove(p_NodeKey)

   TreeView_Delete_Node = True

End Function
Listing 3: Ereignis-Prozeduren der Formular-Elemente (Schaltflächen, etc.)
'*** Schaltfläche: neuen untergeordneten Eintrag ***
'***************************************************
Private Sub btn_Node_AddNew_Child_Click()

   Call TreeView_AddNew(Me![TreeView, "tbl_Struktur",  tvwChild)
   Me.Requery    '* Daten aktualisieren

   '* Node-Click-Ereignis anstossen
   Call TreeView_NodeClick(Me![TreeView].SelectedItem)

   TreeView.StartLabelEdit '* Editiermodus

End Sub

'*** Schaltfläche: neuen Eintrag (gleicher Ebene) ***
'****************************************************
Private Sub btn_Node_AddNew_Last_Click()

   Call TreeView_AddNew(Me![TreeView], "tbl_Struktur, tvwLast)
   Me.Requery

   '* Node-Click-Ereignis anstossen
   Call TreeView_NodeClick(Me![TreeView].SelectedItem)

   TreeView.StartLabelEdit

End Sub

'*** Schaltfläche: Eintrag löschen ***
'*************************************
Private Sub btn_Node_Delete_Click()
   Dim tof As Boolean  '*True OR False

   '* Eintrag aus Tabelle und TreeView löschen
   tof = TreeView_Delete(Me![TreeView], _
                         "tbl_Struktur, _
                         Me![TreeView].SelectedItem.Key)

  If tof = True Then
     Me.Requery
     Call TreeView_NodeClick(Me![TreeView].SelectedItem)
   End If
End Sub

'*** Schaltfläche: aktualisieren ***
'***********************************
Private Sub btn_Treeview_Actualize_Click()
   Call Form_Load
End Sub

'*** Node nach Editierung speichern ***
'**************************************
Private Sub TreeView_AfterLabelEdit(Cancel As Integer, _
                                    NewString As String)
   Me![Eintrag] = NewString
   Me.Refresh

End Sub
Seitenanfang