Ribbonvariable fehlerresistent machen

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.

Unbehandelter Laufzeitfehler

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.

Die Variable db ist geleert.

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

Testen, ob die Ribbon-Variable bei Fehler geleert wird

Wenn Sie die Ribbon-Definition wie hier beschrieben und in der Beispielanwendung umgesetzt angelegt haben, sollte nach einem Neustart der Anwendung das Tab mit der Beschriftung Beispieltab mit einer Gruppe Beispielgruppe und einer Schaltfläche Beispielbutton erscheinen (siehe Bild 3).

Das Beispielribbon in Aktion

Bild 3: Das Beispielribbon in Aktion

Die Schaltfläche ist nicht aktiviert, da die Variable bol-Enabled standardmäßig den Wert False hat und noch nicht auf True eingestellt wurde.

Also liefert die Prozedur getEnabled den Wert False für den Parameter enabled, der dann an das aufrufende button-Element weitergegeben wird.

Nun probieren wir, ob auch das Zuweisen des IRibbon-UI-Objekts funktioniert hat. Dazu rufen wir die folgende Prozedur auf. Diese stellt zunächst bolEnabled auf True ein. Beim nächsten Aufruf von getEnabled sollte also die Schaltfläche btn aktiviert werden. Außerdem ruft die Prozedur die Invalidate-Methode von objRibbon_Main auf:

Public Sub BeispielbuttonAktivieren()
     bolEnabled = True
     objRibbon_Main.Invalidate
End Sub

Dies führt dazu, dass die Schaltfläche btn nun aktiviert dargestellt wird (siehe Bild 4). Mit der folgenden Prozedur können Sie diese wieder deaktivieren:

Die nun aktivierte Schaltfläche im Ribbon

Bild 4: Die nun aktivierte Schaltfläche im Ribbon

Public Sub BeispielbuttonDeaktivieren()
     bolEnabled = False
     objRibbon_Main.Invalidate
End Sub

Damit können wir nun wieder einen unbehandelten Fehler auslösen:

Public Sub RaiseError()
     Debug.Print 1 / 0
End Sub

Versuchen wir nun, mit der Prozedur BeispielbuttonAktivieren die Schaltfläche im Ribbon wieder zu aktivieren, erhalten wir den Fehler aus Bild 5. Genau dieses Verhalten wollen wir nun ändern.

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

Schreibe einen Kommentar