Access-Add-Ins

Wollen Sie Access um Funktionen erweitern, stehen Sie vor der Entscheidung, ob Sie ein Access-Add-In oder ein COM-Add-In bauen. Ersteres ist dem Access-Entwickler natürlich lieber, weil er sich nicht auf ungewohntes Terrain begeben muss. Wir zeigen am Beispiel eines Steuerelement-Rename-Tools, was Sie beim Bau eines Access-Add-Ins beachten müssen.

Ein Access-Add-In ist in erster Instanz eine herkömmliche Access-Datenbank, die allerdings einige Besonderheiten aufweist. Als Erstes wäre da die Tatsache zu nennen, dass sie natürlich nicht allein, sondern immer in Zusammenhang mit einer anderen Access-Anwendung geöffnet wird – immerhin soll das Add-In ja bei der Entwicklung einer Anwendung helfen. Dazu müssen Sie einige Voraussetzungen erfüllen: Als Erstes benötigt die Add-In-Datenbank eine öffentliche Funktion in einem Standardmodul, welche zum Aufrufen des Add-Ins beziehungsweise der enthaltenen Funktion nötig ist.

Dabei gibt es zwei Möglichkeiten:

  • Das Add-In besitzt eine Benutzeroberfläche in Form eines Formulars, das durch die öffentliche Funktion angezeigt wird, um dem Benutzer die Steuerung des Add-Ins zu ermöglichen.
  • Das Add-In besitzt keine Benutzeroberfläche, sondern soll ausschließlich VBA-Code auslösen, der eine bestimmte Aufgabe erledigt. Diesen bringt man dann entweder direkt in der öffentlichen Funktion unter oder ruft von dort aus entsprechende Routinen auf.

Registry

Damit ein Add-In beim Bearbeiten einer anderen Anwendung zur Verfügung steht, benötigen Sie ein paar Registry-Einträge. Bei einem Menü-Add-In, das man unter Access 2003 über den Menüeintrag Extras|Add-Ins öffnen möchte, liegen diese in der Registry im Pfad HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Office\\12.0\\Access\\Menu Add-Ins (12.0 ersetzen Sie dabei durch die jeweilige Office-Version).

Dort benötigen Sie zunächst einen Schlüssel mit dem Namen des Add-Ins, zum Beispiel ControlRenamer. Unterhalb dieses Schlüssels legen Sie dann die folgenden Einträge des Typs Zeichenfolge an:

  • Description: Beschreibung des Add-Ins
  • Expression: Aufruf der öffentlichen Funktion in der Add-In-Datenbank, die das Add-In startet
  • Library: Pfad und Name der Add-In-Datenbank

Zur Vereinfachung beim Anlegen eines Add-Ins hat Microsoft sich etwas einfallen lassen: Sie können die benötigten Informationen in einer Tabelle namens USysRegInfo unterbringen. Diese sieht wie in Abb. 1 aus und hat eine Besonderheit: Tabellen, deren Name mit USys… beginnt, zeigt Access standardmäßig nicht an. Dies geschieht erst, wenn Sie die Anzeige ausgeblendeter Objekte aktivieren (unter Accesss 2003 und älter: Access-Optionen, Registerseite Ansicht, Eigenschaft Ausgeblendete Objekte, unter Access 2007: Rechtsklick auf den Kopf des Navigationsbereichs, Eintrag Navigationsoptionen, Option Ausgeblendete Objekte anzeigen).

pic001.tif

Abb. 1: Der Add-In-Manager liest diese Einträge aus und schreibt sie in die Registry.

Wenn Sie jetzt noch eine Funktion namens Autostart in einem Standardmodul anlegen, die wie folgt aussieht, können Sie das Add-In bereits registrieren:

Public Function Autostart()
    MsgBox "ControlRenamer"
End Function

Dazu schließen Sie die Add-In-Datenbank und benennen sie in .mda (Access 2003 und älter) oder .accda (Access 2007) um, um den gängigen Konventionen zu entsprechen. Dann öffnen Sie eine beliebige Datenbank unter Access und rufen den Add-In-Manager auf (unter Access 2003 und älter: Extras|Add-Ins|Add-In-Manager, unter Access 2007: Ribboneintrag Datenbanktools|Add-Ins|Add-In-Manager). Dieser sieht wie in Abb. 2 aus und zeigt nach einem Klick auf die Schaltfläche Hinzufügen… einen Datei öffnen-Dialog an. Wählen Sie hier die soeben erstellte Add-In-Datenbank aus und klicken Sie auf Öffnen.

pic002.tif

Abb. 2: Der Add-In-Manager von Access

Der Add-In-Manager zeigt daraufhin einen entsprechenden Eintrag in der Liste der Add-Ins an, wobei dieser im Gegensatz zu den anderen möglicherweise schon vorhandenen Add-Ins nicht durch ein x markiert ist. Dies ist jedoch kein Grund zur Sorge: Beim nächsten Öffnen des Add-In-Managers ist auch dieser Eintrag entsprechend markiert.

Außerdem sollte auch das über den Add-Ins-Menüeintrag zu öffnende Menü den Namen des neuen Add-Ins anzeigen. Mit einem Klick auf dieses lösen Sie es aus und zeigen so das in obiger Routine programmierte Meldungsfenster an.

Was ist geschehen

Der Add-In-Manager hat nun zweierlei Dinge erledigt:

  • Er hat die Add-In-Datei an den in der Tabelle angegebenen Ort kopiert, in diesem Fall |ACCDIR\\ControlRenamer.accda. |ACCDIR entspricht hier dem Verzeichnis C:\\Dokumente und Einstellungen\\<Benutzername>\\Anwendungsdaten\\Microsoft\\AddIns\\.
  • Er hat außerdem die in der Tabelle USysRegInfo angegebenen Registry-Einträge vorgenommen.

Dadurch ruft Access beim Klick auf den Add-In-Eintrag nun jeweils die angegebene Funktion des neuen Add-Ins auf, das sich im Add-In-Verzeichnis des aktuellen Benutzers befindet.

Achtung, Falle!

Besonders wichtig ist das oben beschriebene Umkopieren der Add-In-Datenbank in das Add-In-Verzeichnis, wenn Sie nachträglich änderungen am Add-In vornehmen: Dies erledigen Sie dann entweder direkt mit der im Add-In-Verzeichnis gespeicherten Datenbank oder Sie bearbeiten die in einem Entwicklungs-Verzeichnis enthaltene Datenbank und kopieren die jeweils neue Version in das Add-In-Verzeichnis. Dabei leistet eine kleine Batch-Datei namens CopyToAddInsPath.bat mit folgendem Inhalt gute Dienste:

Copy "<Originalverzeichnis>\\ControlRename.accda" "<Add-In-Verzeichnis>" /Y
Pause

Hier müssen Sie noch die Platzhalter <Originalverzeichnis> und <Add-In-Verzeichnis> ersetzen. Die Option /Y sorgt dafür, dass eine bestehende Datei ohne Rückfrage überschrieben wird, der Befehl Pause lässt die Eingabeaufforderung geöffnet, damit Sie sich vom Erfolg der Aktion überzeugen können.

Feinheiten

Möglicherweise möchten Sie Ihr Add-In richtig rund machen – sodass es beispielsweise im Add-In-Manager einen entsprechenden Text für den Autor und die Beschreibung des Add-Ins anzeigt. Dies ist natürlich hilfreich, gerade wenn man mit mehreren Add-Ins arbeitet und mit einem Blick erfahren möchte, was ein bestimmtes Add-In für eine Aufgabe hat. Um die beiden in Abb. 2 zu erkennenden Felder zu füllen, schließen Sie die Add-In-Datenbank und zeigen über das Kontextmenü ihres Eintrags im Windows Explorer ihr Eigenschaftsfenster an. Dort brauchen Sie nur die beiden Eigenschaften Autor und Kommentar mit den gewünschten Texten zu versehen – fertig!

Add-In entfernen

Zum Entfernen eines Add-Ins, etwa um es erneut sauber zu installieren, sind zwei Schritte nötig:

  • Löschen der Registry-Einträge: Dies erledigen Sie, indem Sie das Add-In im Add-In-Manager markieren und auf Deinstallieren klicken.
  • Entfernen der Add-In-Datenbank aus dem Add-In-Verzeichnis

Steuerelemente umbenennen

Kommen wir zur Aufgabe unseres Beispiel-Add-Ins: Wenn man ein Formular oder einen Bericht an eine Datenherkunft wie eine Tabelle oder Abfrage bindet und Elemente dieser Datenherkunft aus der Feldliste in den Entwurf zieht, legt Access automatisch Steuerelemente an, die im Wesentlichen zwei Eigenschaften aufweisen: den Namen des Feldes als Name des Steuerelements und gleichzeitig als Steuerelementinhalt. Für den Steuerelementinhalt ist dies okay, den Namen des Steuerelements möchte man aber vielleicht einer Namenskonvention anpassen. Diese könnte beispielsweise für verschiedene Steuerelemente bestimmte Präfixe verlangen:

  • Textfeld: txt
  • Kombinationsfeld: cbo
  • Listenfeld: lst
  • Kontrollkästchen: chk

Meist verzichten Entwickler darauf, gebundene Steuerelemente so umzubenennen, und behalten die Feldnamen als Steuerelementnamen bei. Unser Add-In soll die gebundenen Steuerelemente eines Formulars automatisch umbenennen.

Wie aber geschieht dies am einfachsten Immerhin sollte der Benutzer noch ein wenig Kontrolle darüber behalten, wie seine Steuerelemente später heißen. Grundsätzlich macht das geplante Add-In natürlich am meisten Sinn, wenn man es direkt nach dem Erstellen der Steuerelemente einsetzt und nicht erst nach dem Hinzufügen von Ereignisprozeduren, die durch die betroffenen Steuerelemente ausgelöst werden oder auf deren Inhalt zugreifen. Für eine möglichst große Kontrolle soll der Benutzer vor dem Umbenennen vorher sehen, für welche Steuerelemente eine Namensänderung Sinn macht, und dann selbst auswählen, ob dies auch für alle angegebenen Steuerelemente geschehen soll. Das Add-In soll dazu die betroffenen Formulare und Berichte sowie ihre Steuerelemente in einem Formular anzeigen.

Das sieht, wenn das Add-In einmal fertig ist, wie in Abb. 3 aus. Es zeigt ein Kombinationsfeld zum Auswählen aller vorhandenen Formulare und Berichte an. Beim Einlesen der Objekte werden jedoch nur diejenigen berücksichtigt, die überhaupt noch gebundene Steuerelemente enthalten, deren Name mit dem Feldnamen übereinstimmt.

pic003.tif

Abb. 3: Das fertige Add-In zum Anpassen von Steuerelementbezeichnungen

Das Listenfeld unter dem Kombinationsfeld zeigt alle gebundenen Steuerelemente des ausgewählten Formulars oder Berichts an, deren Name-Eigenschaft mit der Steuerelementinhalt-Eigenschaft übereinstimmt.

Der Benutzer kann mit zwei Schaltflächen alle Einträge markieren oder abwählen und per Mausklick auf die Listenfeldeinträge auch einzelne Elemente herauspicken, die er mit einem Präfix versehen möchte. Klickt er auf die Schaltfläche Steuerelementnamen ändern, fragt das Add-In noch kurz nach, ob es die änderungen wirklich vornehmen soll, und führt diese dann aus.

Formularbau

Das Formular sieht in der Entwurfsansicht wie in Abb. 4 aus. Die Datensatzherkunft des Kombinationsfelds cboObjects enthält zunächst die Werte einer Tabelle namens tblObjects, die des Listenfelds lstControls ist zunächst leer, wird aber später aus einer weiteren Tabelle namens tblControls gespeist.

pic004.tif

Abb. 4: Das Add-In-Formular in der Entwurfsansicht

Warum Tabellen in der Add-In-Datenbank Nun, wir arbeiten mit Access, und wenn die Möglichkeit besteht, Informationen in Tabellen zu speichern und diese so ganz leicht in Steuerelementen wie etwa Kombinations- oder Listenfeldern anzuzeigen, sollte man das auch ausnutzen.

Wie kommt das Kombinationsfeld cboObjects zu seinen Daten Das Add-In füllt die Tabellen tblObjects und tblControls erstmalig beim Laden des Formulars, und zwar über die Routine ScanDatabase aus Listing 1.

Listing 1: Diese Routine durchläuft alle Formular- und Berichtsobjekte und ruft für jedes die Prozedur ScanObject auf.

Private Sub ScanDatabase()
    Dim dbAddin As DAO.Database
    Dim obj As AccessObject
    Set dbAddin = CodeDb
    dbAddin.Execute "DELETE FROM tblObjects", dbFailOnError
    For Each obj In CurrentProject.AllForms
        ScanObject obj, dbAddin
    Next obj
    For Each obj In CurrentProject.AllReports
        ScanObject obj, dbAddin
    Next obj
    dbAddin.Execute "DELETE FROM tblObjects WHERE ID NOT IN " _
        & "(SELECT DISTINCT ObjectID FROM tblControls)", dbFailOnError
End Sub

ScanDatabase durchläuft zunächst die Auflistung AllForms der Formulare des Objekts CurrentProjects und erledigt anschließend das Gleiche für die Berichte. Sie ruft für jedes Objekt einmal die Routine ScanObject auf (s. Listing 2). Diese Routine ermittelt zunächst den Namen und den Typ des Objekts und schreibt diese in die Tabelle tblObjects, die wie in Abb. 5 aussieht.

Listing 2: Diese Routine liest Informationen aus Formularen und Berichten sowie aus deren Steuerelementen ein und schreibt sie in die beiden Tabellen tblObjects und tblControls.

Private Sub ScanObject(obj As AccessObject, dbAddin As DAO.Database)
    '' ... Deklarationen ...
    If Not obj.Name = Me.Name Then
        strObjectname = obj.Name
        lngObjecttype = obj.Type
        dbAddin.Execute "INSERT INTO tblObjects(Objectname, Objecttype) VALUES(''" _
            & strObjectname & "'', " & lngObjecttype & ")", dbFailOnError
        lngObjectID = dbAddin.OpenRecordset("SELECT @@IDENTITY").Fields(0)
        If lngObjecttype = 2 Then ''Form
            DoCmd.OpenForm strObjectname, view:=acDesign, WindowMode:=acHidden
            Set objFormReport = Forms(strObjectname)
        Else ''Report
            DoCmd.OpenReport strObjectname, view:=acViewDesign, WindowMode:=acHidden
            Set objFormReport = Reports(strObjectname)
        End If
        For Each ctl In objFormReport.Controls
            Select Case ctl.ControlType
                Case acCheckBox, acComboBox, acListBox, acTextBox
                    strControlname = ctl.Name
                    lngControltype = ctl.ControlType
                    strControlsource = ctl.ControlSource
                    If strControlname = strControlsource Then
                        Select Case ctl.ControlType
                            Case acCheckBox: strPrefix = "chk"
                            Case acComboBox: strPrefix = "cbo"
                            Case acListBox: strPrefix = "lst"
                            Case acTextBox: strPrefix = "txt"
                    End Select
                    dbAddin.Execute "INSERT INTO tblControls(Controlname, Controltype, ControlSource, " _
                        & "Prefix, ObjectID) VALUES(''" & strControlname & "'', " & lngControltype & ", ''" _
                        & strControlsource & "'', ''" & strPrefix & "'', " & lngObjectID & ")", dbFailOnError
                End If
            End Select
        Next ctl
        If lngObjecttype = 2 Then
            DoCmd.Close acForm, strObjectname
        Else
            DoCmd.Close acReport, strObjectname
        End If
    End If
End Sub

pic005.tif

Abb. 5: Die beiden Tabellen der Add-In-Datenbank

Dann öffnet sie das Objekt mit der dem Objekttyp entsprechenden Anweisung und durchläuft alle enthaltenen Steuerelemente. Dabei untersucht sie nur die Kontrollkästchen, Kombinationsfelder, Listenfelder und Textfelder und prüft, ob der Wert der Eigenschaft Name dem Wert der Eigenschaft ControlSource entspricht.

Ist das der Fall, legt die Routine einen neuen Eintrag in der Tabelle tblControls an (s. Abb. 5).

Nachdem die Routine ScanObject für alle Formulare und Berichte je einmal aufgerufen wurde, untersucht die Prozedur ScanDatabase aus Listing 1 noch, ob die Tabelle tblObjects Einträge aufweist, für die die Tabelle tblControls keine Steuerelemente enthält. Die beiden Routinen zum Füllen der beiden Tabellen tblObjects und tblControls werden erstmalig beim Öffnen des Formulars ausgelöst. Im gleichen Zuge sorgt die Routine InitializeForm (s. Listing 3) dafür, dass das Kombinationsfeld cboObjects aktualisiert wird und nunmehr alle in der Tabelle tblObjects enthaltenen Datensätze anzeigt.

Listing 3: InitializeForm füllt mit ScanDatabases die Tabellen des Add-Ins und aktualisiert dann deren Steuerelemente.

Private Sub InitializeForm()
    ScanDatabase
    Me!cboObjects.Requery
    Me!cboObjects = Me!cboObjects.ItemData(0)
    FillControls
End Sub

InitializeForm ruft weiterhin die Routine FillControls auf, die dem Listenfeld lstControls einen neuen SQL-Ausdruck als Datensatzherkunft zuweist (s. Listing 4). Dieser besteht aus allen Datensätzen der Tabelle tblControls, deren ObjectID mit dem im Kombinationsfeld angezeigten Datensatz der Tabelle tblObjects übereinstimmt.

Listing 4: Die Routine FillControls aktualisiert die Datensatzherkunft des Listenfelds lstControls in Abhängigkeit vom Kombinationsfeld cboObjects.

Private Sub FillControls()
    Dim strSQL As String
    strSQL = "SELECT ID, Controlname AS [Aktuelle Bezeichnung], " _
         & "Prefix & Controlname AS [Neue Bezeichnung] " _
         & "FROM tblControls WHERE ObjectID = " & Me!cboObjects
    Me!lstControls.RowSource = strSQL
End Sub

Der Benutzer braucht nun nur noch die gewünschten Steuerelemente zu markieren und auf die Schaltfläche cmdChangeNames zu klicken:

Dies löst die entsprechende Ereignisprozedur aus, die über die Routine ChangeNames zunächst das entsprechende Formular oder den Bericht öffnet und dann über die Auflistung der Steuerelemente die Bezeichnungen der betroffenen Steuerelemente um das jeweilige Präfix ergänzt (Routine ChangeName, alle Listing 5).

Listing 5: Diese Routinen sind für das ändern der Steuerelementnamen verantwortlich und ergänzen diese jeweils um das entsprechende Präfix.

Private Sub cmdChangeNames_Click()
    If MsgBox("Achtung: Dies ändert die Namen der links markierten gebundenen Steuerelemente." _
              & vbCrLf & "Eventuelle änderungen am Quellcode müssen Sie selbst durchführen." _
            & vbCrLf & "Fortfahren", _
            vbYesNo + vbCritical, "änderung von Steuerelementnamen") = vbYes Then
        ChangeNames
    End If
End Sub
Private Sub ChangeNames()
    '' ... Deklaration ...
    Set dbAddin = CodeDb
    lngObjectID = Me!cboObjects
    lngObjecttype = dbAddin.OpenRecordset("SELECT Objecttype FROM tblObjects WHERE ID = " _
        & lngObjectID).Fields(0)
    strObjectname = dbAddin.OpenRecordset("SELECT Objectname FROM tblObjects WHERE ID = " _
        & lngObjectID).Fields(0)
    Select Case lngObjecttype
        Case 2 ''Formular
            DoCmd.OpenForm strObjectname, view:=acDesign, WindowMode:=acHidden
            Set obj = Forms(strObjectname)
            For Each var In Me!lstControls.ItemsSelected
                ChangeName obj, Me!lstControls.ItemData(var)
            Next var
            DoCmd.Close acForm, strObjectname, acSaveYes
        Case Else
            DoCmd.OpenReport strObjectname, view:=acViewDesign, WindowMode:=acHidden
            Set obj = Reports(strObjectname)
            For Each var In Me!lstControls.ItemsSelected
                ChangeName obj, Me!lstControls.ItemData(var)
            Next var
            DoCmd.Close acReport, strObjectname, acSaveYes
    End Select
    InitializeForm
End Sub
Private Sub ChangeName(obj As Object, lngControlID As Long)
    Dim strControlname As String
    Dim dbAddin As DAO.Database
    Dim strPrefix As String
    Set dbAddin = CodeDb
    strControlname = dbAddin.OpenRecordset("SELECT Controlname FROM tblControls WHERE ID = " _
        & lngControlID).Fields(0)
    strPrefix = dbAddin.OpenRecordset("SELECT Prefix FROM tblControls WHERE ID = " _
        & lngControlID).Fields(0)
    obj.Controls(strControlname).Name = strPrefix & strControlname
End Sub

Zugriff auf Host- und Add-In-Datenbank: CurrentDB vs. CodeDB, Currentproject vs. Codeproject und die Domänenfunktionen

Die Routine aus Listing 1 liefert ein gutes Beispiel dafür, dass man es bei der Arbeit mit Access-Add-Ins gleich mit zwei Datenbanken zu tun hat: mit der Host-Anwendung, also der Access-Datenbank, von der aus man das Add-In aufruft, und mit der Add-In-Datenbank selbst.

Es kann passieren, dass ein Add-In auf die Tabellen der Host-Datenbank sowie auch auf die eigenen Tabellen zugreift, und genauso kann es vorkommen, dass das Add-In Objekte wie Formulare und Berichte aus Host- und Add-In-Datenbank referenzieren muss.

Der Zugriff auf die in Tabellen gespeicherten Daten geschieht per VBA normalerweise über das Database-Objekt, das man beim alltäglichen Umgang mit Access-Datenbanken über die Funktion CurrentDB bezieht. Darüber kommt man aber nicht an die Daten in der Add-In-Datenbank heran. Um das Database-Objekt der Add-In-Datenbank zu verwenden, braucht man die Funktion CodeDB, wie auch in der Routine ScanDatabase aus Listing 1 geschehen.

Das Gleiche gilt für den Zugriff auf die Objekte: Wer etwa alle Formulare über die AllForms-Auflistung durchlaufen möchte, bezieht sich dabei üblicherweise auf CurrentProject. Auch hierfür gibt es ein Add-In-Pendant, nämlich CodeProject.

Und worauf greifen eigentlich die Domänenfunktionen wie DLookup, DCount oder DMax zu Diese beziehen sich immer auf die Host-Datenbank. Wenn Sie solche Funktionen mit den Daten in der Add-In-Datenbank verwenden, müssen Sie selbst Hand anlegen: Bauen Sie sich eigene Domänenfunktionen wie im Beitrag Schnelle Domänenfunktionen (Shortlink 644, Ausgabe 1/2009) beschrieben, und beziehen Sie sich darin auf CodeDB statt auf CurrentDB. Solche Funktionen könnte man dann CLookup oder CCount nennen (wobei C für CodeDB steht). Im Übrigen sollten Sie sowieso eher mit den schnellen Ersatzfunktionen für die eingebauten Domänenfunktionen arbeiten …

Zusammenfassung und Ausblick

Dieses Add-In steht in Versionen für Access 2007 sowie für Access 2000 bis 2003 zur Verfügung. Sie sollten es unmittelbar nach dem Hinzufügen gebundener Steuerelemente zu einem Formular oder Bericht verwenden, um deren Steuerelementnamen um ein Präfix zu erweitern. Wenn ein gebundenes Steuerelement einen anderen Namen als das dadurch repräsentierte Feld hat, sind Sie auf jeden Fall auf der richtigen Seite, wenn Sie entweder auf das Steuerelement oder auf das darin angezeigte Feld zugreifen möchten. Wenn Sie andere Ideen für nützliche Add-Ins haben, können Sie dieses hier als Vorlage verwenden: Füllen Sie es einfach mit der benötigten Benutzeroberfläche und legen Sie die passenden Funktionen an. Hilfreich ist dabei sicher ein Blick in unseren Kasten Zugriff auf Host- und Add-In-Datenbank …, der einige Besonderheiten beim Zugriff auf die Daten und Objekte der beim Einsatz eines Add-Ins beteiligten Datenbanken aufzeigt.

Downloads zu diesem Beitrag

Enthaltene Beispieldateien:

ControlRenamer.mda

Download

Schreibe einen Kommentar