Wenn Sie ein Abonnement des Magazins 'Access im Unternehmen' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.
Datenzugriff per VSTO-Add-In
Im Beitrag »Access-Add-In mit VSTO« haben Sie erfahren, wie Sie aus einer der bestehenden Vorlagen für Outlook-, Word- oder Excel-Add-Ins ein Access-Add-In zaubern. In diesem Beitrag nun wollen wir uns ansehen, wie Sie von einem solchen Add-In auf die Objekte der Datenbank und auch auf die darin enthaltenen Daten zugreifen können. Damit erhalten Sie die Grundlage, viele interessante und praktische Add-Ins für Access zu bauen.
Wenn Sie ein Add-In für eine Access-Anwendung programmieren, wollen Sie damit nicht irgendwelche Funktionen ausführen, sondern solche, welche die Funktionen von Access oder die Daten der aktuellen Datenbank nutzen. Dazu müssen Sie innerhalb des Visual Studio-Projekts auf die Access-Instanz zugreifen können, von der aus das Add-In gestartet wurde. Darüber können Sie dann die Daten der aktuell geladenen Datenbank lesen.
Wie aber kommen wir an die Access-Instanz? Das ist, im Vergleich zu früheren Vorgehensweisen in Zusammenhang mit COM-Add-Ins für Office-Anwendungen, wesentlich leichter geworden. Genau genommen stellt die Klasse ThisAddIn.Designer.vb dieses Objekt über die Variable Application zur Verfügung (s. Bild 1). Weiter unten finden Sie dann in der gleichen Klasse die Methode Initialize, welche die Application-Eigenschaft mit einem Verweis auf die als Host verwendete Datei füllt.
Bild 1: Variable zur Bereitstellung des Application-ObjektsDies ist der Hintergrund, für uns ist aber vielmehr wichtig, dass wir über Application jederzeit die als Host dienende Access-Anwendung referenzieren können. Dies schauen wir uns an einem einfachen Beispiel an, wobei wir das im Beitrag Access-Add-In mit VSTO (www.access-im-unternehmen.de/1092) erstellte Visual Basic-Add-In als Grundlage verwenden, das Sie auch im Download zum vorliegenden Beitrag finden.
Hier fügen wir der Methode ThisAddIn_Startup im Klassenmodul ThisAddIn.vb einfach eine Anweisung hinzu, welche den Namen der mit dem Application-Objekt gelieferten Anwendung liefert:
Private Sub ThisAddIn_Startup() Handles Me.Startup
MessageBox.Show("Application.Name: " + Application.Name)
End Sub
Dies zeigt beim Öffnen von Access nach dem Starten des Add-Ins (und später auch beim Öffnen ohne vorheriges Starten des Add-Ins, das ja dann weiterhin über die Registry aufgerufen wird) eine Meldung mit dem Namen Microsoft Access an. Wer früher einmal COM-Add-Ins für Access programmiert hat, weiß, dass damit mehr benutzerdefinierter Aufwand verbunden war.
Geöffnete Datenbank referenzieren
Etwas komplizierter wird es, wenn Sie vom Add-In aus die geöffnete Datenbank referenzieren möchten. Das Problem dabei ist, dass das Add-In ja bereits beim Starten von Access geladen wird und über die Objektvariable Application verfügbar ist. Zu diesem Zeitpunkt ist aber noch keine Access-Datenbank in Access geöffnet. Dies geschieht erst später. Wir müssen also einen Weg finden, um zu erkennen, wann Access eine Datenbankdatei öffnet, und diese dann referenzieren. Wir probieren zuerst einmal aus, was geschieht, wenn wir die Methode ThisAddIn_Startup wie folgt ausstatten:
Private Sub ThisAddIn_Startup() Handles Me.Startup
If Application.CurrentDb Is Nothing Then
MessageBox.Show("DB ist nicht geladen.")
Else
MessageBox.Show("DB: " + Application.CurrentDb.Name)
End If
End Sub
Damit das MessageBox-Objekt verfügbar ist, fügen Sie noch den folgenden Verweis auf den Namespace System.Windows.Forms hinzu:
Imports System.Windows.Forms
Die Prozedur prüft nun mit CurrentDb Is Nothing, ob es eine aktuelle Datenbankdatei gibt, und liefert eine entsprechende Meldung. Das Ergebnis: Wenn man Access ohne Datenbank öffnet, hat CurrentDb den Wert Nothing, wenn man es direkt mit einer Datenbank startet, enthält CurrentDb einen Verweis auf die geladene Datenbank. Dann gibt die MessageBox.Show-Methode den Pfad zur geladenen Datenbank aus. Wenn wir allerdings Access starten und erst dann eine Datenbank öffnen, erkennt das Add-In zwar beim Starten von Access, dass noch keine Datenbank geöffnet ist, aber wenn wir dann nachträglich eine Datenbank öffnen, löst dies kein Ereignis mehr aus, aufgrund dessen wir erkennen könnten, dass eine Datenbank geöffnet wurde.
Warum aber müssen wir so genau wissen, ob gerade eine Datenbank geladen ist oder nicht? Ganz einfach: Wir wollen ja beispielsweise über das Ribbon Funktionen anbieten, mit denen der Benutzer auf die Objekte oder Daten der aktuellen Datenbank zugreifen kann. Wir könnten zwar einfach beim Betätigen der entsprechenden Elemente prüfen, ob aktuell eine Datenbank geöffnet ist, und gegebenenfalls eine Meldung ausgeben, dass die Funktion nur bei geöffneter Datenbank zur Verfügung steht. Aber schicker wäre es natürlich, wenn solche Schaltflächen deaktiviert sind, wenn die Funktionen nicht zur Verfügung stehen.
Timer mit Datenbankerkennung
Wir wollen nun einen Timer zum Add-In hinzufügen, der in jeder Sekunde einmal prüft, ob eine Datenbank geöffnet ist. Wenn eine Datenbank geöffnet ist, soll ein Meldungsfenster erscheinen und den Pfad zur geöffneten Datenbank anzeigen. Dazu fügen wir der Klasse ThisAddIn.vb folgenden Verweis auf den Namespace SystemThreading.Tasks hinzu:
Imports System.Threading.Tasks
Wir benötigen eine Boolean-Variable namens Loaded, welche den Zustand speichert, ob eine Datenbank geladen ist oder nicht:
Private Loaded As Boolean
Schließlich erweitern wir die Methode ThisAddIn_Startup so, dass diese einen Timer auf Basis der gleichnamigen Klasse erstellt, der alle 1.000 Millisekunden aufgerufen werden soll. Für diese Klasse legen wir einen Handler an, der durch das Ereignis Elapsed des Timers ausgelöst wird und die Methode HandleTimer aufruft. Dann starten wir den Timer mit der Start-Methode:
Private Sub ThisAddIn_Startup() Handles Me.Startup
Dim timer As System.Timers.Timer
timer = New System.Timers.Timer(1000)
AddHandler timer.Elapsed, AddressOf HandleTimer
With timer
.Start()
End With
End Sub
Die als Ereignishandler festgelegte Prozedur HandleTimer sieht wie in Listing 1 aus. Für VBA-erprobte Entwickler sieht der Aufbau etwas gewöhnungsbedürftig aus. Es handelt sich dabei um eine sogenannte asynchron laufende Methode, die mehrfach aufgerufen werden kann, auch wenn die aktuelle Instanz noch nicht abgearbeitet ist. So können wir die Methode tatsächlich jede Sekunde aufrufen, auch wenn der Benutzer noch nicht die durch den vorherigen Aufruf angezeigte MessageBox geschlossen hat. Das kann dann schnell unübersichtlich werden, wenn man nicht sauber codiert hat und plötzlich eine MessageBox nach der anderen auf dem Bildschirm erscheint. Für den Fall, dass Ihnen das einmal geschieht, können Sie schnell zu Visual Studio wechseln und dort Umschalt + F5 klicken – dann ist der Spuk vorbei. Falls Sie das Add-In gerade nicht im Kontext des Debuggens von Visual Studio aus gestartet haben, müssen Sie wohl den Task-Manager bemühen.
Private Async Sub HandleTimer(sender As Object, e As EventArgs)
Await Task.Run(
Sub()
If Application.CurrentDb Is Nothing Then
If Loaded = True Then
Loaded = False
End If
Else
If Loaded = False Then
Loaded = True
MessageBox.Show("Geladen: " + Application.CurrentDb.Name + " " + Loaded.ToString())
End If
End If
End Sub
)
End Sub
Listing 1: Ereignisprozedur, die nach Ablauf des Timers ausgelöst wird
Nun zu der Methode: Sie enthält innerhalb der Anweisung Await Task.Run eine weitere Methode, die bei jedem Aufruf ausgeführt wird. Diese Methode prüft, ob eine Datenbank geladen ist (Application.CurrentDb Is Nothing). Ist das nicht der Fall, prüft sie den Wert der Variablen Loaded und stellt diesen im Falle des Wertes True auf False ein. Beim Öffnen von Access ohne Datenbank geschieht also nichts weiter – CurrentDb ist Nothing und Loaded hat den Wert False.
Interessant wird es, wenn eine Datenbank geöffnet wird. Dann ist CurrentDb nicht mehr Nothing und der Else-Teil der äußeren If...Then-Bedingung wird aufgerufen. Loaded hat bis dahin den Wert False und wird auf True eingestellt. Außerdem gibt die Methode als Zeichen, dass sie erkannt hat, dass eine Datenbank geladen wurde, den Namen der Datenbank per MessageBox aus. Beim nächsten Durchgang wird dann, weil CurrentDb nicht Nothing ist, wieder der Else-Teil der äußeren Bedingung angesteuert. Diesmal hat Loaded aber den Wert False, weshalb die MessageBox nicht erneut angezeigt wird.
Was machen wir nun mit dieser Methode, die nach dem Öffnen einer Datenbank eine MessageBox anzeigt? Wir können zum Beispiel statt des Öffnens der MessageBox Code ausführen, der dafür sorgt, dass die gewünschten Ribbon-Elemente, die wir für die Bearbeitung der Datenbank hinzufügen, aktiviert oder deaktiviert werden. Das schauen wir uns einmal an einer einzelnen Ribbon-Schaltfläche an.
Ribbon hinzufügen
Um eine Ribbon-Definition hinzuzufügen, die alle Elemente des Access-Ribbons abbilden kann, können wir leider nicht das Element Menüband (Visueller Designer) des Dialogs Neues Element hinzufügen nutzen, das wir mit dem Kontextmenü-Befehl Hinzufügen|Neues Element... des Projekt-Elements im Projektmappen-Explorer öffnen. Stattdessen fügen wir das Element Menüband (XML) hinzu, für das wir den Namen Ribbon_Access.vb festlegen (s. Bild 2).
Bild 2: Anlegen des Elements Menüband (XML)
Aus der damit hinzugefügten Datei Ribbon_Access.vb kopieren wir die dort noch auskommentierte Methode CreateRibbonExtensibilityObject in das Klassenmodul ThisAddIn.vb und entfernen die Kommentarzeichen:
Protected Overrides Function _
CreateRibbonExtensibilityObject() _
As Microsoft.Office.Core.IRibbonExtensibility
Return New Ribbon_Access()
End Function
Diese Methode wird beim Starten des Add-Ins automatisch ausgeführt. Einzelheiten zum Erstellen eines Ribbons über das Element Menüband (XML) finden Sie im Beitrag COM-Add-In-Ribbons mit dem XML-Designer (www.access-im-unternehmen.de/1093).
Funktionen hinzufügen
Unser Add-In soll nun sowohl eine Funktion enthalten, die sich auf die Anwendung selbst bezieht, als auch eine, welche auf die Daten einer geladenen Datenbank zugreift. Auf diese Weise erhalten Sie eine gute Ausgangsposition für selbst programmierte Add-Ins.
Die geplanten Funktionen sind in dem Ribbon-Tab aus Bild 3 abgebildet. Die linke Gruppe enthält zwei Schaltflächen, mit denen der Benutzer den Navigationsbereich ein- und ausblenden kann. Die rechte Gruppe enthält eine Schaltfläche, welche einen Dialog zur Anzeige aller Tabellen der aktuell geöffneten Datenbank liefern soll. Diese Schaltfläche soll natürlich nur aktiviert sein, wenn Access überhaupt eine Datenbank geladen hat.
Bild 3: Unsere selbst erstellten Ribbon-BefehleDie Definition des Ribbons sieht wie in Listing 2 aus. Die erste Gruppe enthält die Schaltflächen btnNavigationsbereichEinblenden und btnNavigationsbereichAusblenden. Beide sind mit einer onAction-Callback-Eigenschaft und einem Bild ausgestattet. Die Bilder werden mit der image-Eigenschaft angegeben. Für das Laden der Bilder ist die im Element customUI im Attribut loadImage angegebene Methode verantwortlich (mehr zum Laden von Bildern im Beitrag COM-Add-In-Ribbons mit dem XML-Designer, www.access-im-unternehmen.de/1093). Die zweite Gruppe enthält die Schaltfläche btnTabellen, die ebenfalls eine onAction-Eigenschaft sowie zusätzlich die Callback-Eigenschaft getEnabled enthält.
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Ribbon_Load" loadImage="loadImage">
<ribbon>
<tabs>
<tab id="tabAddIn" label="COM-Add-In">
<group id="grpAnwendung" label="Anwendung">
<button id="btnNavigationsbereichEinblenden" label="Navigationsbereich einblenden" onAction="onAction"
image="window_sidebar" size="large"/>
<button id="btnNavigationsbereichAusblenden" label="Navigationsbereich ausblenden" onAction="onAction"
image="window_sidebar_delete" size="large"/>
</group>
<group id="grpDatenbank" label="Datenbank">
<button id="btnTabellen" label="Tabellen anzeigen" onAction="onAction" image="tables" size="large"
getEnabled="getEnabled"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
Listing 2: Definition des Beispielribbons
Aktivieren und deaktivieren der Schaltfläche btnTabellen
Diese Schaltfläche soll ja nur aktiviert sein, wenn Access gerade eine geladene Datenbank enthält. Wie wir dies ermitteln, haben Sie weiter oben erfahren – wir starten per Timer jede Sekunde eine Prozedur, die prüft, ob CurrentDb den Wert Nothing hat. Wie können wir diese nun nutzen, um die Schaltfläche btnTabellen abhängig vom Ergebnis zu aktivieren oder zu deaktivieren? Zusammengefasst gehen wir so vor: Wir fügen der Klasse Ribbon_Access.vb eine Eigenschaft hinzu, welche den Zustand erfasst (Datenbank geladen/nicht geladen). Diese wird von der Callback-Funktion getEnabled ausgewertet. Noch dazu machen wir in der Klasse Ribbon_Access.vb aus dem IRibbonUI-Objekt Ribbon eine Eigenschaft gleichen Namens, auf die wir von außen zugreifen können. Damit können wir dann der Klasse Ribbon_Access.vb von außen sowohl den Zustand Datenbank geladen/nicht geladen mitgeben als auch dafür sorgen, dass die Methode getEnabled ausgelöst wird – nämlich durch Aufrufen der Invalidate-Methode des IRibbonUI-Objekts. Damit wir von der Klasse ThisAddIn allerdings überhaupt auf das IRibbonUI-Objekt zugreifen können, müssen wir dieses noch irgendwie innerhalb dieser Klasse referenzieren. Unter Visual Basic 2015 wäre es sogar möglich, dafür ein Modul mit einer öffentlichen Variablen anzulegen, aber diesen Weg wollen wir hier gezielt nicht gehen. Schauen wir uns den resultierenden Code an!
Ribbon in ThisAddIn.vb referenzieren
Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.