Möchtest Du den gesamten Artikel lesen? Und vielleicht sogar den Artikel im PDF-Format und die Beispieldateien herunterladen? Dann hole Dir den Artikel gleich hier - völlig kostenlos!
Im Artikel “VBA: Early Binding und Late Binding” (www.vbentwickler.de/4) haben wir die beiden Methoden Early Binding und Late Binding vorgestellt und ihre Vor- und Nachteile beschrieben. Im vorliegenden Artikel zeigen wir nun eine automatische Lösung, um schnell einige oder alle per Early Binding definierten Elemente nach Late Binding zu migrieren. Dazu nutzen wir Code, der zunächst alle Early Binding-Elemente ermittelt, diese in einem Formular anzeigt und dann die Möglichkeit bietet, diese in Late Binding-Elemente umzuwandeln.
Die Lösung aus diesem Artikel soll es ermöglichen, alle Module der aktuellen Datenbank aus einem Listenfeld auszuwählen und alle dort in Deklarationszeilen vorhandenen Klassen, Typen und Enumerationen einzulesen.
Wir benötigen zwar die Typen und Enumerationen nicht, da wir diese nicht nach Late Binding migrieren können, aber aus technischen Gründen können wir diese nicht ohne erheblichen Aufwand aus der Ermittlung ausschließen.
Das Formular zur Steuerung dieses Vorgangs sehen wir in Bild 1. Hier haben wir das Modul mdlTest ausgewählt und anschließend auf die Schaltfläche Typen einlesen geklickt, um alle dort enthaltenen Typen zu ermitteln und in einem weiteren Listenfeld anzuzeigen.

Bild 1: Formular zum Steuern der Migration nach Late Binding
Wenn wir nun einen oder mehrere dieser Einträge markieren und auf die Schaltfläche Early Binding ersetzen klicken, werden die als Early Binding deklarierten Elemente samt der zur Initialisierung verwendeten Anweisungen in Late Binding umgewandelt – und zwar in allen Modulen, die im oberen Listenfeld markiert sind.
Zusätzlich finden wir dort noch ein Kontrollkästchen namens Ersetzte Zeilen als Kommentar behalten. Damit können wir festlegen, dass die Early Binding-Anweisungen nicht gelöscht, sondern lediglich auskommentiert werden.
Im Beispielmodul mdlTest haben wir einige mit Early Binding versehene Anweisungen untergebracht – und zwar in den unterschiedlichsten Ausprägungen:
- als einfache Deklarationszeilen,
- als Parameter von Prozeduren,
- als Deklaration in Prozeduren,
- als Rückgabewert von Prozeduren und
- mit und ohne Zeilenumbrüche (siehe Bild 2).

Bild 2: Beispielanweisungen mit Early Binding
Nachdem wir die beiden Typen ADODB.Connection und ADODB.Recordset mit unserem Formular umgestellt haben, sieht der abgebildete Ausschnitt des Moduls wie in Bild 3 aus.

Bild 3: Beispielanweisungen mit Late Binding und auskommentierter Early Binding-Version
Beschreibung des Formulars
Im Formular finden wir in der Entwurfsansicht die folgenden Steuerelemente (siehe Bild 4):

Bild 4: Das Formular frmEarlyBindingToLateBinding in der Entwurfsansicht
- txtSucheModule: Erlaubt das schnelle Filtern der im Listenfeld lstModule angezeigten Module.
- Schaltfläche cmdAlleAuswahlen: Markiert alle Einträge im Listenfeld lstModules.
- Listenfeld lstModules: Zeigt die gefundenen Module an, die den jeweiligen Typ enthalten.
- Schaltfläche cmdTypenEinlesen: Liest alle Typen der markierten Module ein und zeigt diese im Listenfeld lstTypes an.
- Textfeld txtSucheTypen: Filtert das Listenfeld lstTypes nach dem eingegebenen Suchbegriff.
- Schaltfläche cmdAlleTypenAuswaehlen: Markiert alle Einträge des Listenfeldes lstTypes.
- Listenfeld lstTypes: Zeigt alle gefundenen Typen in den markierten Modulen an.
- Schaltfläche cmdEarlyBindingErsetzen: Ersetzt für alle markierten Typen in den markierten Modulen Early Binding durch Late Binding.
- Kontrollkästchen chkErsetzeZeilenAlsKommentarBehalten: Gibt an, ob die ersetzten Zeilen auskommentiert oder einfach ersetzt werden sollen.
Ereignis beim Laden des Formulars
Beim Laden des Formulars wird das Ereignis aus Listing 1 ausgelöst. Es referenziert die aktuelle Datenbank mit der CodeDb-Funktion (dies ist eine Vorbereitung, um die Lösung als Add-In zu nutzen). Dann löscht es die beiden Tabellen tblModules und tblTypes, in denen wir die ermittelten Daten speichern, um sie in den Listenfeldern anzuzeigen. tblModules enthält das Primärschlüsselfeld ModulID und das Textfeld Modul. Die Tabelle tblTypes enthält die beiden Felder TypeID und Type sowie Line und LineNumber, um jeweils eine Zeile zu speichern, in der dieser Typ auftritt (diese wurden eher zu Testzwecken während der Programmierung der Lösung genutzt).
Private Sub Form_Load() Dim db As DAO.Database Dim objVBProject As VBIDE.VBProject Dim objVBComponent As VBIDE.VBComponent Set db = CodeDb db.Execute "DELETE * FROM tblModules", dbFailOnError db.Execute "DELETE * FROM tblTypes", dbFailOnError Set objVBProject = CurrentVBProject For Each objVBComponent In objVBProject.VBComponents db.Execute "INSERT INTO tblModules(Modul) VALUES(''" & objVBComponent.name & "'')", dbFailOnError Next objVBComponent Me.lstModules.Requery Me.lstTypes.Requery If Not IsNull(Me.OpenArgs) Then Dim i As Integer Dim strVBComponent As String strVBComponent = Me.OpenArgs For i = 0 To Me.lstModules.ListCount - 1 If Me.lstModules.Column(1, i) = strVBComponent Then Me.lstModules.Selected(i) = True Call cmdTypenEinlesen_Click End If Next i End If End Sub
Listing 1: Ereignisprozedur, die beim Laden des Formulars ausgelöst wird
Danach holen wir mit der Funktion CurrentVBProject einen Verweis auf das VBA-Projekt der aktuellen Datenbank. Dies ist notwendig, da beim Vorhandensein von Access-Add-Ins oder eingebundenen Bibliotheksdatenbanken sonst gegebenenfalls das falsche VBA-Projekt verwendet wird. Die Funktion CurrentVBProject sieht wie folgt aus:
Public Function CurrentVBProject() As VBIDE.VBProject Dim objVBProject As VBIDE.VBProject For Each objVBProject In VBE.VBProjects If objVBProject.FileName = CurrentDb.name Then Set CurrentVBProject = objVBProject Exit Function End If Next objVBProject End Function
Sie durchläuft alle vorhandenen VB-Projekte und prüft, ob der Pfad dem Pfad der aktuell geöffneten Access-Datenbank entspricht. Ist das der Fall, wird der Verweis auf dieses VB-Projekt zurückgegeben.
Für die Verwendung dieser und anderer nachfolgend genutzten Elemente, die auf den VBA-Editor und seine Module zugreifen, fügen wir dem VBA-Projekt einen Verweis auf die Bibliothek Microsoft Visual Basic for Applications Extensibility 5.3 Object Library hinzu.
Die Prozedur Form_Load durchläuft nun alle Elemente der Auflistung VBComponents, was den Modulen entspricht. Für jedes Modul wird ein Eintrag in der Tabelle tblModules angelegt. Danach werden die beiden Listenfelder lstModules und lstTypes aktualisiert, damit sie den aktuellen Inhalt der beiden Tabellen tblModules und tblTypes anzeigen.
Schließlich haben wir noch den Fall vorbereitet, dass das Formular direkt für ein bestimmtes Modul aufgerufen wird. Dann würden wir das Argument OpenArgs mit dem Namen des gewünschten Moduls füllen. Dies wird dann direkt im Listenfeld lstModules markiert.
Der Aufruf des Formulars für diesen Fall sieht wie folgt aus:
DoCmd.OpenForm "frmEarlyBindingToLateBinding", _ OpenArgs:="mdlTest"
Auswählen aller Module
Um alle Module auszuwählen, klicken wir auf die Schaltfläche cmdAlleTypenAuswaehlen. Diese durchläuft alle Elemente des Listenfeldes und stellt die Eigenschaft Selected für den jeweiligen Index auf True ein:
Private Sub cmdAlleTypenAuswaehlen_Click() Dim lngItem As Long For lngItem = 0 To Me.lstTypes.ListCount - 1 Me.lstTypes.Selected(lngItem) = True Next lngItem End Sub
Einlesen der Typen der markierten Module
Ein Klick auf die Schaltfläche cmdTypenEinlesen soll alle Typen der markierten Module ermitteln und in die Tabelle tblTypes schreiben.
Dazu referenziert sie wieder die Datenbank mit dem Formular und leert die Tabelle tblTypes. Dann prüft sie, ob überhaupt Einträge im Listenfeld lstModules markiert sind, und weist darauf hin, falls das nicht der Fall ist.
Danach durchläuft sie alle markierten Einträge des Listenfeldes lstModules über die ItemsSelected-Auflistung und ermittelt mit Column(1, var) den Namen des jeweiligen Moduls. Innerhalb der Schleife ruft sie für jedes dieser Elemente die Prozedur TypesToTable auf und übergibt dieser den Namen des Moduls. Schließlich aktualisiert sie die Liste der Typen.
Private Sub cmdTypenEinlesen_Click() Dim var As Variant Dim strModule As String Dim db As DAO.Database Set db = CodeDb DoCmd.Hourglass True db.Execute "DELETE * FROM tblTypes", dbFailOnError If Me.lstModules.ItemsSelected.Count = 0 Then MsgBox "Markiere die zu untersuchenden Module.", _ vbOKOnly + vbExclamation, "Kein Modul markiert" Exit Sub End If For Each var In Me.lstModules.ItemsSelected strModule = Me.lstModules.Column(1, var) Call TypesToTable(strModule) Next var Me.lstTypes.Requery DoCmd.Hourglass False End Sub
Auslesen der Typen eines Moduls
Die Prozedur TypesToTable referenziert wieder die Code-Datenbank und das aktuelle VBA-Projekt (siehe Listing 2).
Public Sub TypesToTable(strModule As String) Dim db As DAO.Database Dim objVBProject As VBIDE.VBProject Dim objVBComponent As VBIDE.VBComponent Dim objCodeModule As VBIDE.CodeModule Dim strLine As String Dim strProc As String Dim lngProcType As Long Dim lngProcBodyLine As Long Dim strLineOriginal As String Dim lngLine As Long Dim lngProcline As Long Set db = CodeDb Set objVBProject = CurrentVBProject Set objVBComponent = objVBProject.VBComponents(strModule) Set objCodeModule = objVBComponent.CodeModule For lngLine = 1 To objCodeModule.CountOfLines strLineOriginal = objCodeModule.Lines(lngLine, 1) strLine = Trim(strLineOriginal) strLine = KommentarAbschneiden(strLine) strLine = TextAusLiteralenEntfernen(strLine) ...
Listing 2: Einlesen der Typen (Teil 1)
Danach füllt sie die Variable objVBComponent mit einem Verweis auf das übergebene Modul und holt einen weiteren Verweis auf das enthaltene CodeModule-Objekt, das in objCodeModule landet.
Dann durchläuft sie in einer For…Next-Schleife alle Codezeilen, wobei die letzte Zeile mit der Eigenschaft CountOfLines ermittelt wird.
Hier speichert sie die Originalzeile in strLineOriginal und in strLine die um führende und folgende Leerzeichen bereinigte Version der Zeile.
Für diese ruft sie nun die Funktion KommentarAbschneiden auf, die wir ebenfalls im Modul finden und die alle am Ende der Zeile befindlichen Kommentare aus strLine entfernt.
Eine weitere Funktion namens TextAusLiteralenEntfernen leert eventuell in Anführungszeichen vorhandene Texte. Aus der Zeile strText = “Beispieltext” wird dann zum Beispiel strText = “”.
Dies dient der Vereinfachung, damit eventuell in Literalen enthaltene Bezeichnungen der gesuchten Typen nicht berücksichtigt werden. Die Ersetzung schlägt sich nicht auf die späteren Änderungen nieder.
Danach steigen wir in eine Select Case-Bedingung ein, die verschiedene Aspekte überprüft (siehe Listing 3). Der erste Case-Zweig prüft, ob die Anweisung der aktuellen Zeile mit Const, Public Const oder Private Const beginnt. Solche Zeilen sollen nicht berücksichtigt werden, da Konstanten keine der für uns interessanten Typen aufnehmen können.
...
Select Case True
Case Left(strLine, 5) = "Const", Left(strLine, 12) = "Public Const", Left(strLine, 13) = "Private Const"
Case Else
lngProcline = lngLine
Do While Right(strLine, 1) = "_"
lngProcline = lngProcline + 1
strLine = Left(strLine, Len(strLine) - 1)
strLine = strLine & Trim(objCodeModule.Lines(lngProcline, 1))
Loop
If Not InStr(1, strLine, " As ") = 0 Then
If Not Len(strLine) = 0 Then
strProc = ""
strProc = objCodeModule.ProcOfLine(lngLine, lngProcType)
If Not Len(strProc) = 0 Then
lngProcBodyLine = objCodeModule.ProcBodyLine(strProc, lngProcType)
Else
If Split(strLine, " ")(0) = "Event" Or Split(strLine, " ")(1) = "Event" _
Or Split(strLine, " ")(0) = "Declare" Or Split(strLine, " ")(1) = "Declare" Then
lngProcBodyLine = lngLine
End If
End If
If lngProcBodyLine = lngLine Then
Call ProzedurparameterVerarbeiten(db, strLine, strLineOriginal, lngLine)
Call RueckgabewertVerarbeiten(db, strLine, strLineOriginal, lngLine)
Else
Select Case True
Case Left(strLine, 3) = "Dim", Left(strLine, 6) = "Public", Left(strLine, 7) = "Private"
Call DeklarationenVerarbeiten(db, strLine, strLineOriginal, lngLine)
End Select
End If
End If
End If
lngLine = lngProcline
End Select
DoEvents
Next lngLine
End Sub
Listing 3: Einlesen der Typen (Teil 2)
Der zweite Case-Zweig bearbeitet alle übrigen Zeilen, wo wir als Erstes eventuell auf mehrere Zeilen aufgeteilte Anweisungen in eine Zeile schreiben. Aus
Public Sub TestParameterCrLf( _ rst As ADODB.Recordset)
wird dann beispielsweise
Public Sub TestParameterCrLf(rst As ADODB.Recordset)
Dazu holen wir uns als Erstes die Nummer der aktuellen Zeile und speichern diese in lngProcLine. Solange die Zeile mit dem Unterstrich endet und die Anweisung somit in der nächsten Zeile fortgesetzt wird, erhöhen wir lngProcLine um den Wert 1 und lesen den Teil der Zeile vor dem Unterstrich in strLine ein. Aus
Public Sub TestParameterCrLf( _
wird somit die folgende Zeile:
Public Sub TestParameterCrLf(
Dann fügen wir den Inhalt der folgenden Zeile zu strLine hinzu. Diesen Vorgang wiederholen wir, bis die letzte Zeile der Anweisung erreicht ist. Später stellen wir lngLine auf lngProcLine ein, damit die Zeilenfortsetzungen nicht erneut eingelesen werden.
Nun untersuchen wir weitere Aspekte. Zunächst prüfen wir, ob die Zeile das Schlüsselwort As enthält. Nur in diesem Fall kann überhaupt eine Deklaration für einen Typ vorliegen.
Als Nächstes prüfen wir, ob es sich bei der aktuellen Zeile um eine einfache Deklaration oder um einen Prozedurkopf handelt. Dazu ermitteln wir mit der Funktion ProcOfLine, ob sich die aktuelle Zeile innerhalb einer Prozedur befindet. In diesem Fall liefert die Funktion den Namen der Prozedur zurück, den wir in strProc speichern. Ist strProc nicht leer, befindet sich die Zeile innerhalb einer Prozedur. Um herauszufinden, ob es sich bei der Zeile um den Prozedurkopf handelt, ermitteln wir mit ProcBodyLine die Nummer der Zeile, in der sich der Prozedurkopf der aktuellen Prozedur befindet. Ist strProc leer, handelt es sich um eine Prozedurkopf-ähnliche Anweisung, etwa ein Event oder eine API-Deklaration. Auch diese wollen wir wie einen Prozedurkopf behandeln und speichern auch hier die Zeilennummer in lngProc.
Wenn die aktuelle Zeile aus lngLine nun dem Wert aus lngProc entspricht, handelt es sich um einen Prozedurkopf oder eine Event– oder Declare-Anweisung.
In diesem Fall rufen wir eine weitere Prozedur namens ProzedurparameterVerarbeiten auf und übergeben die relevanten Parameter. Diese Prozedur beschreiben wir im Anschluss. Außerdem rufen wir noch die Prozedur RueckgabewertVerarbeiten auf, denn auch die Rückgabewerte einer Prozedur können Typen enthalten, die wir umwandeln wollen.
Ist es keine Prozedurkopfzeile, handelt es sich um eine Deklarationszeile. Dies prüfen wir noch, indem wir untersuchen, ob die ersten Zeichen der Anweisung Dim, Public oder Private lauten. In diesem Fall rufen wir die Prozedur DeklarationenVerarbeiten auf.
Auf diese Weise haben wir nun alle Anweisungen auf Typen untersucht. In den aufgerufenen Prozeduren werden die Typen in die Tabelle tblTypes eingetragen.
Deklarationszeilen analysieren
Wir schauen uns diese Verarbeitung beispielhaft an der Prozedur DeklarationenVerarbeiten an (siehe Listing 4).
Public Function DeklarationenVerarbeiten(db As DAO.Database, strLine As String, strLineOriginal As String, _ lngLine As Long) As String Dim intDeclaration As Integer Dim strDeclarations() As String Dim strDeclaration As String Dim lngStartAs As Long Dim strLineBehindAs As String Dim strType As String strDeclarations = Split(strLine, ",") For intDeclaration = LBound(strDeclarations) To UBound(strDeclarations) strDeclaration = strDeclarations(intDeclaration) strDeclaration = Replace(strDeclaration, " New ", " ") lngStartAs = InStr(strDeclaration, " As ") If Not lngStartAs = 0 Then strLineBehindAs = Mid(strDeclaration, InStr(1, strDeclaration, " As ") + 4) strType = Split(strLineBehindAs, " ")(0) Call SaveTypesToTable(db, strType, strLineOriginal, lngLine) End If Next intDeclaration End Function
Listing 4: Verarbeiten der Deklarationszeilen
Diese splittet die Deklarationszeile zunächst am Komma auf und schreibt die einzelnen Elemente in ein Array. Dies berücksichtigt Deklarationen wie die folgende:
Dim rst As ADODB.Recordset, cnn As ADODB.Connection
Das Array durchlaufen wir in einer For…Next-Schleife. Hier schreiben wir zunächst das aktuelle Element in die Variable strDeclaration, zum Beispiel Dim rst As ADODB.Recordset. Wir prüfen, ob das Element das Schlüsselwort New enthält und ersetzen dieses durch eine leere Zeichenkette.
Dann ermitteln wir die Position des Schlüsselwortes As. Wenn diese nicht 0 ist, lesen wir den Teil hinter dem Schlüsselwort As ein, zum Beispiel ADODB.Recordset, und tragen diesen Typ in strLineBehindAs ein. Dieser kann wiederum aus mehreren Elementen bestehen, wobei uns nur das erste Element interessiert. Dieses landet in der Variablen strType.
Mit der Prozedur SaveTypesToTable tragen wir diesen Typ in die Tabelle tblTypes ein und bearbeiten anschließend eventuell weitere in dieser Zeile enthaltene Deklarationen.
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
