Steuerelemente ausrichten per COM-Add-In

Im Beitrag „Steuerelemente ausrichten per VBA“ (www.access-im-unternehmen.de/1429) haben wir eine Prozedur entwickelt, die markierte Steuerelemente samt Bezeichnungsfeldern in der Entwurfsansicht ausrichtet. Damit wir sie nutzen können, muss der Code in der jeweiligen Datenbank vorliegen. Bequemer ist es, wenn sie in jeder Datenbank über das Ribbon und das Kontextmenü der Steuerelemente bereitsteht. Wie wir dazu ein COM-Add-In mit twinBASIC bauen, zeigt der vorliegende Beitrag.

Von der VBA-Prozedur zum COM-Add-In

Access lädt ein COM-Add-In beim Start. Seine Funktionen stehen damit unabhängig von der geöffneten Datenbank zur Verfügung. Wir müssen keinen Code in einzelne Datenbanken kopieren. Die Prozedur aus dem VBA-Beitrag bleibt im Kern erhalten. Es ändert sich, wie wir ihre Parameter festlegen und wie wir sie aufrufen.

Die Parameter lngDistanceLabelControl, lngVerticalDistance, lngAlignment und bolColons der Prozedur AlignControls erscheinen als Elemente im Ribbon. Zwei Textfelder legen den horizontalen Abstand zwischen Bezeichnungsfeld und Steuerelement sowie den vertikalen Abstand fest, jeweils in Twips. Eine Dropdownliste bestimmt die Textausrichtung der Bezeichnungsfelder, und ein Kontrollkästchen schaltet den Doppelpunkt (:) hinter den Beschriftungen ein. Zwei Schaltflächen starten das Ausrichten und das Anhängen der Doppelpunkte (siehe Bild 1).

Der neue Bereich im Ribbon

Bild 1: Der neue Bereich im Ribbon

Dieselben beiden Befehle fügen wir dem Kontextmenü der Steuerelemente hinzu (siehe Bild 2).

Die beiden Befehle im Kontextmenü

Bild 2: Die beiden Befehle im Kontextmenü

Das Register im Ribbon soll nur erscheinen, wenn wir ein Formular oder einen Bericht in der Entwurfsansicht bearbeiten. Dafür verwenden wir kontextbezogene Register.

Das Add-In besteht aus drei Teilen. Die Klasse ControlAligner enthält die Anbindung an Access, das Ribbon und die Kontextmenüs. Das Modul Alignment enthält die Ausrichtungslogik. Das Modul DllRegistration trägt das Add-In bei der Registrierung in die Registry ein.

Das twinBASIC-Projekt anlegen

Wie wir twinBASIC einrichten und ein COM-Add-In für Access aufbauen, haben wir in den Beiträgen twinBASIC – VB/VBA mit moderner Umgebung (www.access-im-unternehmen.de/1303) und twinBASIC – COM-Add-Ins für Access (www.access-im-unternehmen.de/1306) beschrieben. Hier legen wir ein neues Projekt vom Typ ActiveX-DLL mit dem Namen amvControlAligner an und fügen die drei Quelldateien hinzu. Das Projekt benötigt vier Verweise.

  • OLE Automation: liefert die Standardtypen (stdole2.tlb).
  • Microsoft Add-In Designer: liefert die Schnittstelle IDTExtensibility2 (MSADDNDR.DLL).
  • Microsoft Office 16.0 Object Library: liefert die Schnittstellen IRibbonExtensibility und IRibbonControl (MSO.DLL).
  • Microsoft Access 16.0 Object Library: liefert die Typen von Access (MSACC.OLB).

Außerdem schalten wir Option Explicit für das Projekt ein. Die DLL muss dieselbe Bitbreite wie die installierte Access-Version haben. In unserem Beispiel ist das die 32-Bit-Version von Access.

Nach dem Erstellen müssen wir die DLL registrieren. Das Modul DllRegistration schreibt dazu unter HKCU\SOFTWARE\Microsoft\Office\Access\Addins einen Schlüssel mit dem Namen amvControlAligner.ControlAligner und den Werten FriendlyName, Description und LoadBehavior. Der Wert 3 für LoadBehavior bewirkt, dass Access das Add-In beim Start lädt. Das Vorgehen dazu zeigt der Beitrag COM-Add-Ins und -DLLs installieren oder registrieren (www.access-im-unternehmen.de/1484).

Die Klasse des Add-Ins

Die Klasse ControlAligner implementiert zwei Schnittstellen. IDTExtensibility2 meldet dem Add-In, dass Access es lädt oder entlädt. IRibbonExtensibility liefert Access beim Laden die Ribbondefinition:

[ ClassId ("940037E2-0FB7-89-B188-3212448EA4EC") ]
Class ControlAligner
    Implements IDTExtensibility2
    [ WithDispatchForwarding ]
    Implements IRibbonExtensibility
    Private objAccess As Access.Application
    Private objRibbon As IRibbonUI
    Private lngDistanceLabelControl As Long
    Private lngVerticalDistance As Long
    Private lngAlignment As Long
    Private bolColons As Boolean
    Private WithEvents cbbFormAlign As CommandBarButton
    Private WithEvents cbbFormColons As CommandBarButton
    Private WithEvents cbbReportAlign As CommandBarButton
    Private WithEvents cbbReportColons As CommandBarButton
    ...
End Class

Das Attribut ClassId legt die Klassen-ID fest, unter der Access das Add-In findet. Jedes Add-In benötigt eine eigene ID. Das Attribut WithDispatchForwarding vor IRibbonExtensibility sorgt dafür, dass Access die im Ribbon-XML angegebenen Callbackprozeduren anhand ihres Namens in der Klasse aufrufen kann.

Die Variable objAccess nimmt die Access-Anwendung auf und objRibbon das Ribbon-Objekt, das wir zum Aktualisieren benötigen. Die vier Variablen für die Einstellungen entsprechen den Parametern der Ausrichtung. Die vier mit WithEvents deklarierten Variablen vom Typ CommandBarButton nehmen später die Schaltflächen der Kontextmenüs auf und liefern deren Ereignis Click.

Beim Laden ruft Access die Prozedur OnConnection auf (siehe Listing 1). Sie speichert die Access-Anwendung in objAccess, lädt die gespeicherten Einstellungen und legt die Schaltflächen in den Kontextmenüs an.

Sub OnConnection(ByVal Application As Object, ByVal ConnectMode As ext_ConnectMode, ByVal AddInInst As Object, _
        ByRef custom As Variant()) Implements IDTExtensibility2.OnConnection
    Set objAccess = Application
    LoadSettings
    SetCommandBars
End Sub

Listing 1: Die Prozedur OnConnection verbindet das Add-In mit Access

Beim Entladen ruft Access die Prozedur OnDisconnection auf. Diese entfernt die Schaltflächen wieder und gibt die Objektvariablen frei. Die übrigen Prozeduren der Schnittstelle (OnAddInsUpdate, OnStartupComplete und OnBeginShutdown) benötigen wir nicht und lassen sie leer. Da sie Teile der Schnittstelle IDTExtensibility2 sind, dürfen wir sie nicht einfach weglassen.

Die Einstellungen speichern

Die Einstellungen sollen beim nächsten Start von Access wieder zur Verfügung stehen. Das Add-In speichert sie mit der Funktion SaveSetting in der Registry und liest sie in der Prozedur LoadSettings mit der Funktion GetSetting wieder ein. Fehlt ein Eintrag, verwendet LoadSettings die Standardwerte. Das sind 100 Twips für beide Abstände, die Textausrichtung Standard und keine Doppelpunkte.

Die Prozedur SaveSettings schreibt die vier Werte und aktualisiert danach das Ribbon (siehe Listing 2).

Private Sub SaveSettings()
    SaveSetting SETTINGS_APP, SETTINGS_SECTION, "AbstandBezeichnung", CStr(lngDistanceLabelControl)
    SaveSetting SETTINGS_APP, SETTINGS_SECTION, "AbstandVertikal", CStr(lngVerticalDistance)
    SaveSetting SETTINGS_APP, SETTINGS_SECTION, "Ausrichtung", CStr(lngAlignment)
    SaveSetting SETTINGS_APP, SETTINGS_SECTION, "Doppelpunkte", IIf(bolColons, "1", "0")
    'Das Ribbon enthält die Steuerelemente je einmal für
    'Formulare und Berichte und muss beide neu lesen.
    If Not objRibbon Is Nothing Then
        objRibbon.Invalidate
    End If
End Sub

Listing 2: Die Prozedur SaveSettings speichert die Einstellungen

Der Aufruf der Methode Invalidate ist erforderlich, weil das Ribbon die Elemente zweimal enthält, einmal im Register für Formulare und einmal im Register für Berichte. Ändern wir einen Wert in einem der beiden Register, müssen beide ihre Anzeige neu einlesen.

Die Eingaben in die beiden Textfelder prüft die Funktion ParseDistance. Sie wandelt den Text mit der Funktion Val in eine ganze Zahl zwischen 0 und 99999 um.

Ist die Eingabe keine Zahl oder liegt sie außerhalb dieses Bereichs, liefert die Funktion den Standardwert 100. Die Obergrenze ergibt sich aus der Länge der Eingabe, die wir im Ribbon mit dem Attribut maxLength auf fünf Zeichen begrenzen:

Private Function ParseDistance(ByVal strText As String) As Long
    If IsNumeric(strText) Then
        If Val(strText) >= 0 And Val(strText) <= 99999 Then
            ParseDistance = CLng(Val(strText))
            Exit Function
        End If
    End If
    ParseDistance = 100
End Function

Das Ribbon mit kontextbezogenen Registern

Access ruft beim Laden die Funktion GetCustomUI der Schnittstelle IRibbonExtensibility auf und erwartet als Ergebnis den XML-Code der Ribbondefinition. Wir erzeugen ihn in dieser Funktion (siehe Listing 3).

Private Function GetCustomUI(ByVal RibbonID As String) _
        As String Implements IRibbonExtensibility.GetCustomUI
    Dim strXML As String
    strXML &= "<customUI xmlns=""http://schemas.microsoft.com/office/2009/07/customui""" & _
              " onLoad=""OnRibbonLoad"">" & vbCrLf
    strXML &= "<ribbon startFromScratch=""false"">" & vbCrLf
    strXML &= "<contextualTabs>" & vbCrLf
    strXML &= "<tabSet idMso=""TabSetFormTools"">" & vbCrLf
    strXML &= GetTabXml("Form")
    strXML &= "</tabSet>" & vbCrLf
    strXML &= "<tabSet idMso=""TabSetReportTools"">" & vbCrLf
    strXML &= GetTabXml("Report")
    strXML &= "</tabSet>" & vbCrLf
    strXML &= "</contextualTabs>" & vbCrLf
    strXML &= "</ribbon>" & vbCrLf
    strXML &= "</customUI>" & vbCrLf
    Return strXML
End Function

Listing 3: Die Funktion GetCustomUI liefert die Ribbondefinition

Das Attribut onLoad des Elements customUI nennt die Callbackprozedur OnRibbonLoad, über die wir das Ribbon-Objekt erhalten. Das Register soll nur im Entwurf von Formularen und Berichten erscheinen.

Deshalb enthält das Element contextualTabs zwei Elemente tabSet. Über die Werte TabSetFormTools und TabSetReportTools des Attributs idMso wählen wir die kontextbezogenen Registergruppen für Formulare und Berichte aus. Access blendet das Register ein, wenn das Objekt in der Entwurfsansicht geöffnet ist. In der Formularansicht erscheint es nicht.

Access prüft die Werte für idMso beim Laden des Ribbons.

Verwenden wir einen unbekannten Wert, meldet Access einen Fehler in der benutzerdefinierten Benutzeroberfläche.

Beide Registergruppen erhalten dasselbe Register. Weil jedes Element im Ribbon eine eindeutige ID benötigt, hängt die Funktion GetTabXml an alle IDs ein Suffix an, nämlich Form für Formulare und Report für Berichte (siehe Listing 4).

Private Function GetTabXml(ByVal strSuffix As String) As String
    Dim strXML As String
    strXML &= "        <tab id=""tabAmvAlign" & strSuffix & """ label=""Steuerelemente ausrichten"">" & vbCrLf
    strXML &= "          <group id=""grpAmvSettings" & strSuffix & """ label=""Einstellungen"">" & vbCrLf
    strXML &= "            <editBox id=""edtDistanceLabel" & strSuffix & """ label=""Abstand Bezeichnung/Feld"" " & _
              "sizeString=""99999"" maxLength=""5""" & " screentip=""Horizontaler Abstand in Twips"" " & _
              "getText=""GetDistanceLabelText"" onChange=""OnDistanceLabelChange""/>" & vbCrLf
    strXML &= "            <editBox id=""edtDistanceVertical" & strSuffix & """ label=""Vertikaler Abstand"" " & _
              "sizeString=""99999"" maxLength=""5""" & " screentip=""Vertikaler Abstand zwischen den Zeilen in " & _
              "Twips"" getText=""GetDistanceVerticalText"" onChange=""OnDistanceVerticalChange""/>" & vbCrLf
    strXML &= "            <dropDown id=""ddAlignment" & strSuffix & """ label=""Beschriftung"" " & _
              "screentip=""Textausrichtung der Bezeichnungsfelder""" & _
              " getSelectedItemIndex=""GetAlignmentIndex"" onAction=""OnAlignmentChange"">" & vbCrLf
    strXML &= "              <item id=""itmAlign0" & strSuffix & """ label=""Standard""/>" & vbCrLf
    strXML &= "              <item id=""itmAlign1" & strSuffix & """ label=""Links""/>" & vbCrLf
    strXML &= "              <item id=""itmAlign2" & strSuffix & """ label=""Zentriert""/>" & vbCrLf
    strXML &= "              <item id=""itmAlign3" & strSuffix & """ label=""Rechts""/>" & vbCrLf
    strXML &= "              <item id=""itmAlign4" & strSuffix & """ label=""Verteilen""/>" & vbCrLf
    strXML &= "            </dropDown>" & vbCrLf
    strXML &= "            <checkBox id=""chkColons" & strSuffix & """ label=""Mit Doppelpunkten"" " & _
              "screentip=""Beim Ausrichten Doppelpunkte hinter die Beschriftungen setzen""" & _
              " getPressed=""GetColonsPressed"" onAction=""OnColonsChange""/>" & vbCrLf
    strXML &= "          </group>" & vbCrLf
    strXML &= "          <group id=""grpAmvActions" & strSuffix & """ label=""Befehle"">" & vbCrLf
    strXML &= "            <button id=""btnAlign" & strSuffix & """ label=""Steuerelemente ausrichten"" " & _
              "size=""large"" imageMso=""AlignLeft""" & " screentip=""Markierte Steuerelemente ausrichten"" " & _
              "onAction=""OnAlignClicked""/>" & vbCrLf
    strXML &= "            <button id=""btnColons" & strSuffix & """ label=""Doppelpunkte anhängen"" " & _
              "size=""large"" imageMso=""AlignRight""" & " screentip=""Markierten Bezeichnungsfeldern einen " & _
              "Doppelpunkt anhängen"" onAction=""OnColonsClicked""/>" & vbCrLf
    strXML &= "          </group>" & vbCrLf
    strXML &= "        </tab>" & vbCrLf
    Return strXML
End Function

Listing 4: Die Funktion GetTabXml erzeugt das Register

Die Gruppe Einstellungen enthält zwei Elemente editBox für die Abstände, ein Element dropDown mit den fünf Einträgen Standard, Links, Zentriert, Rechts und Verteilen für die Textausrichtung der Bezeichnungsfelder sowie ein Element checkBox für die Doppelpunkte. Die Reihenfolge der Einträge in der Dropdownliste entspricht den Werten der Eigenschaft TextAlign, also 0 für Standard bis 4 für Verteilen. Die Gruppe Befehle enthält die Schaltflächen Steuerelemente ausrichten und Doppelpunkte anhängen.

Im Formularentwurf zeigt Access es hinter dem Register Format an (siehe Bild 3).

Das Register Steuerelemente ausrichten im Entwurf eines Formulars

Bild 3: Das Register Steuerelemente ausrichten im Entwurf eines Formulars

Die Schaltflächen zeigen eingebaute Symbole von Office, die wir über das Attribut imageMso angeben. Eigene Bilder binden wir nach dem Muster aus dem Beitrag Benutzerdefinierte Bilder in twinBASIC (www.access-im-unternehmen.de/1325) ein.

Access im Unternehmen

Unser exklusives Angebot für Dich!

Access im Unternehmen
13,25 € im Monat*

(Gilt für den Abschluss eines Jahres-Abonnements im ersten Jahr, danach 189,-/Jahr)

Hier geht’s weiter →

Die ersten 4 Wochen kostenlos testen – voller Zugriff auf alle Artikel, vollständigen Code und Beispieldatenbanken. Kein Risiko: Wenn es nicht passt, kündigst Du einfach innerhalb der ersten vier Wochen.

PayPal VISA Mastercard SEPA
Kostenlos & unverbindlich

Hast Du eine konkrete Frage zu Deiner eigenen Access-Anwendung?

Vielleicht stellt Deine Anwendung Dich vor eine Herausforderung, zu der Du bisher keine Lösung findest. Schlechte Performance, kein ausreichender Zugriffsschutz, Du bist unsicher über Dein Datenmodell oder Dein Code liefert unerklärliche Fehler?

In unserem kostenlosen Access-Audit schaut sich André Minhorst persönlich gemeinsam mit Dir Deine Lösung per Zoom an – und zeigt Dir, wo Datenmodell, VBA-Code, Ergonomie und Sicherheit Optimierungspotenzial bieten.

Jetzt kostenloses Access-Audit anfordern →