Manche Aufgaben im Alltag eines Access-Entwicklers wiederholen sich immer wieder und unterscheiden sich nur durch Kleinigkeiten. Zum Beispiel sehen Detailformulare, Übersichtsformulare oder auch Formulare zum Verwalten von Daten in Haupt- und Unterformular immer gleich aus – wenn auch für andere Daten. Im Beitrag „Tabellendaten mit Übersicht und Details anzeigen“ (www.access-im-unternehmen.de/1488) haben wir grundsätzlich gezeigt, wie wir ein Hauptformular mit einer Übersicht in einem Unterformular anlegen und programmieren können. Der Beitrag „Detailformular per Mausklick erstellen“ (www.access-im-unternehmen.de/10) wiederum liefert ein Beispiel dafür, wie wir ein einfaches Detailformular schnell definieren und per Mausklick erstellen können. Im vorliegenden Beitrag wollen wir beides kombinieren – also ein Übersichtsformular mit Haupt- und Unterformular in einem Konfigurationsformular einrichten und dann automatisch erstellen.
Im oben genannten Beitrag Tabellendaten mit Übersicht und Details anzeigen (www.access-im-unternehmen.de/1488) haben wir gezeigt, wie ein einfaches Formular zum Anzeigen der Übersicht über die Daten eines Formulars erstellt werden kann.
Dieses soll folgende Elemente beinhalten und sieht in der Formularansicht wie in Bild 1 aus:
Bild 1: Beispiel für ein Übersichtsformular
- Unterformular mit den anzuzeigenden Daten
- Schaltfläche zum Anlegen neuer Datensätze
- Schaltfläche zum Bearbeiten des aktuell markierten Datensatzes
- Schaltfläche zum Löschen des aktuell markierten Datensatzes
- Schaltfläche zum Schließen des Formulars
- Textfeld zum Eingeben von Suchbegriffen zum Suchen nach Datensätzen im Unterformular
Außerdem soll sich das Unterformular an die Größe des Hauptformulars anpassen, wenn der Benutzer dieses vergrößert. Zum Erstellen eines solchen Formulars wollen wir in diesem Beitrag ein Konfigurationsformular erstellen sowie den Code, der nötig ist, um das Formular auf Knopfdruck anhand der vorgenommenen Einstellungen zu erstellen.
Dabei wollen wir auch direkt die Möglichkeit schaffen, das im Beitrag Detailformular per Mausklick erstellen (www.access-im-unternehmen.de/10) erstellte Formular zum Erstellen neuer oder zum Bearbeiten vorhandener Datensätze aufzurufen beziehungsweise den Namen dieses Formulars zu diesem Zweck auszuwählen.
Wir beginnen, indem wir das Konfigurationsformular Schritt für Schritt zusammenstellen und dabei gleichzeitig den Code entwickeln, der zum Erstellen des Formulars dienen soll.
Im gleichen Zuge stellen wir auch die Tabellen zusammen, welche die Konfigurationsdaten erfassen. Wir wollen diese speichern und jederzeit erneut abrufen können.
Die dazu verwendeten Tabellen heißen tblOverviewforms und tblOverviewFields. Die Tabellen sind ähnlich aufgebaut wie in der Lösung zum Erstellen von Detailformularen. Die Tabelle tblOverviewForm sehen wir in der Entwurfsansicht in Bild 2. Sie enthält die wesentlichen Informationen zum Erstellen des Hauptformulars und der Steuerelemente im Hauptformular.
Bild 2: Tabelle für die Daten zum Erstellen von Haupt- und Unterformular
Die Tabelle tblOverviewFields enthält die Daten der Felder, die für die Übersicht im Unterformular angezeigt werden sollen (siehe Bild 3).
Bild 3: Tabelle für die Daten der Felder im Unterformular
Von der einfachen Access-Anwendung zum Add-In
Wir arbeiten wieder in zwei Schritten: Wir werden zunächst das Konfigurationsformular in der gleichen Anwendung entwickeln, in der wir auch testweise die Formulare anlegen und aus der die Tabellen oder Abfragen stammen, die als Grundlage für das Übersichtsformular dienen. Erst wenn dies erledigt ist, wandeln wir die Datenbankdatei in eine Add-In-Datenbank um.
Das Konfigurationsformular sieht in der Formularansicht wie in Bild 4 aus.
Bild 4: Formulare mit den Daten zum Erstellen des Übersichtsformulars
Es enthält oben links die Basisinformationen des zu erstellenden Formulars:
- Datensatzquelle: Tabelle oder Abfrage mit den in der Übersicht anzuzeigenden Daten
- Formularname: Name des zu erstellenden Formulars
- Formulartitel: Text für die Titelleiste des Formulars
- Rand/Abstand: Gibt die Abstände der Steuerelemente zu den Rändern und zu den anderen Steuerelementen an.
- Automatisch zentrieren: Legt den Wert für die Eigenschaft Automatisch zentrieren fest.
- Bereich Suchen-Textfeld: Hier finden wir Informationen darüber, ob ein Suchfeld angezeigt werden soll, wie die Beschriftung des Suchfeldes heißt und welcher Code ausgelöst werden soll, wenn sich der Wert des Suchen-Feldes ändert.
- Bereich OK-Button: Enthält Informationen darüber, ob eine OK-Schaltfläche angezeigt werden soll, welchen Text sie enthalten soll, welchen Code sie ausführen und welches Symbol sie enthalten soll.
- Bereich Anlegen-Button: siehe OK-Button
- Bereich Bearbeiten-Button: siehe OK-Button
- Bereich Löschen-Button: siehe OK-Button
Im unteren Bereich sehen wir schließlich noch ein Unterformular, das alle Felder der für die Eigenschaft Datensatzquelle ausgewählten Tabelle oder Abfrage aufnimmt. Hier können wir individuelle Einstellungen für diese Felder vornehmen, zum Beispiel, ob dieses Feld angezeigt oder ignoriert werden soll. Außerdem können wir die Beschriftung für die Spaltenköpfe hier abweichend von den Feldnamen und den im Tabellenentwurf gespeicherten Beschriftungen vornehmen. Mit Fieldindex geben wir die Reihenfolge an und mit FieldWidth die Breite des Feldes. Außerdem finden wir hier noch die typischen Eigenschaften für Felder, die als Kombinationsfelder ausgelegt sind und die wir hier auch nochmal individuell anpassen können.
Tabelle oder Abfrage mit den anzuzeigenden Daten auswählen
Der erste Schritt ist die Auswahl der Tabelle oder Abfrage, deren Daten im Unterformular angezeigt werden soll. Dazu benötigen wir ein Kombinationsfeld, mit dem wir alle Tabellen und Abfragen der aktuellen Datenbank auswählen können.
Dieses Kombinationsfeld wollen wir mit allen Tabellen und Abfragen füllen, die in der Tabelle MSysObjects vorhanden sind. Dabei müssen wir die Elemente erfassen, die im Feld Type einen der Werte 1, 4, 5 oder 6 enthalten und deren Wert im Feld Name nicht mit F_ oder MSys beginnt.
Allerdings können wir dazu später in der Add-In-Datenbank nicht einfach eine Abfrage zuweisen, die auf der Tabelle MSysObjects basiert. Damit würden wir nämlich auf die Tabelle MSysObjects der Add-In-Datenbank zugreifen und nicht auf die der Host-Datenbank, der wir das Formular hinzufügen wollen. Also müssen wir ein Recordset erstellen und dieses mit den entsprechenden Daten der Tabelle MSysObjects der Host-Datenbank füllen.
Das erledigen wir in der Prozedur, die durch das Ereignis Beim Laden des Formulars ausgelöst wird (siehe Listing 1).
Private Sub Form_Load() Dim db As DAO.Database Dim dbc As DAO.Database Dim rst As DAO.Recordset Dim rstIcons As DAO.Recordset Set db = CurrentDb Set dbc = CodeDb Set rst = db.OpenRecordset("SELECT Name FROM MSysObjects WHERE Type IN (1, 4, 5, 6) AND Name NOT LIKE ''F_*'' " _ & "AND Name NOT LIKE ''MSys*'' AND Name NOT LIKE ''USys*'' AND Name NOT LIKE ''~*''", dbOpenDynaset) Set Me!cboDatasource.Recordset = rst Set rstIcons = dbc.OpenRecordset("SELECT id, [Name] FROM MSysResources WHERE Extension = ''png'' ORDER BY [Name]", _ dbOpenDynaset) Set Me.cboOKIcon.Recordset = rstIcons Me!chkOKButton.DefaultValue = True Me!txtOKCaption.DefaultValue = """OK""" Me!txtOKCode.DefaultValue = """Debug.Print """"OK""""""" Me!cboOKIcon.DefaultValue = 13 Set Me!cboAddNewIcon.Recordset = rstIcons Me!chkAddNewButton.DefaultValue = True Me!txtAddNewCaption.DefaultValue = """Anlegen""" Me!txtAddNewCode.DefaultValue = """Debug.Print """"Anlegen""""""" Me!cboAddNewIcon.DefaultValue = 10 Set Me!cboDeleteIcon.Recordset = rstIcons Me!chkDeleteButton.DefaultValue = True Me!txtDeleteCaption.DefaultValue = """Löschen""" Me!txtDeleteCode.DefaultValue = """Debug.Print """"Löschen""""""" Me!cboDeleteIcon.DefaultValue = 12 Set Me!cboEditIcon.Recordset = rstIcons Me!chkEditButton.DefaultValue = True Me!txtEditCaption.DefaultValue = """Bearbeiten""" Me!txtEditCode.DefaultValue = """Debug.Print """"Bearbeiten""""""" Me!cboEditIcon.DefaultValue = 11 Me!chkAutoCenter.DefaultValue = True Me!cboButtonStyleID.DefaultValue = dbc.OpenRecordset("SELECT ButtonStyleID FROM tblButtonStyles", _ dbOpenDynaset).Fields(0) Me!txtSubformHeight.DefaultValue = 5000 Me!txtSubformWidth.DefaultValue = 10000 Me!txtMargin.DefaultValue = 50 Me!chkSearchTextbox.DefaultValue = True Me!txtSearchCaption.DefaultValue = """Suchen nach""" Me!txtSearchCode.DefaultValue = """Debug.Print """"Suchen""""""" If Not Len(Nz(Me.OpenArgs, "")) = 0 Then Me!cboDatasource = Me.OpenArgs cboDatasource_AfterUpdate End If End Sub
Listing 1: Aktionen beim Starten des Formulars frmOverviewForms
Die Prozedur definiert zwei verschiedene Database-Objekte, von denen das erste mit CurrentDb das Database-Objekt der Host-Datenbank referenziert und das zweite mit CodeDb das Database-Objekt der Add-In-Datenbank. Solange wir noch mit einer Datenbank entwickeln, beziehen sich beide Objektvariablen auf das gleiche Database-Objekt. Mit der Variablen rst referenzieren wir die Datensätze der Tabelle MSysObjects der Host-Datenbank für das Kombinationsfeld der Datensatzquellen. Dieses weisen wir der Recordset-Eigenschaft des Kombinationsfeldes cboDatasource zu.
Das Recordset rstIcons stellen wir auf die Tabelle MSysResources der Add-In-Datenbank ein. Diese Tabelle enthält die Icons, die wir den Schaltflächen der zu erstellenden Formulare zuweisen wollen.
Das Recordset weisen wir den Kombinationsfeldern cboOKIcon, cboAddNewIcon, cboDeleteIcon und cboEditIcon zu.
Außerdem stellt die Prozedur beim Laden Standardwerte für die Kontrollkästchen und Textfelder der Schaltflächen ein.
Auswählen einer Datensatzquelle für das zu erstellende Übersichtsformular
Der erste Schritt beim Erstellen ist die Auswahl einer Datensatzquelle, auf deren Basis das Übersichtsformular erstellt werden soll. Das Auswählen eines der Einträge des Kombinationsfeldes cboDatasource löst das Ereignis Nach Aktualisierung aus, für das wir die Prozedur aus Listing 2 hinterlegen.
Private Sub cboDatasource_AfterUpdate() If ReadOverviewFieldsFromTable(Me!cboDatasource) = True Then Me!sfmOverviewforms.Form.Requery End If If Len(Nz(Me!txtFormname, "")) = 0 Then Me!txtFormname = Replace(Replace(Me!cboDatasource, "tbl", "frm"), "qry", "frm") End If If Len(Nz(Me!txtUnterformularname, "")) = 0 Then Me!txtUnterformularname = Replace(Me!txtFormname, "frm", "sfm") End If If Len(Nz(Me!txtFormTitle, "")) = 0 Then Me!txtFormTitle = Replace(Replace(Me!cboDatasource, "tbl", ""), "qry", "") End If End Sub
Listing 2: Aktualisieren des Kombinationsfeldes cboDatasource
Die Prozedur ruft eine weitere Funktion namens ReadOverviewFieldsFromTable auf, der wir die gewählte Tabelle oder Abfrage übergeben und die wir gleich beschreiben. Hat diese Funktion die Felder erfolgreich ausgelesen und in die Tabelle tblOverviewFields geschrieben, wird das Unterformular sfmOverviewforms aktualisiert.
Wenn txtFormname noch nicht gefüllt ist, trägt die Prozedur automatisch den Namen der Datenquelle ein, wobei vorher Präfixe wie tbl oder qry durch frm ersetzt werden. Das Unterformular erhält den gleichen Namen, allerdings wird das Präfix frm durch sfm ersetzt.
Für den Titel wird der gleiche Ausdruck eingetragen, jedoch werden die Präfixe tbl und qry ersatzlos gestrichen.
Eintragen der Feldnamen für die Übersicht im Unterformular
Die im Unterformular anzuzeigenden Felder werden erstmalig ausgelesen, sobald der Benutzer die Datensatzquelle selektiert hat. Dies löst die Prozedur cboDatasource_AfterUpdate und in der Folge die Funktion ReadOverviewFieldsFromTable aus. Diese Funktion nimmt den Namen der Tabelle oder Abfrage entgegen, die als Datensatzquelle gewählt wurde (siehe Listing 3). Dann referenziert sie die Host- und die Add-In-Database-Objekte mit den Variablen db und dbc.
Public Function ReadOverviewFieldsFromTable(strDataSource As String) As Boolean Dim db As DAO.Database, dbc As DAO.Database Dim rst As DAO.Recordset, rstSource As DAO.Recordset Dim bolReadFields As Boolean, lngControlType As AcControlType Dim varRowSourceType As Variant, varRowSource As Variant, varBoundColumn As Variant Dim varCaption As Variant, varColumnCount As Variant, varColumnWidths As Variant Dim fld As DAO.Field, i As Long Set db = CurrentDb Set dbc = CodeDb Set rst = dbc.OpenRecordset("SELECT * FROM tblOverviewfields WHERE OverviewformID = " & Me!OverviewformID, _ dbOpenDynaset) If Not rst.EOF Then rst.MoveLast End If bolReadFields = True Me.Dirty = False If Not rst.RecordCount = 0 Then bolReadFields = MsgBox("Sollen die Felder neu eingelesen werden?", vbYesNo + vbExclamation, _ "Felder neu einlesen?") = vbYes End If If bolReadFields = True Then dbc.Execute "DELETE FROM tblOverviewfields WHERE OverviewformID = " & Me!OverviewformID, dbFailOnError Set rstSource = db.OpenRecordset(strDataSource, dbOpenDynaset) For Each fld In rstSource.Fields Call GetControlProperties(fld, varCaption, intControlType, varBoundColumn, varRowSourceType, _ varRowSource, varColumnCount, varColumnWidths) rst.AddNew rst!OverviewformID = Me!txtOverviewFormID rst!FieldName = fld.Name rst!Fieldindex = i rst!FieldWidth = 2000 rst!FieldHeight = 300 rst!FieldColumn = 1 rst!Fieldlabel = Nz(varCaption, fld.Name) rst!ControlType = lngControlType rst!RowSourceType = varRowSourceType rst!RowSource = varRowSource rst!BoundColumn = varBoundColumn rst!ColumnCount = varColumnCount rst!ColumnWidths = varColumnWidths rst.Update i = i + 1 Next fld ReadOverviewFieldsFromTable = True Me!sfmOverviewforms.Form.Requery Else ReadOverviewFieldsFromTable = False End If End Function
Listing 3: Aktualisieren der Felder für das Übersichtsformular
Für die Add-In-Datenbank referenziert sie mit der Variablen rst die Datensätze der Tabelle tblOverviewFields, die über das Fremdschlüsselfeld OverviewFormID mit dem aktuellen Primärschlüsselwert des Hauptformulars verbunden sind. Findet die Funktion hier bereits Datensätze, verschiebt sie den Datensatzzeiger auf den letzten Datensatz, um mit rst.RecordCount die Anzahl der Datensätze zu ermitteln. Dann stellt sie die Variable bolReadFields auf True und speichert alle Änderungen im Formular.
Sind nun bereits Datensätze vorhanden, fragt die Funktion den Benutzer, ob diese neu eingelesen werden sollen. Antwortet der Benutzer mit Ja, behält bolReadFields den Wert True und die Felder werden nachfolgend neu ausgelesen. Anderenfalls werden die Felder nicht neu eingelesen.
Im Falle des Neueinlesens löscht die Funktion zunächst alle Einträge der Tabelle tblOverviewfields, die dem aktuell im Formular angezeigten Datensatz der Tabelle tblOverviewForms zugeordnet sind.
Dann stellt sie das Recordset rstSource auf die mit strDataSource übergebene Tabelle oder Abfrage ein. Anschließend durchläuft sie alle Felder dieses Recordsets. Dabei ruft sie für jedes Feld die Funktion GetControlProperties auf und übergibt dieser mit fld einen Verweis auf das Feld sowie die Variablen varCaption, intControlType, varBoundColumn, varRowSourceType, varRowSource, varColumnCount und varColumnWidths. Diese Funktion, die wir weiter unten beschreiben, liest die Werte für die Variant-Parameter aus den Eigenschaften des Field-Objekts aus fld aus.
Die folgenden Anweisungen tragen einige Informationen in das Recordset auf Basis der Tabelle tblOverviewfields ein. Dazu gehört der Primärschlüsselwert des aktuell im Hauptformular angezeigten Datensatzes der Tabelle tblOverviewForms, damit die Daten dem jeweils zu erstellenden Formular zugeordnet werden können.
Außerdem trägt sie den Feldnamen, den Index, den Wert 2000 als Feldbreite, die Beschriftung, den Steuerelementinhalt und einige Eigenschaften bezüglich eventuell in der Tabelle definierter Nachschlagefelder ein. Dazu gehören ControlType, RowSourceType, RowSource, BoundColumn, ColumnCount und ColumnWidths. Damit wird für jedes Feld der Datensatzquelle ein Datensatz in der Tabelle tblOverviewfields gespeichert und das Unterformular zur Anzeige dieser Felder aktualisiert.
Einlesen der Feldeigenschaften
Die soeben angesprochene Funktion GetControlProperties liest die verschiedenen Eigenschaften des mit dem Parameter fld übergebenen Feldes ein und liefert diese in Rückgabeparametern zurück (siehe Listing 4).
Public Function GetControlProperties(fld As DAO.Field, varCaption As Variant, intControlType As AcControlType, _ varBoundColumn As Variant, varRowSourceType As Variant, varRowSource As Variant, varColumnCount As Variant, _ varColumnWidths As Variant) intControlType = 0 varCaption = Null varBoundColumn = Null varRowSourceType = Null varRowSource = Null varColumnCount = Null varColumnWidths = Null On Error Resume Next varCaption = fld.Properties("Caption") intControlType = fld.Properties("DisplayControl") Select Case intControlType Case acTextBox Debug.Print fld.Name, "acTextBox" Case acComboBox varBoundColumn = fld.Properties("BoundColumn") varRowSourceType = fld.Properties("RowSourceType") varRowSource = fld.Properties("RowSource") varColumnCount = fld.Properties("ColumnCount") varColumnWidths = fld.Properties("ColumnWidths") Case acCheckBox Debug.Print fld.Name, "acCheckBox" Case Else Debug.Print "anderer Controltype", intControlType End Select On Error GoTo 0 End Function
Listing 4: Einlesen der Feldeigenschaften
Einige Eigenschaften von Feldern kann man einfach so einlesen, aber bei einigen ist dies nicht so einfach möglich: Es werden nämlich nicht alle Eigenschaften für jedes Feld angelegt. Beispielweise wird die Eigenschaft Caption nur für ein Feld hinterlegt, wenn der Benutzer die Eigenschaft Beschriftung über den Tabellenentwurf eingestellt hat. Anderenfalls ist die Property namens Caption schlicht nicht vorhanden und der Versuch, diese auszulesen, löst einen Fehler aus.
Daher lesen wir diese Werte bei deaktivierter Fehlerbehandlung ein. Das ist der Fall bei der Eigenschaft Caption, aber auch bei DisplayControl. Letztere legt fest, ob ein Feld im Datenblatt als Textfeld, Kontrollkästchen oder Kombinationsfeld angezeigt wird. Im Falle eines Kombinationsfeldes stehen noch einige weitere Eigenschaften zur Verfügung. Um also herauszufinden, ob wir diese auch einlesen müssen, lesen wir zuerst den Wert der Eigenschaft DisplayControl ein. Lautet dieser acComboBox, lesen wir auch noch die nun verfügbaren Eigenschaften BoundColumn, RowSourceType, RowSource, ColumnCount und ColumnWidths in die entsprechenden Rückgabeparameter ein. Im Falle von acTextBox oder acCheckBox müssen keine weiteren Parameter eingelesen werden.
Übersichtsformulare per Code erstellen
Damit haben wir alle Informationen erfasst, die wir zum Erstellen des Übersichtsformulars benötigen und können nun den Code zusammenstellen. Dabei beginnen wir mit dem Unterformular, dass die Daten der Tabelle in der Datenblattansicht anzeigen soll. Wenn wir dieses erstellt haben, fügen wir den Code zum Erstellen des Hauptformulars hinzu. Dieses wird schließlich auch ein Unterformularsteuerelement enthalten, das wir mit dem Unterformular füllen.
Funktion zum Erstellen eines neuen Formulars
Wir verwenden eine einfache Funktion zum Erstellen der beiden Formulare. Diese heißt CreateNewForm und erwartet den Namen des zu erstellenden Formulars als einzigen Parameter. Wie daran gut zu erkennen ist, erledigt die Funktion nichts anderes, als ein neues Formular zu erstellen (siehe Listing 5). Sie prüft mit der Hilfsfunktion ExistsForm, ob bereits ein Formular mit dem angegebenen Namen vorhanden ist:
Public Function CreateNewForm(strName As String) As Boolean Dim frm As Form Dim strNameTemp As String If ExistsForm(strName) Then If MsgBox("Formular ''" & strName & "'' ist bereits vorhanden. Überschreiben?", vbYesNo + vbExclamation, _ "Formular überschreiben") = vbYes Then If DeleteForm(strName) = False Then Exit Function End If Else Exit Function End If End If Set frm = CreateForm() strNameTemp = frm.Name frm.Visible = True DoCmd.Close acForm, frm.Name, acSaveYes DoCmd.Rename strName, acForm, strNameTemp CreateNewForm = True End Function
Listing 5: Funktion zum Erstellen von Formularen
Public Function ExistsForm(strForm As String) As Boolean Dim objForm As AccessObject For Each objForm In CurrentProject.AllForms If objForm.Name = strForm Then ExistsForm = True Exit Function End If Next objForm End Function
Falls ja, fragt die Funktion, ob das Formular überschrieben werden soll. Antwortet man mit Ja, versucht die Routine, das Formular mit einer weiteren Funktion namens DeleteForm zu löschen:
Public Function DeleteForm(strName As String) As Boolean On Error Resume Next DoCmd.DeleteObject acForm, strName If Err.Number = 0 Then DeleteForm = True Else MsgBox Err.Description, vbCritical + vbOKOnly, _ "Löschen fehlgeschlagen" End If End Function
Gelingt dies nicht, wird die Funktion verlassen und liefert den Wert False zurück. Auch wenn man auf die Frage, ob ein bestehendes Formular überschrieben werden soll, mit Nein antwortet, endet die Funktion mit dem Rückgabewert False.
Anderenfalls ruft die Funktion die Anweisung CreateForm auf und speichert den Verweis auf das neu erstellte Formular in der Variablen frm.
Sie trägt den von Access vergebenen Namen in die Variable strNameTemp ein. Das Formular wird dann geschlossen und die Funktion ändert noch den Namen vom temporären Namen in den als Parameter übergebenen Namen.
Starten der Formularerstellung
Die eigentliche Formularerstellung mit dem Aufruf der zuvor beschriebenen Funktionen erfolgt, wenn der Benutzer die Schaltfläche Erstellen anklickt. Für die Ereignisprozedur Beim Klicken hinterlegen wir die Prozedur aus Listing 6.
Private Sub cmdCreate_Click() Dim frm As Form RunCommand acCmdSaveRecord If CreateNewForm(Me!txtUnterformularname) = True Then DoCmd.OpenForm Me!txtUnterformularname, acDesign Set frm = Forms(Me!txtUnterformularname) With frm .DefaultView = acDefViewDatasheet .RecordSource = Me!cboDatasource .HasModule = True AddControlsOverviewSubform Me!OverviewformID End With End If ... End Sub
Listing 6: Prozedur zum Erstellen der Formulare, hier zunächst für das Unterformular
Diese speichert zunächst den aktuell im Formular angezeigten Datensatz. Nach dem Aufruf der weiter oben bereits beschriebenen Funktion CreateForm sollte das angehende Unterformular nun angelegt sein.
Dieses öffnen wir nun mit der DoCmd.OpenForm-Methode und referenzieren es mit der Objektvariablen frm. Nun stellen wir einige Eigenschaften für dieses Formular ein:
- DefaultView: Legt mit acDefViewDatasheet fest, dass das Formular in der Datenblattansicht angezeigt werden soll.
- RecordSource: Wird auf die aus Me!cboDatasource ausgelesene Datensatzquelle, also die Tabelle oder Abfrage, eingestellt.
- HasModule: Legt mit dem Wert True fest, dass das Unterformular ein Klassenmodul enthalten soll.
Außerdem rufen wir für dieses Formular die Prozedur AddControlsOverviewSubform auf. Dieser Prozedur übergeben wir den Primärschlüsselwert des Datensatzes der Tabelle tblOverviewforms, für den wir die Steuerelemente hinzufügen wollen.
Steuerelemente zum Unterformular hinzufügen
Die Prozedur AddControlsOverviewSubform unterstützt uns beim Hinzufügen der Steuerelemente zum Unterformular, die in der Tabelle tblOverviewfields festgelegt wurden (siehe Listing 7).
Public Sub AddControlsOverviewSubform(lngOverviewformID As Long) Dim dbc As DAO.Database Dim rstControls As DAO.Recordset Dim rstForm As DAO.Recordset Dim strFormName As String Dim ctl As Control Dim lngMaxWidth As Long Dim lngLabelWidth As Long Dim lngTop As Long Set dbc = CodeDb Set rstForm = dbc.OpenRecordset("SELECT * FROM tblOverviewforms WHERE OverviewformID = " & lngOverviewformID, _ dbOpenDynaset) strFormName = rstForm!Subformname Set dbc = CodeDb Set rstControls = dbc.OpenRecordset("SELECT * FROM tblOverviewfields WHERE OverviewformID = " _ & lngOverviewformID, dbOpenDynaset) lngLabelWidth = GetMaxLabelWidth(rstControls) lngTop = rstForm!Margin Do While Not rstControls.EOF If rstControls!IgnoreField = False Then Select Case rstControls!ControlType Case acTextBox Set ctl = AddTextBox(rstForm, rstControls, strFormName, acDetail, lngLabelWidth, lngTop, _ lngMaxWidth) Case acComboBox Set ctl = AddComboBox(rstForm, rstControls, strFormName, acDetail, lngLabelWidth, lngTop, _ lngMaxWidth) Case acCheckBox Set ctl = AddCheckBox(rstForm, rstControls, strFormName, acDetail, lngLabelWidth, lngTop, _ lngMaxWidth) Case Else Set ctl = AddTextBox(rstForm, rstControls, strFormName, acDetail, lngLabelWidth, lngTop, lngMaxWidth) End Select lngTop = lngTop + ctl.Height + rstForm!Margin ctl.ColumnWidth = rstControls!FieldWidth End If rstControls.MoveNext Loop End Sub
Listing 7: Prozedur zum Hinzufügen der Steuerelemente zum Unterformular
Sie erwartet den Primärschlüsselwert des Eintrags der Tabelle tblOverviewforms und weitere Parameter. Sie referenziert mit dem Recordset rstForm die Tabelle tblOverviewforms mit dem Hauptdatensatz für das zu erstellende Formular und ermittelt daraus den Namen des zu erstellenden Unterformulars.
Dann referenziert sie die zu diesem Formular gehörenden Datensätze der Tabelle tblOverviewfields mit einem weiteren Recordset namens rstControls.
Auch wenn dies in diesem Unterformular in der Datenblattansicht nicht unbedingt nötig ist, holt die Prozedur mit der Funktion GetMaxLabelWidth die maximale Breite für die hinzuzufügenden Steuerelemente (siehe Listing 8). In dieser Funktion ermitteln wir mithilfe der Wizhook-Klasse die Breiten für die Beschriftungen der abzubildenden Felder. Dabei nehmen wir ein Vorlagenformular zu Hilfe, das ein entsprechendes Label-Feld enthält, von dem wir die Schriftarteigenschaften für die Berechnung der Länge der Texte heranziehen. Die Funktion durchläuft die Beschriftungen und ermittelt jeweils die Breite des Textes für die angegebenen Schrifteigenschaften. Ist die Breite für einen Text breiter als die bisher breiteste, wird dieser als der aktuell breiteste Wert gespeichert. Auf diese Weise erhalten wir am Ende den größten Wert für die Breite der Beschriftungen. Nachdem diese Breite ermittelt wurde, holen wir aus dem Feld Margin den Abstand zum oberen Rand und legen diesen als lngTop fest. Dann durchläuft die Prozedur alle Steuerelemente der Tabelle tblOverviewfields und prüft zuerst anhand der Eigenschaft IgnoreField, ob überhaupt ein Steuerelement für dieses Feld angelegt werden soll. Falls ja, prüfen wir in einer Select Case-Bedingung, welches Steuerelement angelegt werden soll. Auch, wenn es sich um ein Unterformular in der Datenblattansicht handelt, können neben Textfeldern auch Kombinationsfelder und Kontrollkästchen auftauchen.
Public Function GetMaxLabelWidth(rst As DAO.Recordset) Dim frm As Form Dim lbl As Label Dim lngBold As Long Dim lngWidthText As Long Dim lngHeight As Long Dim lngWidthMax As Long DoCmd.OpenForm "frmFontStyles" Set frm = Forms!frmFontStyles Set lbl = frm!lblFontStyle WizHook.Key = 51488399 Do While Not rst.EOF With lbl If .FontBold Then lngBold = 700 Else lngBold = 400 End If WizHook.TwipsFromFont .FontName, .FontSize, lngBold, .FontItalic, .FontUnderline, 0, _ rst!Fieldlabel & ":", 0, lngWidthText, lngHeight End With If lngWidthText > lngWidthMax Then lngWidthMax = lngWidthText End If rst.MoveNext Loop rst.MoveFirst GetMaxLabelWidth = lngWidthMax DoCmd.Close acForm, "frmFontStyles" End Function
Listing 8: Funktion zum Ermitteln der Breite der breitesten Beschriftung
Textfelder anlegen
Im Falle von acTextBox rufen wir die Funktion AddTextBox auf (siehe Listing 9). Dieser übergeben wir die notwendigen Informationen – das Recordset rstForm, das Recordset rstControls mit den hinzuzufügenden Feldern, den Namen des Formulars, den Bereich, in dem die Steuerelemente eingefügt werden sollen und so weiter.
Public Function AddTextBox(rstForm As DAO.Recordset, rstControls As DAO.Recordset, strFormName As String, _ lngSection As AcSection, lngLabelWidth As Long, lngTop As Long, lngMaxWidth As Long) As Control Dim ctl As Control Dim lbl As Label Set ctl = CreateControl(strFormName, acTextBox, lngSection, , rstControls!FieldName, lngLabelWidth + 2 * _ rstForm!Margin, lngTop, rstControls!FieldWidth, Nz(rstControls!FieldHeight, 300)) Set lbl = CreateControl(strFormName, acLabel, lngSection, ctl.Name, , rstForm!Margin, lngTop, lngLabelWidth, _ Nz(rstControls!FieldHeight, 300)) lbl.Caption = rstControls!Fieldlabel & ":" If ctl.Left + ctl.Width > lngMaxWidth Then lngMaxWidth = ctl.Left + ctl.Width End If Set AddTextBox = ctl End Function
Listing 9: Funktion zum Hinzufügen einer Textbox
Die Routine verwendet die Funktion CreateControl, um dem Formular das Formular mit den entsprechenden Eigenschaften hinzuzufügen. Zusätzlich zum Textbox-Steuerelement wird auch noch ein Label-Steuerelement erstellt, das später in der Datenblattansicht die Spaltenüberschrift beisteuert. Außerdem legt die Routine noch die Beschriftung des Label-Steuerelements fest und stellt die Position der Steuerelemente fest, damit diese untereinander eingefügt werden.
Kontrollkästchen anlegen
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