Lies in den Artikel rein und unten bekommst Du ein unschlagbares Angebot!
Wer den VBA-Editor mit eigenen Tools erweitern möchte, kommt um die Programmierung von COM-Add-Ins kaum herum – zumindest nicht, wenn er eine schicke Benutzeroberfläche und die Integration in Menüs, Symbolleisten und Kontextmenüs wünscht. In diesem Artikel zeigen wir daher, wie wir die Basis für ein solches COM-Add-In mit twinBASIC programmieren. Ausgehend davon kannst Du direkt loslegen und Deine gewünschten Funktionen einbauen – es sind nur jeweils wenige Anpassung notwendig. Wir erklären Schritt für Schritt, wie die Basis des COM-Add-Ins aufgebaut ist und welche Anpassungen Du vornehmen musst, um ein COM-Add-In für Deine eigenen Anwendungen zu bauen.
Start mit Vorlage
Für den Start kannst Du das Beispielprojekt VBECOMAddInBasis.twinproj aus dem Download zu diesem Artikel verwenden. Diese Datei öffnest Du in der Entwicklungsumgebung twinBASIC, deren jeweils aktuelle Fassung Du unter dem folgenden Link findest:
https://github.com/twinbasic/twinbasic/releases
Dort klickst Du auf Assets und kannst dann wie in Bild 1 die .zip-Datei mit der Entwicklungsumgebung herunterladen. Eine Installation ist nicht notwendig, es reicht das Entpacken im gewünschten Verzeichnis.
Bild 1: Download der neuesten twinBASIC-Version
Damit kannst Du direkt die Datei VBECOMAddInBasis.twinproj öffnen. Diese besteht aus verschiedenen Komponenten, die wir für eine eigene Lösung ändern müssen:
- Code des COM-Add-Ins im Modul MeinComAddIn.twin, das die eigentliche Funktionalität enthält
- Code des Moduls dllREgistration.twin, das den Code für die Registrierung des Tools in der Registry enthält
- Eigenschaften im Bereich Settings, zum Beispiel um den Projektnamen und Verweise anzupassen
Code der Hauptklasse
Die Hauptklasse enthält bereits alles, was zum Erstellen eines funktionsfähigen COM-Add-Ins für den VBA-Editor enthält.
Im Kopf sehen wir eine ClassId, die wir für die Registrierung benötigen. Diese müssen wir jeweils auf eine neue, auf dem jeweiligen System eindeutige GUID anpassen:
[ ClassId ("95FA1EA0-DF9F-4505-A22E-E7CFDCD189") ]
Darunter beginnt gleich die Klassendefinition. Den Namen der Klasse passen wir, genau wie den Namen des Moduls, auf den gleichen Wert an. Aktuell heißen die Klasse und die Moduldatei MeinComAddIn.
Die Klasse implementiert die Schnittstelle IDTExtensibility2:
Class MeinComAddIn ''auf eigenes Add-In anpassen
Implements IDTExtensibility2
Konstanten für das COM-Add-In
Bevor wir diese erläutern, werfen wir einen Blick auf die benötigten Konstanten und Variablen. Hier definieren wir zunächst zwei Konstanten. Die erste enthält den Namen des Hauptmenüpunktes für Dein COM-Add-In, der zweite den Namen des Befehls, der sich in diesem Menü befindet:
Private Const cStrMenu As String = "Mein Menü" Private Const cStrCommandButton As String = "Mein Befehl"
Der folgende Code wird dafür sorgen, dass diese beiden Konstanten beim Anlegen der Menübefehls und des Kontextmenü-Eintrags berücksichtigt werden. Der Menübefehl wird dann wie in Bild 2 auftauchen.
Bild 2: Hier soll der Menübefehl erscheinen.
Den Kontextmenü-Eintrag finden wir, wenn wir mit der rechten Maustaste in das Code-Fenster klicken (siehe Bild 3).
Bild 3: Der Kontextmenübefehl wird hier eingeblendet.
Variablen für das COM-Add-In
Für die Basisversion des COM-Add-Ins verwenden wir die folgenden Variablen:
- objVBE: Nimmt einen Verweis auf den Visual Basic Editor auf.
- objAddIn: Referenziert das Add-In.
- cbc: Referenziert das CommandBarButton-Objekt für den Button in der Menüleiste.
- cbcContext: Referenziert das CommandBarButton-Objekt für den Button in der Kontextmenü-Leiste.
- cbcEvents: Nimmt die Events für den Button in der Menüleiste auf.
- cbcEventsContext: Nimmt die Events für den Button in der Kontextmenü-Leiste auf.
Die Deklaration sieht wie folgt aus:
Private objVBE As VBIDE.VBE Private objAddIn As VBIDE.AddIn Private cbc As CommandBarButton Private cbcContext As CommandBarButton Private WithEvents cbcEvents As VBIDE.CommandBarEvents Private WithEvents cbcEventsContext As VBIDE.CommandBarEvents
Schließlich benötigen wir noch eine Variable, mit der wir die Information speichern, ob das Add-In geladen ist:
Private isConnected As Boolean
Beim Laden/Verbinden des COM-Add-Ins
Weiter oben haben wir geschrieben, dass die Klasse MeinComAddIn die Schnittstelle IDTExtensibility2 implementiert.
Das bedeutet, dass wir einerseits bestimmte Ereignisprozeduren implementieren müssen. Andererseits können wir aber auch sichergehen, dass diese Ereignisprozeduren ausgelöst werden, wenn der VBA-Editor für eine der Office-Anwendungen gestartet wird und unser COM-Add-In in der Registry eingetragen ist.
Die erste und wichtigste Prozedur, die dadurch aufgerufen wird, heißt OnConnection. Sie sieht wie folgt aus:
Sub OnConnection(ByVal Application As Object, _ ByVal ConnectMode As ext_ConnectMode, _ ByVal AddInInst As Object, _ ByRef custom As Variant()) _ Implements IDTExtensibility2.OnConnection Set objVBE = Application Set objAddIn = AddInInst isConnected = True CreateToolBar() End Sub
Die Prozedur hat vor allem eine Aufgabe: Einen Verweis auf die Klasse des VBA-Editors entgegenzunehmen, der mit dem Parameter Application geliefert wird, und diesen mit der Variablen objVBE zu referenzieren.
Diese Variable ist der Ausgangspunkt für jegliche Aktionen, die wir im VBA-Editor codegesteuert durch unser Add-In durchführen wollen.
Außerdem referenzieren wir noch die Add-In-Instanz selbst, stellen isConnected auf True ein und rufen die Prozedur CreateToolBar auf, die wiederum die Menü- und Kontextmenü-Leisten erstellt.
Schaltflächen zu Menü und Kontextmenü hinzufügen
In der Regel wollen wir durch COM-Add-Ins im VBA-Editor Operationen am Code durchführen. Deshalb gehen wir in dieser Basisversion davon aus, dass wir den gleichen Befehl einmal in der Menüleiste und einmal im Kontextmenü hinzufügen wollen. Das erledigen wir mit der Prozedur CreateToolBar aus Listing 1.
Private Sub CreateToolBar() Dim cbr As CommandBar Dim cbp As CommandBarPopup Dim cbpContext As CommandBarPopup ''Eintrag in Menüleiste anlegen Set cbr = objVBE.CommandBars("Menüleiste") On Error Resume Next Set cbp = cbr.Controls(cStrMenu) On Error GoTo 0 If cbp Is Nothing Then Set cbp = cbr.Controls.Add(msoControlPopup, Temporary:=True) With cbp .Move Before:=8 .Caption = cStrMenu Set cbb =.Controls.Add(msoControlButton, Temporary:=True) End With Else Set cbb = cbp.Controls.Add(msoControlButton, temporary:=True) End If With cbb .Caption = cStrCommandButton .BeginGroup = True Set cbbEvents = objVBE.Events.CommandBarEvents(cbb) End With ''Eintrag in Kontextmenü anlegen On Error Resume Next Set cbpContext = objVBE.CommandBars("Code Window").Controls(cStrMenu) On Error GoTo 0 If cbpContext Is Nothing Then Set cbpContext = objVBE.CommandBars("Code Window").Controls.Add(msoControlPopup, Temporary:=True) With cbpContext .Caption = cStrMenu End With End If Set cbbContext = cbpContext.Controls.Add(Temporary:=True) With cbbContext .Caption = cStrCommandButton .BeginGroup = True Set cbbEventsContext = objVBE.Events.CommandBarEvents(cbbContext) End With End Sub
Listing 1: Hinzufügen von Schaltflächen zu Menüleiste und Kontextmenü
Die Prozedur deklariert zunächst zwei Untermenü-Elemente namens cbp (für das Hauptmenü) und cbpContext (für das Kontextmenü) mit dem Typ CommandBarPopup.
Untermenü referenzieren oder zur Menüleiste hinzufügen
Dann versucht sie, ein eventuell bereits vorhandenes Untermenü mit dem Namen aus cStrMenu, in dieser Basisversion also Mein Menü, zu referenzieren. Warum das? Weil es sein kann, dass wir bereits mehrere andere COM-Add-Ins in einem Untermenü untergebracht haben, in dem wir die Funktionen all unserer COM-Add-Ins aufrufen wollen. Also versuchen wir bei deaktivierter Fehlerbehandlung, ein solches Untermenü zu referenzieren.
Direkt danach prüfen wir, ob dies erfolgreich war – dann wäre cbp gefüllt. Falls nicht, erstellen wir das Element mit der Add-Methode der Controls-Auflistung der Menüleiste neu und referenzieren es mit der Variablen cbp. Wir bewegen es mit der Move-Methode an die achte Stelle im Menü, also relativ weit nach rechts. Dies kannst Du individuell anpassen.
Außerdem stellen wir die Beschriftung über die Caption-Eigenschaft auf den Wert aus cStrMenu ein, in diesem Fall also Mein Menü.
Schaltfläche zum Untermenü der Menüleiste hinzufügen
Diesem fügen wir mit der Add-Methode der Controls-Auflistung eine neue Schaltfläche hinzu und legen für diese die Beschriftung aus der Konstanten cStrCommandButton fest, in diesem Fall Mein Befehl. Außerdem beginnen wir hier eine neue Gruppe und stellen die Variable cbcEvents auf die Events-Auflistung für die Schaltfläche ein. Dadurch können wir später für dieses Objekt die Ereignisse der Schaltflächen implementieren.
Untermenü und Schaltfläche zum Kontextmenü hinzufügen
Auf ähnliche Weise fügen wir das Untermenü und die Schaltfläche ein. Damit erhalten wir ebenfalls eine Objektvariable namens cbcEventsContext, mit der wir das Ereignis beim Anklicken der Schaltfläche implementieren können.
Weitere Methoden der IDTExtensibility2-Schnittstelle
Wir programmieren noch weitere Methoden der oben vorgestellten Schnittstelle. Die erste namens OnDisconnection ruft die Prozedur ShutdownAddin auf:
Sub OnDisconnection( _ ByVal RemoveMode As ext_DisconnectMode, _ ByRef custom As Variant()) _ Implements IDTExtensibility2.OnDisconnection ShutdownAddin() End Sub
Die zweite rufe diese ebenfalls auf:
Sub OnBeginShutdown(ByRef custom As Variant()) _ Implements IDTExtensibility2.OnBeginShutdown ShutdownAddin() End Sub
Die beiden übrigen Methoden haben keine Funktion, aber wir müssen immer alle Methoden einer Schnittstelle implementieren:
Sub OnAddInsUpdate(ByRef custom As Variant()) _ Implements IDTExtensibility2.OnAddInsUpdate End Sub Sub OnStartupComplete(ByRef custom As Variant()) _ Implements IDTExtensibility2.OnStartupComplete End Sub
Beim Herunterfahren des COM-Add-Ins
Gleich zwei der Prozeduren der Schnittstelle IDTExtensibility rufen die Prozedur ShutdownAddin auf. Diese Prozedur leert die Objektvariablen, damit diese nicht im Speicher verbleiben und auf irgendeine Weise das Entladen des COM-Add-Ins behindern können. Da wir diese Prozedur zur Sicherheit gleich von zwei Prozeduren der Schnittstelle aus aufrufen, setzen wir die Variable isConnected nach dem ersten Aufruf auf False, damit die Prozedur beim zweiten Aufruf direkt verlassen werden kann: