Home > Artikel > Ausgabe 11/2013 > Kontextmenüs im TreeView, Teil I: Anlegen und Löschen

Kontextmenüs im TreeView, Teil I: Anlegen und Löschen

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Das TreeView-Steuerelement eignet sich perfekt zur Anzeige hierarchischer Daten. Allerdings hat es einen Nachteil: Im Gegensatz zu gebundenen Formularen kann man darin nicht mal eben einen neuen Datensatz anlegen, einen Datensatz löschen oder bearbeiten. Dazu muss man Hand in Hand mit einem weiteren Element wie etwa einem Unterformular zur Anzeige des aktuellen Datensatzes arbeiten. Aber wie legt man mal eben einen neuen Datensatz von einem TreeView-Steuerelement aus an oder löscht ein Element? Dies gelingt am einfachsten per Kontextmenü. Dieser Artikel liefert das nötige Know-how und einige Beispiele.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1311_TreeView.mdb.

Beispielformular

Als Beispielformular verwenden wir das im Artikel TreeView und Unterformulare aufgebaute Formular, das wir kopiert und frmTreeViewKontextmenue genannt haben.

Damit wir über das TreeView-Steueelement leicht neue Kunden hinzufügen können, haben wir es etwas umgestaltet: Statt wie zuvor alle Kunden in der ersten Ebene des TreeView-Steuerelements anzuzeigen, haben wir einen Root-Knoten namens Kunden angelegt und alle Kunden-Elemente unterhalb dieses Knotens hinzugefügt. Dazu waren die in Listing 1 dargestellten Änderungen nötig. Dort fügen wir zunächst mit der Add-Methode das Root-Element mit dem Key-Wert r0 und dem Text Kunden hinzu. Dieses wird mit Expanded = True aufgeklappt. Die beim Durchlaufen der Tabelle tblKunden hinzugefügten Elemente legt die Prozedur dann unterhalb des Elements mit dem Key-Wert r0 an. Das Ergebnis sieht schließlich wie in Bild 1 aus.

Hinzufügen eines Root-Knotens zum TreeView-Steuerelement

Bild 1: Hinzufügen eines Root-Knotens zum TreeView-Steuerelement

Element löschen

Als Erstes wollen wir einen Kontextmenü-Eintrag hinzufügen, mit dem der Benutzer eines der Kunden-Elemente löschen kann. Das Kontextmenü soll beim Anklicken des zu löschenden Elements angezeigt werden und einen Eintrag namens Löschen anbieten. Um das Herunterdrücken der rechtem Maustaste abzufangen, verwenden wir die Ereignisprozedur, die durch das Ereignis MouseDown ausgelöst wird. Für ActiveX-Steuerelemente legen Sie eine solche Ereignisprozedur nicht über die entsprechende Eigenschaft im Eigenschaftsfenster an, sondern direkt im VBA-Fenster.

Für das TreeView-Steuerelement liegt bereits eine Objektvariable namens m_TreeView vor, die mit dem Schlüsselwort WithEvents deklariert wurde und somit auch Ereignisprozeduren ausführt.

Öffnen Sie also das Klassenmodul Form_frmTreeviewKontextmenue und wählen Sie dann erst im linken Kombinationsfeld den Eintrag m_TreeView und im rechten den Eintrag MouseDown aus (siehe Bild 2).

Anlegen der Ereignisprozedur MouseDown

Bild 2: Anlegen der Ereignisprozedur MouseDown

Die auf diese Weise erstellte Ereignisprozedur m_TreeView_MouseDown liefert mit dem Parameter Button einen Wert, der über die betätigte Maustaste Auskunft gibt. Diesen können Sie über die beiden Konstanten acLeftButton oder acRightButton prüfen. In diesem Fall interessiert uns, ob der Benutzer die rechte Maustaste gedrückt hat. Wir können die Ereignisprozedur also mit einer Bedingung wie der folgenden anreichern:

Select Case Button

     Case acRightButton

         'Kontextmenü anzeigen

End Select

Fehlt nur noch der Code, der ermittelt, welches Element der Benutzer angeklickt hat und der das passende Kontextmenü zusammenstellt. Hat der Benutzer tatsächlich mit der rechten Maustaste auf das Treeview-Steuerelement geklickt, können verschiedene Fälle eintreten:

  • Er hat mit einer anderen Maustaste als der rechten geklickt.
  • Er hat mit der rechten Maustaste ein Element im TreeView-Steuerelement angeklickt.
  • Er hat mit der rechten Maustaste auf einen leeren Bereich im TreeView-Steuerelement geklickt.

Wenn der Benutzer kein Element getroffen hat, soll nichts geschehen. Aber wie prüfen wir das? Indem wir die Funktion HitTest des TreeView-Steuerelements nutzen. Diese erwartet die Koordinaten für den Punkt, den der Benutzer angeklickt hat. Diese liefert die Ereignisprozedur MouseDown ebenfalls, und zwar mit den Parametern x und y.

Da wir Informationen von dem angeklickten Node-Element benötigen, in diesem Fall den Wert der Key-Eigenschaft, speichern wir den Verweis auf das Node-Element in einer Variablen zwischen.

Diese deklarieren wir vorher wie folgt:

Dim objNode As MSComctlLib.Node

Dem Key-Wert entnehmen wir zwei Informationen: den als erstes Zeichen angegebenen Buchstaben, der den Typ des Elements liefert (zum Beispiel r für Root, k für Kunde, b für Bestellung oder p für Bestellposition) und die folgende Zahlenkombination, die dem Primärschlüsselwert des Datensatzes in der passenden Tabelle entspricht.

Diese Informationen sollen ebenfalls gespeichert werden, und zwar in den folgenden Variablen:

Dim strTyp As String

Dim lngID As Long

Die folgenden Anweisungen ermitteln zunächst das angeklickte Element:

Set objNode = m_Treeview.HitTest(x, y)

Da HitTest den Wert Nothing übergibt, wenn kein Element angeklickt wurde, sondern ein leerer Bereich im TreeView-Steuerelement, prüfen wir vor den weiteren Schritten, ob objNode überhaupt mit einem Verweis auf ein angeklicktes Element gefüllt wurde und lesen dann die gewünschten Informationen aus:

If Not objNode Is Nothing Then

     strTyp = Left(objNode.Key, 1)

     lngID = Mid(objNode.Key, 2)

End If

Damit ausgestattet können wir eine Select Case-Bedingung anlegen, die den Wert der Variablen strTyp prüft. In unserem Beispiel enthält strTyp einen der Werte r, k, b oder p. Um die Übersichtlichkeit zu erhalten, rufen wir für jeden Wert eine eigene Prozedur zum Anzeigen des Kontextmenüs auf.

Diese heißen KontextmenueRootAnzeigen, KontextmenueKundenAnzeigen und so weiter. Alle Prozeduren außer das für das Root-Element nehmen den Primärschlüsselwert des angeklickten Elements als Parameter entgegen. Die vollständige Ereignisprozedur m_TreeView_MouseDown finden Sie in Listing 2.

Private Sub m_Treeview_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As stdole.OLE_XPOS_PIXELS, _

         ByVal y As stdole.OLE_YPOS_PIXELS)

     Dim strTyp As String

     Dim lngID As Long

     Dim objNode As MSComctlLib.Node

     Select Case Button

         Case acRightButton

             Set objNode = m_Treeview.HitTest(x, y)

             If Not objNode Is Nothing Then

                 strTyp = Left(objNode.Key, 1)

                 lngID = Mid(objNode.Key, 2)

                 Select Case strTyp

                     Case "k"

                         KontextmenueKundeAnzeigen(lngID)

                     Case "r"

                         KontextmenueRootAnzeigen

                     Case "b"

                         KontextmenueBestellungAnzeigen(lngID)

                     Case "p"

                         KontextmenuePositionAnzeigen(lngID)

                 End Select

             End If

     End Select

End Sub

Listing 2: Aufrufen der Prozeduren zum Anzeigen der verschiedenen Kontextmenüs

Wir wollen mit dem Kontextmenü-Eintrag zum Löschen des aktuell angeklickten Kunden beginnen. Die entsprechende Prozedur heißt KontextmenueKundeAnzeigen und sieht wie in Listing 3 aus. Die Prozedur deklariert eine CommandBar- und eine CommandBarButton-Variable. Dann löscht sie ein eventuell bereits vorhandenes Kontextmenü namens TreeView_Kunde.

Private Sub KontextmenueKundeAnzeigen(lngID As Long)

     Dim cbr As Office.CommandBar

     Dim cbc As Office.CommandBarButton

     On Error Resume Next

     CommandBars("TreeView_Kunde").Delete

     On Error GoTo 0

     Set cbr = CommandBars.Add("TreeView_Kunde", msoBarPopup, False, True)

     With cbr

         Set cbc = .Controls.Add(msoControlButton)

         With cbc

             .Caption = "Kunde löschen"

             .OnAction = "=KundeLoeschen(" & lngID & ")"

         End With

         .ShowPopup

     End With

End Sub

Listing 3: Anzeigen des Kontextmenüs für einen Kunden, hier zunächst mit der Mögilchkeit zum Löschen des Kunden

Da dies einen Fehler auslösen kann, deaktiviert die Prozedur die integrierte Fehlerbehandlung für die Durchführung dieser Anweisung.

Anschließend fügt sie mit der Add-Methode der CommandBars-Auflistung ein neues Kontextmenü namens TreeView_Kunde hinzu. Dabei gibt sie mit dem ersten Parameter den Namen und mit dem zweiten den Menütyp an. Der dritte Parameter legt fest, dass es sich nicht um eine Menüleiste handelt und der vierte sorgt dafür, dass das Kontextmenü nur temporär erstellt wird – damit wird es beim Schließen von Access gelöscht.

Für das mit cbr referenzierte Kontextmenü fügt die Prozedur mit der Add-Methode der Controls-Auflistung einen neuen Kontextmenü-Eintrag hinzu. Der Typ wird mit dem Wert msoControlButton festgelegt. Anschließend stellt die Prozedur noch die beiden Eigenschaften Caption (Beschriftung) und OnAction ein. Letztere erwartet den Aufruf der beim Auswählen des Kontextmenü-Eintrags auszuführenden VBA-Funktion. Dieser soll gleich den Primärschlüsselwert des angeklickten TreeView-Elements übergeben, daher wird der Aufruf aus =KundeLoeschen, einer öffnenden Klammer, dem Primärschlüsselwert und einer schließenden Klammer zusammengesetzt – also zum Beispiel als =KundeLoeschen(123).

Die ShowPopup-Methode des CommandBar-Objekts zeigt das soeben erstellte Kontextmenü schließlich an (siehe Bild 3).

Anzeigen des Kontextmenüs für einen Kunden

Bild 3: Anzeigen des Kontextmenüs für einen Kunden

Kunden löschen

Fehlt noch die durch das Betätigen des Kontextmenü-Eintrags ausgelöste Löschen des Kunden. Damit ist es allerdings nicht getan: Sie müssen ja nicht nur den Kundendatensatz aus der Tabelle tblKunden löschen, sondern auch noch weitere Aktionen einleiten:

  • Entfernen des Eintrags aus dem TreeView-Steuerelement
  • Setzen des Fokus auf einen anderen Eintrag im TreeView
  • Aktualisieren des Unterformulars für den neu ausgewählten Eintrag

Dies alles erledigt die Prozedur aus Listing 4. Sie fragt erst mit einem Meldungsfenster ab, ob der Datensatz tatsächlich gelöscht werden soll. Falls ja, entfernt die Prozedur zunächst den Datensatz aus der Tabelle tblKunden, dessen Primärschlüsselfeld den Wert der Variablen lngKunde­ID enthält. Dann versucht die Prozedur nacheinander, den vorherigen oder folgenden Eintrag auf der gleichen Ebene des TreeView-Steuerelements mit der Variablen objNode zu referenzieren. Ist beides nicht möglich, referenziert sie das übergeordnete Element mit dem Key-Wert r0 und leert das Unterformular-Steuerelement.

Public Function KundeLoeschen(lngID As Long)

     Dim db As DAO.Database

     Dim objNode As MSComctlLib.Node

     If MsgBox("Soll der Kunde mit der Kundennummer " & lngID _

             & " wirklich gelöscht werden?", vbYesNo + vbExclamation, _

             "Kunde löschen") = vbYes Then

         Set db = CurrentDb

         db.Execute "DELETE FROM tblKunden WHERE KundeID = " & lngID

         Set objNode = m_Treeview.Nodes("k" & lngID).Previous

         If objNode Is Nothing Then

             Set objNode = m_Treeview.Nodes("k" & lngID).Next

         End If

         If objNode Is Nothing Then

             Set objNode = m_Treeview.Nodes("r0")

             Me!sfm.SourceObject = ""

         Else

             m_Treeview_NodeClick objNode

             m_Treeview.SelectedItem = objNode

         End If

         m_Treeview.Nodes.Remove "k" & lngID

     End If

End Function

Listing 4: Löschen eines Kunden per Kontextmenü

Wenn objNode jedoch gefüllt werden konnte, ruft die Prozedur die Ereignisprozedur m_TreeView_NodeClick auf und übergibt den Verweis auf das zu markierende Node-Element. Die Prozedur übernimmt alle notwendigen Schritte: Sie stellt das im Unterformular-Steuerelement sfm anzuzeigende Unterformular ein und filtert den gewünschten Datensatz.

Neuen Kunden anlegen

Nun fügen wir dem Root-Element des TreeView-Steuerelements einen Kontextmenü-Eintrag hinzu, der das Anlegen eines neuen Kunden ermöglicht. Auch dies hört sich leicht an – man braucht ja nur einen neuen Datensatz im Kunden-Unterformular einzutragen! In der Tat sollten aber eigentlich gleich zwei Schritte erfolgen: Der Kunde soll in einem leeren, neuen Datensatz im Unterformular angelegt werden können. Gleichzeitig muss aber auch ein neuer Eintrag im TreeView-Steuerelement her. Aber welchen Text soll dieser anzeigen, wenn der Benutzer noch gar keinen Kundennamen im Unterformular eingetragen hat? Wir gehen also am besten einen kleinen Kompromiss ein: Wir zeigen erst das Unterformular zum Eingeben eines neuen Kunden an und fügen den neuen Kunden erst nach dem Speichern zum TreeView-Steuerelement hinzu.

Dazu müssen wir den Zeitpunkt abfangen, zu dem der Benutzer den neuen Kunden speichert. Eine solche Ereignisprozedur würde man normalerweise im Klassenmodul des Unterformulars unterbringen, und zwar für das Ereignis Nach Aktualisierung. Dazu müsste das Unterformular aber auf das TreeView-Steuerelement im Hauptformular zugreifen. Eine solche Abhängigkeit ist nicht gut, denn das Unterformular sollte im besten Fall überhaupt nichts vom Hauptformular wissen. Deshalb legen wir diese Ereignisprozedur im Hauptformular an. Dazu benötigen wir eine Objektvariable, die das Unterformular referenziert und mit dem Schlüsselwort WithEvents deklariert wurde. Die folgende Deklarationszeile kommt dementsprechend in das Klassenmodul Form_frmTreeViewKontextmenue:

Dim WithEvents objsfmKunde As Form

Im Moment des Aufrufs des Kontextmenü-Eintrags Neuer Kunde soll das Unterformular sfmKunden angezeigt werden und einen neuen, leeren Kundendatensatz liefern. Dies erledigt die Funktion NeuerKunde (siehe Listing 5). Den neuen Datensatz fügt die Funktion mit der AddNew-Methode des Recordset-Objekts des Formulars hinzu. Dann referenziert die Funktion das Unterformular mit der soeben deklarierten Variablen objsfmKunden. Für dieses legt die Funktion außerdem fest, dass im aktuellen Klassenmodul das Ereignis AfterUpdate gelauscht werden soll.

Public Function NeuerKunde()

     Me!sfm.SourceObject = "sfmKunden"

     Me!sfm.Form.Recordset.AddNew

     Set objsfmKunde = Me!sfm.Form

     With objsfmKunde

         .AfterUpdate = "[Event Procedure]"

     End With

End Function

Listing 5: Hinzufügen eines neuen Kunden

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!