Klassengenerator

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.

pic015.png

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.

pic001.png

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:

pic003.png

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).

pic004.png

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.

pic005.png

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.

pic002.png

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).

pic006.png

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.

pic007.png

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.

pic008.png

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.

pic009.png

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:

pic010.png

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

Schreibe einen Kommentar