In VBA-Projekten von Access-Datenbanken (und in VBA im Allgemeinen) gibt es das Problem, dass das Auftreten von unbehandelten Laufzeitfehlern dazu führt, dass Objektvariablen geleert werden. Das ist insbesondere dann nachteilig, wenn Sie mit dem Ribbon arbeiten und dieses zwischendurch mit der Invalidate-Methode ungültig machen müssen, damit die Attribute mit get…-Prozeduren erneut eingelesen werden können. Der Aufruf von Invalidate führt dann unweigerlich zu einem Laufzeitfehler. Dieser Beitrag beschreibt das grundlegende Beispiel und liefert eine Lösung, mit der Sie sich keine Sorgen mehr um Objektvariablen machen müssen, die durch Laufzeitfehler geleert werden.
Leeren von Objektvariablen nach Laufzeitfehlern reproduzieren
Als Erstes schauen wir uns an, mit welchem Problem wir es überhaupt zu tun haben. Das Problem betrifft nicht nur die Variablen, die in Zusammenhang mit dem Ribbon erstellt werden. Allerdings haben Objektvariablen üblicherweise eine überschaubare Gültigkeitsdauer – sie werden in der Regel innerhalb einer Prozedur deklariert und verlieren ihre Gültigkeit nach dem Beenden der Prozedur auch wieder.
Grundsätzlich ist es ohnehin nicht empfehlenswert, mit global deklarierten Variablen zu arbeiten, da diese nicht nur von überall gelesen, sondern auch von überall geändert werden können. Dabei treten schnell Probleme auf, wenn man von mehreren Stellen aus auf solche Variablen zugreift und nicht genau weiß, was man da tut.
Spätestens, wenn mal jemand anderer die Anwendung übernimmt und anpasst und sich nicht bewusst ist, das er dort mit global deklarierten Variablen arbeitet, könnte es zu Problemen kommen.
Beispiel für Datenverlust nach Laufzeitfehlern
Schauen wir uns also zunächst einmal den grundsätzlichen Effekt an, um zu sehen, womit wir es zu tun haben. Dazu deklarieren wir in einem Standardmodul die folgende Variable:
Public db As DAO.Database
db ist nun öffentlich deklariert und kann von überall innerhalb der Anwendung gesetzt und gelesen werden. Wir setzen diese in einer kleinen Prozedur im gleichen Modul:
Public Sub DbSetzen() Set db = CurrentDb End Sub
Wir können nun im Direktbereich von Access über die Objektvariable db auf das referenzierte Objekt zugreifen und so beispielsweise den Pfad der aktuellen Anwendung ermitteln:
db.Name C:\Users\...\RibbonvariableFehlersicherMachen.accdb
Nun provozieren wir einen unbehandelten Laufzeitfehler, indem wir die folgende Prozedur auslösen:
Public Sub RaiseError() Debug.Print 1 / 0 End Sub
Dies ruft die Fehlermeldung aus Bild 1 hervor. Klicken wir hier auf Beenden, vervollständigen wir den unbehandelten Laufzeitfehler. Durch einen Klick auf Debuggen und gegebenenfalls Änderungen im Code könnten wir den Fehler behandeln, was wir aber an dieser Stelle nicht wollen. Stattdessen klicken wir schlicht auf Beenden.
Bild 1: Unbehandelter Laufzeitfehler
Dies führt zunächst einmal zu keiner sichtbaren Folge – davon abgesehen, dass die Prozedur an dieser Stelle einfach abgebrochen wird.
Es gibt jedoch noch einen weiteren Nebeneffekt: Die Variable db ist nun leer und zeigt nicht mehr auf das soeben referenzierte Objekt des Typs Database. Wie können wir das herausfinden Indem wir einfach nochmal versuchen, den Pfad der aktuellen Anwendung im Direktbereich auszugeben.
Dies führt nun zu dem Fehler aus Bild 2. Die Variable enthält also nicht mehr den Verweis auf das Database-Objekt.
Bild 2: Die Variable db ist geleert.
Wozu globale Ribbonvariablen
Doch zurück zur eigentlichen Problemstellung, die solche Variablen betrifft, die global deklariert sind und Verweise auf Ribbon-Definitionen speichern.
Warum machen wir das überhaupt Nun: Der Verweis auf eine Ribbon-Definition wird nur einmal beim Anwenden der Ribbon-Definition geliefert, und zwar mit der Prozedur, die Sie für das Ereignisattribut onLoad hinterlegen können. Diese enthält einen Parameter namens ribbon mit dem Datentyp IRibbonUI.
Damit wir später auf diese Variable zugreifen können, müssen wir diese als globale Variable speichern. Dazu verwenden wir die folgende Variable, die wir in einem Standardmodul beispielsweise namens mdlRibbons deklarieren:
Public objRibbon_Main As IRibbonUI
In der Ribbon-Definition legen wir für das Element CustomUI ein Attribut namens onLoad an, das den Namen der Prozedur enthält, die beim Laden der Ribbondefinition ausgeführt werden soll – außerdem hinterlegen wir für das button-Element btn noch ein Callbackattribut namens getEnabled, mit dem wir gleich prüfen können, ob die Invalidate-Methode des Ribbons funktioniert:
<xml version="1.0"> <customUI ... onLoad="OnLoad_Main"> <ribbon> <tabs> <tab id="tab" label="Beispieltab"> <group id="grp" label="Beispielgruppe"> <button id="btn" getEnabled="getEnabled" label="Beispielbutton"/> </group> </tab> </tabs> </ribbon> </customUI>
Die Prozedur für das Attribut onLoad enthält lediglich eine Anweisung, die den mit dem Parameter ribbon übergebenen Verweis in der Variablen objRibbon_Main speichert:
Sub onLoad_Main(ribbon As IRibbonUI) Set objRibbon_Main = ribbon End Sub
Außerdem hinterlegen wir im Modul mdlRibbons noch eine Variable für den Enabled-Zustand der Schaltfläche btn:
Public bolEnabled As Boolean
Für das Attribut getEnabled des button-Elements btn hinterlegen wir die folgende Prozedur, welche mit dem Parameter enabled den Wert der Variablen bolEnabled zurückgibt:
Sub getEnabled(control As IRibbonControl, ByRef enabled) enabled = bolEnabled End Sub