Möchtest Du den gesamten Artikel lesen? Und vielleicht sogar den Artikel im PDF-Format und die Beispieldateien herunterladen? Dann hole Dir den Artikel gleich hier - völlig kostenlos!
Im Artikel “UstIdNr, IBAN und Co. per Kontextmenü” (www.vbentwickler.de/480) haben wir gezeigt, wie wir die Windows-Kontextmenüs erweitern können. In diesem Fall haben wir Daten, die man gegebenenfalls nicht alle im Kopf hat, aber regelmäßig benötigt, einfach über ein Kontextmenü in die Zwischenablage einfügen kann, um diese dann an der gewünschten Stelle beispielsweise in einem Bestellformular einträgt. Um das Hinzufügen und Aktualisieren dieser Einträge weiter zu vereinfachen und die zahlreichen Handgriffe zu ersparen, zeigen wir im vorliegenden Artikel eine Access-Lösung, mit der wir die Kontextmenü-Befehle mit einem einfachen TreeView verwalten und erweitern können.
Dazu wollen wir die Struktur der benutzerdefinierten Kontextmenü-Einträge im Windows-Menü in einem TreeView-Steuerelement abbilden.
Darin können wir Ordner, Unterordner und die enthaltenen Befehle verwalten. Nachdem wir diese Elemente angelegt haben, wollen wir diese per Mausklick direkt in die Registry schreiben können.
Datenmodell für die Anwendung
Wir wollen die Daten, wir wie sie zum Windows-Kontextmenü hinzufügen wollen, strukturiert in Tabellen ablegen, damit wir sie dynamisch ergänzen und bearbeiten können. Dazu benötigen wir zwei Tabellen:
- tblMenuFolders: Enthält die Ordner und die Unterordner.
- tblMenuEntries: Enthält die Einträge zu den jeweiligen Ordnern und Unterordnern.
Die Tabelle tblMenuFolders gestalten wir wie in Bild 1. Das Feld ParentMenuFolderID verknüpfen wir mit dem Feld MenuFolderID der gleichen Tabelle, damit wir die Zuordnung von Unterordnern zu Ordnern definieren können.
Bild 1: Tabelle zum Speichern der Menü-Ordner
Die zweite Tabelle tblMenuEntries nimmt die eigentlichen Einträge auf (siehe Bild 2). Sie nimmt die ID, die Beschriftung des Menü-Eintrags und den Text auf, der beim Betätigen des Menü-Eintrags in die Zwischenablage kopiert werden soll.
Bild 2: Tabelle zum Speichern der Menü-Einträge
Außerdem wird über das Feld ParentMenuFolderID die Zuordnung zu einem Ordner angegeben.
Im Datenmodell sehen wir die anzulegenden Beziehungen (siehe Bild 3). Die Tabelle tblMenuEntries ist über das Feld ParentMenuFolderID mit der Tabelle tblMenuFolders verknüpft. Die Tabelle tblMenuFolders ist außerdem über das Feld ParentMenuFolderID mit dem Feld MenuFolderID der gleichen Tabelle verknüpft. Für beide Beziehungen haben wir referenzielle Integrität mit Löschweitergabe definiert, damit beim Löschen eines übergeordneten Menüeintrags auch alle untergeordneten Elemente gelöscht werden.
Bild 3: Datenmodell der Anwendung
Aufbau des TreeView-Steuerelements
Wir fügen einem neuen Formular namens frmWindowsKontextmenue ein TreeView-Steuerelement und ein ImageList-Steuerelement hinzu. Diese nennen wir ctlTreeView und ctlImageList (siehe Bild 4).
Bild 4: Formular mit TreeView- und ImageList-Steuerelement
Damit können wir nun den Code zusammenstellen, der zum Anzeigen der Einträge aus den Tabellen tblMenuFolders und tblMenuEntries verwendet wird.
Die Prozedur soll beim Laden des Formulars ausgelöst werden und sieht wie in Listing 1 aus. Hier deklarieren wie zunächst eine modulweit erreichbare Variable für das TreeView-Steuerelement mit dem Schlüsselwort WithEvents:
Private objTreeView As MSComctlLib.TreeView Private Sub Form_Load() Dim db As dao.Database Dim rstImages As dao.Recordset Dim rstFolders As dao.Recordset Dim rstEntries As dao.Recordset Dim strKey As String Dim objNode As MSComctlLib.Node Dim objImageList As MSComctlLib.ImageList Set db = CurrentDb Set objImageList = Me.ctlImageList.Object objImageList.ImageHeight = 16 objImageList.ImageWidth = 16 Set rstImages = dbc.OpenRecordset("SELECT * FROM MSysResources WHERE type = ''img''", dbOpenDynaset) Do While Not rstImages.EOF amvAddIconToImageListFromResources objImageList, rstImages!ID, rstImages!Name rstImages.MoveNext Loop Set objTreeView = Me.ctlTreeview.Object objTreeView.Nodes.Clear Set objTreeView.ImageList = objImageList Set rstFolders = db.OpenRecordset("SELECT * FROM tblMenuFolders WHERE ParentMenuFolderID IS NULL", dbOpenDynaset) Do While Not rstFolders.EOF strKey = "f" & rstFolders!MenuFolderID Set objNode = objTreeView.Nodes.Add(, , strKey, rstFolders!MenuFolderCaption, "folder") objNode.Expanded = True Call AddSubfolders(db, rstFolders!MenuFolderID, strKey) Call AddEntries(db, rstFolders!MenuFolderID, strKey) rstFolders.MoveNext Loop Set rstEntries = db.OpenRecordset("SELECT * FROM tblMenuEntries WHERE ParentMenuFolderID IS NULL", dbOpenDynaset) Do While Not rstEntries.EOF strKey = "e" & rstEntries!MenuEntryID objTreeView.Nodes.Add , , strKey, rstEntries!MenuEntryCaption, "breakpoint" rstEntries.MoveNext Loop End Sub
Listing 1: Prozedur zum Initialisieren des TreeView- und des ImageList-Steuerelements
Private objTreeView As MSComctlLib.TreeView
Die Prozedur Form_Load füllt zunächst das ImageList-Steuerelement mit allen Bildern, die wir in der Tabelle MSysResources finden. Die benötigten Icons haben wir zuvor hinzugefügt, in dem wir ein Formular in der Entwurfsansicht geöffnet und die gewünschten Icons mit dem Befehl Bild einfügen über das Ribbon hinzugefügt haben. Diese Bilder werden automatisch in der Tabelle MSysResources gespeichert (siehe Bild 5).
Bild 5: Bilder für die TreeView-Einträge aus der Tabelle MSysResources
Diese Bilder müssen wir nun zum ImageList-Steuerelement hinzufügen, damit wir sie für die TreeView-Einträge verwenden können. Das erledigen die ersten Anweisungen der Prozedur Form_Load. Hier referenzieren wir die Eigenschaft Object des ImageList-Steuerelements mit der Variablen objImageList. Dann stellen wir Höhe und Breite der Bilder jeweils auf 16 ein.
Schließlich öffnen wir ein Recordset auf Basis der Tabelle MSysResources für alle Einträge, deren Typ img lautet. Diese durchlaufen wir ein einer Do While-Schleife und rufen jeweils die Prozedur amvAddIconToImageList auf. Diese fügt die einzelnen Bilder aus der Tabelle in das ImageList-Steuerelement ein. Die Prozedur amvAddIconToImageList und ihre Hilfsfunktionen wollen wir aus Platzgründen an dieser Stelle nicht erläutern.
Indem wir die Bilder dynamisch einlesen, können wir diese auch einmal ersetzen, ohne dass wir umständlich am Entwurf des Steuerelements operieren müssen.
Nun referenzieren wir das TreeView-Steuerelement mit der Variablen objTreeView, die wir bereits weiter oben deklariert haben.
Wir leeren eventuell noch vorhandene Nodes mit der Clear-Methode und weisen diesem Steuerelement das ImageList-Steuerelement als Quelle für die Icons zu.
Dann erstellen wir ein neues Recordset auf Basis der Einträge der Tabelle tblMenuFolders, aber nur für die Einträge, die keinen übergeordneten Eintrag haben. Diese durchlaufen wir in einer weiteren Do While-Schleife.
In dieser Schleife stellen wir einen eindeutigen Schlüssel für jedes Node-Element im TreeView-Steuerelement zusammen, das für Ordner aus dem Buchstaben f und der ID des jeweiligen Datensatzes besteht (zum Beispiel f1).
Danach fügen wir das neue Element mit der Add-Methode der Nodes-Auflistung des TreeView-Steuerelements hinzu.
Dabei geben wir den Key an, den Namen aus dem Feld MenuFolderCaption und das zu verwendende Bild (folder). Außerdem stellen wir die Eigenschaft Expanded für das neue Element auf True ein, damit untergeordnete Einträge direkt angezeigt werden.
Schließlich rufen wir zwei weitere Prozeduren auf. AddSubfolders fügt die untergeordneten Ordner hinzu und AddEntries die eigentlichen Einträge.
Beiden übergeben wir einen Verweis auf das Database-Objekt, die ID des übergeordneten Ordners und den Key, den wir für das übergeordnete Element verwendet haben.
Da wir auch Elemente in der obersten Ebene anzeigen wollen, fügen wir noch Code hinzu, der die Einträge der Tabelle tblMenuEntries durchläuft, die im Feld ParentMenuFolderID den Wert NULL enthalten. Für diese legen wir als Key den Buchstaben e (für Entry) sowie den anzuzeigenden Text fest.
Unterordner zum TreeView hinzufügen
Die Prozedur AddSubfolders (siehe Listing 2) soll die Unterordner hinzufügen, die dem mit dem Parameter lngParentFolderID entsprechenden Datensatz der Tabelle tblMenuFolders untergeordnete sind.
Private Sub AddSubfolders(db As dao.Database, lngParentFolderID As Long, strKey As String) Dim rstSubfolders As dao.Recordset Dim strSubkey As String Dim objNode As MSComctlLib.Node Set rstSubfolders = db.OpenRecordset("SELECT * FROM tblMenuFolders WHERE ParentMenuFolderID = " _ & lngParentFolderID, dbOpenDynaset) Do While Not rstSubfolders.EOF strSubkey = "f" & rstSubfolders!MenuFolderID Set objNode = objTreeView.Nodes.Add(strKey, tvwChild, strSubkey, rstSubfolders!MenuFolderCaption, "folder") objNode.Expanded = True Call AddSubfolders(db, rstSubfolders!MenuFolderID, strSubkey) Call AddEntries(db, rstSubfolders!MenuFolderID, strSubkey) rstSubfolders.MoveNext Loop End Sub
Listing 2: Prozedur zum Einfügen der untergeordneten Ordner
Dazu öffnen wir ein Recordset, das genau diese Datensätze aus der Tabelle tblMenuFolders liefert. In der folgenden Do While-Schleife durchlaufen wir diese Datensätze und stellen zunächst in der Variablen strSubkey den Key für die zu erstellenden Untereinträge zusammen. Diese bestehen wiederum aus dem Buchstaben f und dem Primärschlüsselwert des neuen Elements, also beispielsweise f2.
Dann erstellen wir ein neues Node-Objekt unterhalb des übergeordneten Objekts. Das übergeordnete Element referenzieren wir mit dem Key dieses Elements und geben mit dem zweiten Parameter tvwChild an, dass das neue Element unterhalb dieses Elements angeordnet werden soll. Der dritte Parameter nimmt den neuen Key auf und der vierte den anzuzeigenden Text. Mit dem fünften Parameter übergeben wir wieder das Icon folder. Auch für dieses Element stellen wir wieder die Eigenschaft Expanded auf True ein.
Danach rufen wir erneut die Prozedur AddSubfolders auf, falls noch weitere Unterordner vorhanden sind.
Außerdem verwenden wir die Prozedur AddEntries, um die enthaltenen Einträge hinzuzufügen.
Elemente zum TreeView hinzufügen
Die Prozedur AddEntries (siehe Listing 3) fügt die eigentlichen Einträge hinzu. Sie erwartet die gleichen Einträge wie AddSubfolders. Hier definieren wir ein Recordset, dass alle Datensätze der Tabelle tblMenuEntries enthält, die dem mit lngParentFolderID übergebenen Ordner zugeordnet sind.
Private Sub AddEntries(db As dao.Database, lngParentFolderID As Long, strKey As String) Dim rstEntries As dao.Recordset Dim strSubkey As String Dim objNode As MSComctlLib.Node Set rstEntries = db.OpenRecordset("SELECT * FROM tblMenuEntries WHERE ParentMenuFolderID = " _ & lngParentFolderID, dbOpenDynaset) Do While Not rstEntries.EOF strSubkey = "e" & rstEntries!MenuEntryID Set objNode = objTreeView.Nodes.Add(strKey, tvwChild, strSubkey, rstEntries!MenuEntryCaption, "breakpoint") rstEntries.MoveNext Loop End Sub
Listing 3: Prozedur zum Einfügen der untergeordneten Einträge
Für jeden Datensatz dieses Recordsets fügen wir wieder ein Element zum TreeView-Steuerelement hinzu, wobei wir ähnlich wie in der zuvor beschriebenen Prozedur vorgehen.
Testen des TreeView-Steuerelements
Nachdem wir manuell einige Einträge zu den Tabellen hinzufügt haben, werden diese beim Öffnen des Formulars frmWindowsKontextmenue wie in Bild 6 dargestellt.
Bild 6: TreeView-Steuerelement mit einigen Einträgen
Kontextmenüs zum Hinzufügen und Löschen von Einträgen anlegen
Wir wollen die Tabelleneinträge nicht immer direkt in die Tabellen eingeben, sondern diese über ein Kontextmenü hinzufügen. Der Hauptknoten Daten sollte beispielsweise die Einträge Neuer Ordner und Neues Element anzeigen. Für die Unterordner sollen die Einträge Neuer Ordner, Neues Element und Ordner löschen erscheinen. Schließlich sollen die Elemente im Kontextmenü den Befehl Element löschen anbieten.
Die Kontextmenüs zeigen wir an, wenn das Ereignis MouseDown des TreeView-Steuerelements ausgelöst wird (siehe Listing 4). Hier prüfen wir, ob die rechte Maustaste zum Anklicken verwendet wurde.
Private Sub ctlTreeview_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Long, ByVal y As Long) Dim objNode As MSComctlLib.Node, lngID As Long, strChar As String, cbr As CommandBar Dim cbbNewFolder As CommandBarButton, cbbNewEntry As CommandBarButton, cbbDeleteFolder As CommandBarButton Dim cbbDeleteEntry As CommandBarButton If Button = 2 Then Set objNode = objTreeView.HitTest(x, y) If Not objNode Is Nothing Then strChar = Left(objNode.Key, 1) lngID = Mid(objNode.Key, 2) Select Case strChar Case "f" On Error Resume Next CommandBars("Folders").Delete On Error GoTo 0 Set cbr = CommandBars.Add("Folders", msoBarPopup, , True) Set cbbNewFolder = cbr.Controls.Add(msoControlButton, , , , True) cbbNewFolder.Caption = "Ordner hinzufügen" cbbNewFolder.OnAction = "=AddFolder(" & lngID & ")" Set cbbDeleteFolder = cbr.Controls.Add(msoControlButton, , , , True) cbbDeleteFolder.Caption = "Ordner löschen" cbbDeleteFolder.OnAction = "=DeleteFolder(" & lngID & ")" Set cbbNewEntry = cbr.Controls.Add(msoControlButton, , , , True) cbbNewEntry.Caption = "Element hinzufügen" cbbNewEntry.OnAction = "=AddEntry(" & lngID & ")" cbr.ShowPopup Case "e" On Error Resume Next CommandBars("Entries").Delete On Error GoTo 0 Set cbr = CommandBars.Add("Entries", msoBarPopup, , True) Set cbbDeleteEntry = cbr.Controls.Add(msoControlButton, , , , True) cbbDeleteEntry.Caption = "Element löschen" cbbDeleteEntry.OnAction = "=DeleteElement(" & lngID & ")" cbr.ShowPopup End Select Else On Error Resume Next CommandBars("Folders").Delete On Error GoTo 0 Set cbr = CommandBars.Add("Folders", msoBarPopup, , True) Set cbbNewFolder = cbr.Controls.Add(msoControlButton, , , , True) cbbNewFolder.Caption = "Ordner hinzufügen" cbbNewFolder.OnAction = "=AddFolder(0)" Set cbbNewEntry = cbr.Controls.Add(msoControlButton, , , , True) cbbNewEntry.Caption = "Element hinzufügen" cbbNewEntry.OnAction = "=AddEntry(0)" cbr.ShowPopup End If End If End Sub
Listing 4: Prozedur zum Anzeigen der Kontextmenüs
Danach holen wir mit den Koordinaten x und y aus den Parametern über die Funktion HitTest das Objekt, das angeklickt wurde. Ist objNode danach nicht leer, können wir das Kontextmenü für das entsprechende Element anzeigen.
Anderenfalls hat der Benutzer in den leeren Raum außerhalb der Elemente geklickt, was ein Kontextmenü mit den Befehlen Ordner hinzufügen und Element hinzufügen anzeigen soll.
Schauen wir uns erst einmal den Fall an, dass der Benutzer auf einen bestehenden Eintrag geklickt hat. In diesem Fall holen wir uns mit Zeichenkettenfunktionen aus der Eigenschaft Key des Elements den Buchstaben und die ID und schreiben diese in die Variablen strChar und lngID.
Im Falle des Buchstaben f hat der Benutzer einen Ordner angeklickt. Dann löschen wir ein eventuell bereits vorhandenes Kontextmenü namens Folders und legen dieses im nächsten Schritt erneut an.
Dann fügen wir eine Schaltfläche mit dem Titel Ordner hinzufügen hinzu und legen für seine Eigenschaft OnAction den Ausdruck zum Aufruf der Funktion OrdnerHinzufuegen fest, wobei wir gleich die ID für den übergeordneten Ordner hinzufügen.
Der Ausdruck sieht zum Beispiel wie folgt aus:
=OrdnerHinzufuegen(1)
Auf die gleiche Weise fügen wir den Button mit der Beschriftung Ordner löschen und einem Befehl wie dem folgenden hinzu:
=DeleteFolder(1)
Schließlich folgt noch eine Schaltfläche mit dem Text Element hinzufügen, welche beispielsweise den folgenden Aufruf enthalten soll:
=AddEntry(1)
Schließlich zeigen wir das Kontextmenü mit der Methode ShowPopup an (siehe Bild 7).
Bild 7: Kontextmenü eines Ordners
Danach folgt das Kontextmenü, falls der Benutzer eines der Elemente angeklickt hat. Der Key dieser Elemente beginnt mit e, also prüfen wir in der Select Case-Bedingung auf diesen Buchstaben.
Hier löschen wir das gegebenenfalls bereits vorhandene Kontextmenü namens Entries und fügen es erneut hinzu. Dieses erhält nur einen Button mit der Beschriftung Element löschen und der folgenden Aktion:
=DeleteElement(1)
Danach zeigen wir das Kontextmenü mit ShowPopup an.
Schließlich folgt noch der Fall, dass der Benutzer außerhalb eines der Elemente geklickt hat.
Das Kontextmenü für diesen Fall soll die Befehle Ordner hinzufügen und Element hinzufügen anzeigen, welche Aufrufe wie die folgenden enthalten:
=AddFolder(0) =AddEntry(0)
Funktionen für die Kontextmenübefehle
Damit hätten wir auch die Kontextmenüs hinzugefügt und können nun die aufzurufenden Funktionen erstellen. Die nachfolgend vorgestellten Funktionen tragen wir in einem neuen Standardmodul namens mdlCommandbars ein und deklarieren mit dem Schlüsselwort Public, da diese sonst nicht aufrufbar wären.
Die Funktion zum Hinzufügen eines Ordners unterhalb eines bestehenden Ordners finden wir in Listing 5. Hier nehmen wir die ID des übergeordneten Ordners entgegen und fragen dann per InputBox den Namen des neuen Ordners ab. Liefert die InputBox keine leere Zeichenkette und ist lngID nicht 0, trägt die Prozedur den neuen Datensatz in die Tabelle tblMenuFolders ein.
Public Function AddFolder(lngID As Long) Dim db As DAO.Database Dim strFolder As String Dim objNode As MSComctlLib.Node Dim strKey As String Dim strSubkey As String Dim lngSubID As Long Set db = CurrentDb strFolder = InputBox("Name des Ordners:", "Neuer Unterordner") If Not Len(strFolder) = 0 Then If Not lngID = 0 Then db.Execute "INSERT INTO tblMenuFolders(MenuFolderCaption, ParentMenuFolderID) VALUES(''" & strFolder & "'', _ " & lngID & ")", dbFailOnError lngSubID = db.OpenRecordset("SELECT @@IDENTITY").Fields(0) strSubkey = "f" & lngSubID strKey = "f" & lngID Set objNode = Forms!frmWindowsKontextmenues!ctlTreeview.Object.Nodes.Add(strKey, tvwChild, _ strSubkey, strFolder, "folder") Else db.Execute "INSERT INTO tblMenuFolders(MenuFolderCaption) VALUES(''" & strFolder & "'')", dbFailOnError lngSubID = db.OpenRecordset("SELECT @@IDENTITY").Fields(0) strSubkey = "f" & lngSubID Set objNode = Forms!frmWindowsKontextmenues!ctlTreeview.Object.Nodes.Add(, , strSubkey, strFolder, "folder") End If objNode.Expanded = True objNode.Parent.Expanded = True End If End Function
Listing 5: Funktion zum Einfügen eines untergeordneten Ordners per Kontextmenü
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo