Gelegentlich ist es sinnvoll, Daten eines Datensatzes aus einer Tabelle in einem Objekt auf Basis einer Klasse abzulegen – vor allem dann, wenn Sie nur auf einen kompakten Satz von Daten zugreifen möchten und diese nicht ändern wollen. Leider ist der nötige Code je nach Anzahl der Felder der Datenherkunft und somit der Eigenschaften recht umfangreich und die Erstellung eine mühselige Arbeit. Mit dem in diesem Beitrag vorgestellten Add-In soll dies ganz schnell von der Hand gehen.
Die Beispieldatenbank ist ein fertiges Add-In. Zur Installation starten Sie den Add-In-Manager und wählen die Datei Klassengenerator.mda aus. Anschließend finden Sie im Add-In-Menü den Eintrag aiuKlassengenerator. Es erscheint der Dialog aus Bild 1. Hier lesen Sie entweder die Daten aller Tabellen neu ein (was beim ersten Öffnen in einer Datenbank automatisch geschieht) und wählen per Kombinationsfeld die Tabelle oder Abfrage aus, zu der Sie eine Klasse erstellen möchten. Das Unterformular zeigt dann alle Felder dieser Datenherkunft. Legen Sie dort fest, welche Felder als Eigenschaften der Klasse berücksichtigt werden sollen, und passen Sie gegebenenfalls von den Feldnamen abweichende Eigenschaftsnamen an. Klicken Sie dann auf Erzeugen, um die komplette Klasse auf Basis der Datenherkunft in Sekundenbruchteilen zu erstellen. Nach Wunsch fügen Sie dem Code auch gleich eine Prozedur hinzu, die eine Routine zum Erstellen und Füllen einer Instanz dieser Klasse erstellt. Die erzeugten Quelltexte müssen Sie nur noch in die Zielmodule kopieren.
Bild 1: Der Klassengenerator in Aktion
Datenmodell
Das Add-In soll die Auswahl einer Tabelle oder Abfrage ermöglichen, für deren Felder eine Klasse angelegt werden soll. Für diese Tabelle soll das Add-In alle Felder anzeigen. Der Benutzer legt fest, welche Felder berücksichtigt werden sollen und wie die entsprechenden Eigenschaften in der Klasse heißen sollen.
Das Add-In soll die Namen der Tabellen und Abfragen in der Datenbank, von der aus das Add-In gestartet wurde, in einem Kombinationsfeld zur Auswahl anbieten. Der direkte Zugriff von einem Kombinationsfeld innerhalb eines Add-Ins auf die Daten der Host-Datenbank ist nicht möglich, also schreiben wir die notwendigen Daten zuvor in eine Tabelle innerhalb der Add-In-Datenbank.
Diese Tabelle enthält die Felder TabelleAbfrageID (Primärschlüsselfeld, Autowert), TabelleAbfrage, Datenbank, Klassenname, Primaerschluesselfeld und Objekttyp. TabelleAbfrage nimmt die Namen der in der Datenbank enthaltenen Tabellen und Abfragen auf (s. Bild 2). Datenbank speichert den Namen der Zieldatenbank, damit einmal definierte Schemata zur Erzeugung von Klassen erneut verwendet werden können. Das Feld Klassenname nimmt den Namen auf, unter dem die Klasse für diese Tabelle oder Abfrage erzeugt werden soll – also beispielsweise clsPerson. Primaerschluesselfeld enthält den Namen des entsprechenden Feldes der Tabelle. Bei Tabellen wird dieser später automatisch ermittelt, bei Abfragen muss er manuell eingetragen werden. Objekttyp nimmt den Zahlenwert des Feldes Type der Tabelle MSysObjects auf – dazu später mehr.
Bild 2: Diese Tabelle speichert die Namen der Tabellen und Abfragen der Zieldatenbank.
Die zweite Tabelle heißt tblFelder (s. Bild 3). Sie enthält neben dem Primärschlüsselfeld FeldID folgende Felder:
Bild 4: Beziehung der beiden Tabellen des Add-Ins
- Feldname: Name des Feldes in der Tabelle
- Eigenschaftsname: Name der Eigenschaft für das Feld in der Klasse
- Erzeugen: Gibt an, ob das Feld beim Erzeugen des Klassencodes berücksichtigt werden soll
- TabelleAbfrageID: Fremdschlüsselfeld zur Tabelle tblTabellenAbfragen
- Datentyp: Nummer des Datentyps des Feldes
Die Beziehung zwischen den beiden Tabellen ist mit referentieller Integrität definiert und Löschweitergabe. Wenn also ein Datensatz aus der Tabelle tblTabellenAbfragen gelöscht wird, entfernt Access auch die mit dem entsprechenden Datensatz verknüpften Datensätze der Tabelle tblFelder (s. Bild 4).
Bild 3: Die Tabelle tblFelder speichert die Felder und ihre Einstellungen.
Formular des Add-Ins
Damit der Benutzer die Tabelle/Abfrage für die Generierung der Klasse auswählen und die Felder festlegen kann, die das Add-In dabei berücksichtigen soll, zeigt das Add-In eine entsprechende Benutzeroberfläche in Form eines Formulars an. Dieses heißt frmKlassengenerator und bietet zunächst ein Kombinationsfeld zur Auswahl der zu verwendenden Tabelle oder Abfrage an. Außerdem soll das Hauptformular jeweils einen Datensatz der Tabelle tblTabelleAbfrage anzeigen – und zwar denjenigen, der vom Benutzer mit dem im Anschluss beschriebenen Kombinationsfeld ausgewählt wurde. Dazu stellen Sie für das Formular die Eigenschaft Datenherkunft auf die Tabelle tblTabelleAbfrage ein. Ziehen Sie außerdem die beiden Felder Datenbank und Klassenname aus der Feldliste in den Entwurf des Formulars. Lassen Sie darüber noch ein wenig Platz für ein Kombinationsfeld zur Auswahl der Tabelle/Abfrage (s. Bild 5). Wir schauen uns zunächst das Kombinationsfeld an und den Mechanismus, der die Datensatzherkunft des Kombinationsfeldes füllt und filtert.
Bild 6: Kombinationsfeld zur Auswahl der Tabellen und Abfragen der Zieldatenbank
Das Kombinationsfeld soll alle Tabellen der aktuellen Datenbank wie in Bild 6 anzeigen. Damit dieses überhaupt Daten anzeigt, weisen Sie diesem zunächst eine entsprechende Datenherkunft zu – und zwar die Tabelle tblTabellenAbfragen. Damit das Kombinationsfeld das Primärschlüsselfeld der Tabelle ausblendet und nur die Namen der Tabellen und Abfragen anzeigt, stellen Sie die beiden Eigenschaften Spaltenanzahl und Spaltenbreiten auf die Werte 2 und 0cm ein. Nachdem Sie in der Formularansicht geprüft haben, dass das Kombinationsfeld die Werte wie gewünscht anzeigt, können Sie die Eigenschaft Datensatzherkunft wieder leeren – wir füllen diese später zur Laufzeit. Immerhin soll das Kombinationsfeld ja nur die Tabellen und Abfragen der aktuellen Datenbank anzeigen. Die Tabelle tblTabellenAbfragen soll jedoch nach der Verwendung des Add-Ins in verschiedenen Anwendungen auch die entsprechenden Daten zu den jeweiligen Anwendungen enthalten.
Bild 5: Entwurfsansicht des aktuellen Stands des Formulars frmKlassengenerator
Deshalb löst das Formular beim Öffnen das Ereignis Beim Laden aus und somit die Ereignisprozedur aus Listing 1. Diese Prozedur speichert zunächst den Namen der Zieldatenbank in der Variablen strDatenbank (beim Erstellen und Testen des Add-Ins öffnen Sie dieses direkt und nicht von einer anderen Anwendung aus, daher enthält strDatenbank hier den Namen der Add-In-Datenbank selbst).
Listing 1: Füllen des Kombinationsfeldes beim Öffnen des Formulars
Private Sub Form_Load() Dim strDatenbank As String strDatenbank = CurrentProject.Name If IsNull(FLookup("TabelleAbfrageID", "tblTabellenAbfragen", "Datenbank = ''" & strDatenbank _ & "''")) Then TabellenAbfragenEinlesen End If Me!cboTabelleAbfrage.RowSource = "SELECT * FROM tblTabellenAbfragen WHERE Datenbank = ''" _ & strDatenbank & "''" End Sub
Ein Aufruf der Funktion FLookup mit den entsprechenden Parametern prüft, ob es in der Tabelle tblTabellenAbfragen bereits mindestens einen Datensatz gibt, dessen Feld Datenbank den Namen der aktuellen Datenbank enthält. Falls ja, wird die Prozedur TabellenAbfragenEinlesen aufgerufen, welche die Tabelle tblTabellenAbfragen mit den Namen der Tabellen und Abfragen der Zieltabelle füllt. Anschließend stellt die Prozedur die Datensatzherkunft des Kombinationsfeldes cboTabelleAbfrage auf eine SQL-Abfrage ein, die alle Datensätze der Tabelle tblTabellenAbfragen liefert, deren Feld Datenbank den Namen der aktuellen Zieldatenbank enthält (beziehungsweise im Standalone-Modus den Namen der Add-In-Datenbank).
Warum nutzen wir hier eine Funktion namens FLookup – heißt diese nicht üblicherweise DLookup Das ist richtig. FLookup ist ein Nachbau der DLookup-Funktion, die allerdings auf eine Datensatzgruppe zugreift, die sich in der mit CodeDB referenzierten Datenbank befindet. DLookup greift, auch von einem Add-In aus, immer auf die Tabellen oder Abfragen der Datenbank zu, die das Add-In geöffnet hat.
Tabellen und Abfragen einlesen
Nun sind beim ersten Start des Formulars noch keine Tabellen oder Abfragen in der Tabelle tblTabellenAbfragen gespeichert. Also ruft die Prozedur Form_Load auf jeden Fall die Prozedur TabellenAbfragenEinlesen auf (s. Listing 2). Diese Prozedur deklariert gleich zwei Database-Objekte. Das erste heißt dbHost und wird mit der Funktion CurrentDb gefüllt. Dies liefert einen Verweis auf die aktuell in Access geöffnete Datenbank. Wenn Sie das Add-In von einer anderen Datenbank aus geöffnet haben, handelt es sich um die öffnende Datenbank, wenn Sie das Add-In direkt geöffnet haben, wird hier die Add-In-Datenbank selbst referenziert. Die zweite Variable namens dbAddIn erhält ihren Verweis auf das Database-Objekt über die Funktion CodeDB. Dieses liefert immer einen Verweis auf die Datenbank, in welcher sich der ausführende Code befindet.
Listing 2: Füllen der Tabelle tblTabellenAbfragen
Private Sub TabellenAbfragenEinlesen() Dim dbHost As DAO.Database Dim dbAddIn As DAO.Database Dim rst As DAO.Recordset Dim strKlassenname As String Set dbHost = CurrentDb Set dbAddIn = CodeDb Set rst = dbHost.OpenRecordset("SELECT Name, Type FROM MSysObjects WHERE Type IN (1,4,5,6) " _ & "AND Name NOT LIKE ''~*'' ORDER BY Name", dbOpenDynaset) Do While Not rst.EOF strKlassenname = KlassennameErmitteln(rst!Name) dbAddIn.Execute "INSERT INTO tblTabellenAbfragen(TabelleAbfrageName, Datenbank, Klassenname) " _ & "VALUES(''" & rst!Name & "'', ''" & CurrentProject.Name & "'', ''" & strKlassenname & "'')", _ dbFailOnError rst.MoveNext Loop Me.Requery Me!cboTabelleAbfrage.Requery Set dbHost = Nothing Set dbAddIn = Nothing End Sub
Mit diesen beiden Verweisen ausgestattet öffnet die Prozedur eine Datensatzgruppe auf Basis der Tabelle MSysObjects der Zieldatenbank. Die Datensatzgruppe liefert das Feld Name aller Datensätze zurück, deren Feld Type den Wert 1, 4, 5 oder 6 enthält und deren Name nicht mit ~ beginnt (dies sind Abfragen, die als Wert der Eigenschaften Datenherkunft beziehungsweise Datensatzherkunft gespeichert, aber nicht im Datenbankfenster oder Navigationsbereich angezeigt werden). Der Wert 1 für das Feld Type gibt an, dass es sich um eine eingebaute Tabelle handelt. 4 kennzeichnet eine per ODBC verknüpfte Tabelle, 5 eine Abfrage und 6 eine per Jet verknüpfte Tabelle – also beispielsweise eine externe Excel- oder Access-Tabelle oder Textdatei.
In einer Do While-Schleife über diese Datensätze trägt die Prozedur für jede Tabelle oder Abfrage einen Datensatz in die Tabelle tblTabellenAbfragen ein. Dazu ermittelt diese mit der Funktion KlassennameErmitteln den Namen der zu erzeugenden Klasse – mehr dazu weiter unten. Eine INSERT INTO-Anweisung füllt die Felder TabelleAbfrageName, Datenbank und Klassenname mit den ermittelten Werten. Anschließend aktualisiert die Prozedur die Datenherkunft des Formulars und des Kombinationsfeldes.
Felder der gewählten Datenherkunft anzeigen
Ein Unterformular namens sfmKlassengenerator liefert die Daten zu den in der Tabelle/Abfrage enthaltenen Feldern. Nach der Anzeige der Tabellen und Abfragen der aktuellen Datenbank und der Auswahl eines der Objekte durch den Benutzer soll das Unterformular alle Felder dieser Datenherkunft in der Datenblattansicht anzeigen. Dazu erstellen Sie zunächst ein Unterformular namens sfmKlassengenerator. Weisen Sie seiner Eigenschaft Datenherkunft die Tabelle tblFelder zu. Ziehen Sie die drei Felder Feldname, Eigenschaftsname und Erzeugen in den Detailbereich des Formularentwurfs und stellen Sie die Eigenschaft Standardansicht des Formulars auf Datenblatt ein (s. Bild 7).
Bild 7: Unterformular zur Anzeige der Felder der Tabelle oder Abfrage
Speichern Sie das Formular, öffnen Sie das Hauptformular frmKlassengenerator in der Entwurfsansicht und ziehen Sie das Unterformular sfmKlassengenerator aus dem Datenbankfenster beziehungsweise dem Navigationsbereich in den Detailbereich des Hauptformulars. Das Ergebnis sieht nun etwa wie in Bild 8 aus. Nun sorgen Sie dafür, dass nach der Auswahl einer Tabelle oder Abfrage durch den Benutzer die entsprechenden Felder im Unterformular angezeigt werden. Dazu müssen diese zunächst einmal in die Tabelle tblFelder eingetragen werden – dies ist bislang noch nicht geschehen.
Bild 8: Hauptformular mit integriertem Unterformular in der Entwurfsansicht
Dazu fügen Sie für das Kombinationsfeld cboTabelleAbfrage eine neue Ereignisprozedur für das Ereignis Nach Aktualisierung hinzu (s. Listing 3). Diese Prozedur verwendet wiederum zwei Objektvariablen des Typs Database – eines für die Add-In-Datenbank und eines für die aufrufende Datenbank. Die Prozedur prüft zunächst, ob der Benutzer überhaupt einen Eintrag im Kombinationsfeld cboTabelleAbfrage ausgewählt hat.
Listing 3: Füllen der Tabelle tblFelder
Private Sub cboTabelleAbfrage_AfterUpdate() Dim dbHost As DAO.Database Dim dbAddIn As DAO.Database Dim rst As DAO.Recordset Dim fld As DAO.Field Dim bolEinlesen As Boolean If Not IsNull(Me!cboTabelleAbfrage) Then bolEinlesen = True If Not IsNull(FLookup("FeldID", "tblFelder", "TabelleAbfrageID = " & Me!cboTabelleAbfrage)) Then bolEinlesen = _ MsgBox("Felder neu einlesen und vorhandene Daten überschreiben", vbYesNo) = vbYes End If If bolEinlesen Then Set dbHost = CurrentDb Set dbAddIn = CodeDb dbAddIn.Execute "DELETE FROM tblFelder WHERE TabelleAbfrageID = " _ & Me!cboTabelleAbfrage, dbFailOnError Set rst = dbHost.OpenRecordset("SELECT * FROM " & Me!cboTabelleAbfrage.Column(1) _ & " WHERE 1=2", dbOpenDynaset) For Each fld In rst.Fields dbAddIn.Execute "INSERT INTO tblFelder(Feldname, Eigenschaftsname, " _ "TabelleAbfrageID, Erzeugen, Datentyp) VALUES(''" & fld.Name & "'', ''" _ & fld.Name & "'', " & Me!cboTabelleAbfrage & ", -1, " & fld.Type & ")", _ dbFailOnError Next fld End If Me!sfmKlassengenerator.Form.Requery Me.Filter = "TabelleAbfrageID = " & Me!cboTabelleAbfrage Me.FilterOn = True End If End Sub
Falls ja, prüft eine weitere Bedingung, ob die Tabelle tblFelder bereits mindestens einen Datensatz enthält, der mit der ausgewählten Tabelle/Abfrage verknüpft ist. Falls ja, fragt die Prozedur den Benutzer, ob die Felder neu eingelesen und somit die gegebenenfalls bereits angepassten Eigenschaftsnamen und die Werte des Feldes Erzeugen zurückgesetzt werden sollen. Wenn der Benutzer hier auf Nein klickt, aktualisiert die Prozedur nur die Anzeige der Felder der gewählten Datenherkunft. Man könnte hier noch weiter ins Detail gehen und prüfen, ob sich die Anzahl oder die Eigenschaften der Felder geändert haben, aber das würde den Rahmen des Beitrags sprengen.
Hat der Benutzer jedoch auf Ja geklickt oder sind noch keine Daten für diese Tabelle/Abfrage vorhanden, füllt die Prozedur die Variablen dbHost und dbAddIn mit den entsprechenden Verweisen und führt eine Aktionsabfrage aus, die alle Einträge der Tabelle tblFelder zur aktuellen Tabelle oder Abfrage löscht.
Danach erstellt die Prozedur ein neues Recordset-Objekt auf Basis der mit cboTabelleAbfrage ausgewählten Tabelle oder Abfrage. Da wir nur die Felder ermitteln möchten, benötigen wir keine Daten – dementsprechend wird als Kriterium der Ausdruck 1=2 eingestellt, der dafür sorgt, dass die Datensatzgruppe keine Daten enthält.
Nun durchläuft die Prozedur alle Field-Elemente der Fields-Auflistung des Recordset-Objekts und trägt diese mithilfe einer INSERT INTO-Aktionsabfrage in die Tabelle tblFelder ein. Dabei füllt sie das Feld Eigenschaftsname ebenfalls mit dem Feldnamen und trägt für das Feld TabelleAbfrageID die entsprechende ID des mit dem Kombinationsfeld cboTabelleAbfrage ausgewählten Eintrags ein.
Schließlich aktualisiert die Prozedur die im Unterformular sfmKlassengenerator angezeigten Daten. Damit dies funktioniert, müssen Sie das Unterformular etwas anpassen – genauer gesagt das Unterformular-Steuerelement. Damit das Unterformular nur solche Datensätze anzeigt, die zu dem im Steuerelement cboTabelleAbfrage ausgewählten Eintrag passen, stellen Sie die Eigenschaften Verknüpfen von und Verknüpfen nach des Unterformulars wie in Bild 9 auf die Werte TabelleAbfrageID und cboTabelleAbfrage ein.
Bild 9: Unterformular nach der ausgewählten Tabelle oder Abfrage filtern
Dadurch zeigt das Unterformular nur diejenigen Datensätze an, deren Fremdschlüsselfeld TabelleAbfrageID mit dem im Kombinationsfeld cboTabelleAbfrage ausgewählten Wert übereinstimmt (s. Bild 10). Hier kann der Benutzer nun ansetzen und einige Einstellungen vornehmen, die im weiteren Verlauf interessant werden. So kann er hier etwa die in der Klasse zu verwendenden Bezeichnungen für die Eigenschaften auf Basis der Feldnamen anpassen und mit dem Ja/Nein-Feld Erzeugen grundsätzlich festlegen, ob ein Feld überhaupt verwendet werden soll.
Bild 10: Anzeige der Felder der gewählten Datenherkunft
Einstellungen für die Erzeugung der Klasse
Ein Element einer Klasse für eine Tabelle oder Abfrage besteht aus drei Teilen: der Deklaration der privaten Variablen zum Speichern des Feldwertes, der Public Property Set/Let-Prozedur zum Einstellen des Eigenschaftswertes und der Public Property Let-Prozedur zum Auslesen des Eigenschaftswertes (siehe clsPerson):
Dim m_Vorname As String Public Property Let Vorname(strVorname As String) m_Vorname = strVorname End Property Public Property Get Vorname() As String Vorname = m_Vorname End Property
Die Verwendung dieser Klasse sieht etwa wie folgt aus:
Public Sub Test_Klasse() Dim objPerson As clsPerson Set objPerson = New clsPerson With objPerson .Vorname = "André" Debug.Print .Vorname End With Set objPerson = Nothing End Sub
Die meisten hier verwendeten Informationen lassen sich beim Einlesen der Eigenschaften der Tabelle und der enthaltenen Felder ermitteln. Einige sollen jedoch gleich automatisch angepasst werden, andere erst beim Zusammenstellen des Codes der Klasse.
Der Klassenname etwa wird aus dem Tabellen- oder Abfragenamen abgeleitet. Dort soll das Präfix (etwa tbl oder qry) durch das Präfix cls ersetzt werden. Und die Member-Variablen (wie m_Vorname) werden aus den Feldnamen abgeleitet und um ein Präfix wie m_ erweitert. Wo aber legt der Benutzer diese Informationen fest Theoretisch könnte man es sich leicht machen und diese Daten einfach im Code verankern. Allerdings sollten diese Einstellungen durchaus flexibel vorgenommen werden können, weshalb wir dafür eine entsprechende Optionen-Tabelle einrichten, deren Werte der Benutzer nach Belieben einstellen kann.
Diese Tabelle heißt tblOptionen und sieht im Entwurf wie in Bild 11 aus. Die Felder haben die folgende Funktion:
Bild 11: Entwurf der Tabelle tblOptionen
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