Ribbon für VBA – Automatische Lieferung

Mit dem letzten Schritt konnten wir ja ganz hervorragend das Ribbon aktualisieren lassen. Jetzt werden wir das noch wunderbar aufhübschen und die Aktualisierung automatisch ausführen lassen. Wären wir nicht in VBA, könnten wir das sehr schön mit dem Observer Pattern und einem Update Interface bewerkstelligen. Wir basteln aber praktisch was ganz ähnliches.

Klasse Klasse

Als erstes legen wir in unserem Projekt eine neue Klasse cApplication an (am Ende sage ich noch was dazu). In dieser Klasse kommt unser notwendiger Code um die Aktualisierung des Ribbons durchzuführen. Für den Anfang sehr übersichtlich.

Public WithEvents AppWord As Word.Application

Private Sub AppWord_WindowSelectionChange(ByVal Sel As Selection)
    ribbon.InvalidateRibbon
End Sub

Wir definieren uns ein neues Objekt als „die Anwendung Word selbst“ und damit wir auf bestimmte Ereignisse reagieren können, kommt der Zusatz WithEvents davor. Die eigentliche Routine soll immer dann ausgeführt werden, wenn die Auswahl in Word geändert wird (SelectionChange). Heißt stumpf: wenn man im Dokument rumklickt.

Autostart

Würde man das aber in diesem Zustand ausprobieren, passiert noch nix. Und zwar, weil Word beim Starten diese erstellte Klasse nicht benutzt. Deshalb kommt ein zweites Modul in unser Projekt „Sys„, in diesem wird der notwendige Code eingefügt.

Dim VbaCoreApplication As New cApplication

Sub AutoExec()
    Set VbaCoreApplication.AppWord = Word.Application
End Sub

AutoExec() ist eine integrierte Routine von VBA die immer beim Starten der Anwendung ausgeführt wird. Und in dieser übergeben wir unserer Klasse (bzw der Eigenschaft AppWord) die aktuelle Anwendung. Die Klasse selbst wird außerhalb der Routine angelegt.

Warum machen wir das nicht wie in anderen Sprachen, zB so: Dim VbaCoreApplication As New cApplication(Word.Application) ? Also das direkte Übergeben im Konstruktor aka Dependency Injection? Ganz einfach, weil VBA das nicht kann. Die dafür nötige Factory können wir wann anders mal basteln. 😉

Zurück zur Bastelei, das ganze mal speichern und schließen, dann Word neu starten und gucken ob es funktioniert.

Und wieder, yeah, Nummer 5 lebt. Der Toggle Button aktualisiert sich selbstständig wenn im Dokument rumgeklickt wird. Was für ein Fest an Funktionalität. 😁

Was sonst noch geschah

Was könnten wir denn noch mit einem Käffchen in der Hand sinnieren.

Ribbon Ribbon

Muss denn immer das gesamte Ribbon aktualisiert werden beim Rumklicken? Nein, natürlich nicht. Als Entwickler kann man ja die InvalidateControl <Id> Methode benutzen. Dann listet man eben im SelectionChange alle Ribbon Elemente auf die aktualisiert werden sollen. Oder man baut sich eine separate Methode mit der man das umsetzt, hier am Beispiel von IOX mal gezeigt:

Methode im Modul Ribbon

Public Sub InvalidateControlSet()
    iox.Ribbon.InvalidateControl ("ToggleEmbedFonts")
    iox.Ribbon.InvalidateControl ("ViewZoomPercentageUp")
    iox.Ribbon.InvalidateControl ("ViewZoomPercentageDown")
    iox.Ribbon.InvalidateControl ("ViewZoomPageRowsUp")
    iox.Ribbon.InvalidateControl ("ViewZoomPageRowsDown")
    iox.Ribbon.InvalidateControl ("ViewZoomPageColumnsUp")
    iox.Ribbon.InvalidateControl ("ViewZoomPageColumnsDown")
    iox.Ribbon.InvalidateControl ("ViewZoomPercentage")
    iox.Ribbon.InvalidateControl ("ViewZoomPageRows")
    iox.Ribbon.InvalidateControl ("ViewZoomPageColumns")
    iox.Ribbon.InvalidateControl ("ViewZoomAutoStoreInDocument")
End Sub
Methode im Klassenmodul cApplication

Private Sub AppWord_WindowSelectionChange(ByVal Sel As Selection)
    iox.Ribbon.InvalidateControlSet
End Sub

Und muss man immer SelectionChange benutzen? Vermutlich auch nicht, es gibt ja noch andere Ereignisse wie Dokument speichern, öffnen, drucken auf die man reagieren könnte. Dann kann man wieder gezielt Elemente im Ribbon aktualisieren lassen die halt dort sinnvoll zu aktualisieren sind.

Projektstruktur

Es gibt so tolle Nomenklaturen für andere Sprachen, wieso diese Namen wie Sys oder cApplication. Das ist recht einfach erklärt, weil Word manchmal böse rumspinnt wenn ein Modul genauso heißt wie eine integrierte Klasse. Word.Document zB oder System. Damit hatte ich viel Freude, deshalb vermeide ich diese Namen wenn nötig. 😨 Da sich Klassen auch nicht gezielt sauber aufrufen lassen habe ich zwangsweise die ungarische Notation benutzt um halbwegs Ordnung zu schaffen. Im IOX kann man sich das ja angucken und entweder zustimmen oder es doof finden. Ich hatte meine Gründe. 😁

Ansonsten kann ich noch dringend eine Benennung des Projekts empfehlen. Damit lassen sich halbwegs namespaces umsetzen, also Abgrenzungen von Programmierungen. Im VbaCore steht dort „TemplateProject„, das kann man gleichnamig auf VbaCore ändern. Damit lassen sich dann wie bei iox sauber alle Projektsachen wie VbaCore.ribbon.Invalidate aufrufen. Das wird vor allem dann interessant, wenn man zwei Projekte in Word einbindet und gleichnamige Funktionen und Module hat. Der Compiler kommt durcheinander und ruft womöglich das falsche Ribbon auf.

VBA bleibt aber komisch, ich kann dem Hirsch auch nicht immer folgen wenn sich irgendwas nicht so benimmt wie es soll. Ich hatte schon fehlerhafte Module die ich exportieren und neu anlegen musste weil irgendwas drin kaputt war.

In der nächsten Runde werden wir mal Templates angehen und die Frage klären, wie man Ribbon Elemente dynamisch mit einer Vorlage wie einem Brief einblendet. 😁