Bei der Arbeit mit Microsoft Access gibt es immer wiederkehrende Aufgaben – zum Beispiel das Anlegen von Detailformularen. Diese sollen die Daten aus einfachen Tabellen darstellen und zwei Schaltflächen namens OK und Abbrechen bereitstellen. So kann der Benutzer neue oder geänderte Datensätze übernehmen oder diese verwerfen. Dazu sind immer wieder viele kleine Handgriffe nötig. Damit dies ab jetzt schneller geht, schauen wir uns an, wie wir die meisten der Schritte automatisieren können. Dazu bauen wir ein Formular, mit dem wir alle Konfigurationsschritte erledigen können – von der Auswahl der Datenquelle über die Benennung des Formulars bis hin zur Erstellung des vollständigen Formulars inklusive Code.
Viele Formulare sind eigentlich immer gleich aufgebaut. Für die herkömmliche Bearbeitung von Daten benötigt man ein Detailformular, das die Felder eines Datensatzes anzeigt und zwei Schaltflächen bietet, mit denen der Benutzer die eingegebenen Daten übernehmen oder verwerfen kann.
Im Artikel Tabellendaten mit Übersicht und Details anzeigen (www.access-im-unternehmen.de/1488) zeigen wir, welche Handgriffe alle nötig sind, um ein solches Formular manuell zu erstellen.
Im vorliegenden Beitrag stellen wir eine Lösung vor, mit der wir die Daten aus beliebigen Tabellen schnell in ein Formular umwandeln können.
Ein Beispiel für eine Tabelle, auf deren Basis wir ein Formular erstellen wollen, sehen wir in Bild 1.
Bild 1: Beispieltabelle tblMitglieder
Formulare und Tabellen zur Steuerung der Erstellung der Formulare
Wir wären nicht in Access, wenn wir nicht auch für das Erstellen von Formularen per Code die dazu benötigten Daten für die Konfiguration in Tabellen speichern und die Konfiguration in Formularen bearbeiten würden.
Aber welche Daten müssen wir hier überhaupt erfassen?
- Die wichtigste Information ist: Aus welcher Tabelle sollen die Daten überhaupt stammen?
- Wie soll das zu erstellende Formular heißen?
- Sollen Navigationsschaltflächen, Datensatzmarkierer oder Bildlaufleisten angezeigt werden – und soll einer oder mehrere Datensätze angesteuert werden können?
- Welche Felder der Tabelle sollen im Detailformular angezeigt werden? Und in welcher Reihenfolge und Anordnung? Welche Breite sollen die Felder aufweisen?
- Wie sollen die Schaltflächen OK und Abbrechen aussehen und welche Funktionen sollen sie auslösen? Sollen sie Icons anzeigen?
Allein die Einstellung dieser Eigenschaften erfordert ein technisch anspruchsvolles Formular. Aber zuerst schauen wir uns an, welche Tabellen wir zum Erfassen dieser Daten benötigen.
Vorbereitungen im Datenmodell
Damit wir möglichst nah am Endergebnis arbeiten können, müssen wir bereits im Tabellenentwurf ein wenig Vorarbeit leisten. Wenn ein Feldname nicht dem Text entspricht, der nachher als Beschriftung in der Datenblattansicht und im Detailformular verwendet werden soll, müssen wir den gewünschten Text für die Eigenschaft Beschriftung für die jeweiligen Felder im Tabellenentwurf eintragen. Außerdem wollen wir für Fremdschlüsselfelder, auf deren Basis Datensätze aus anderen Tabellen ausgewählt werden sollen, direkt Nachschlagfelder definieren. Die damit festgelegten Eigenschaften können wir dann direkt in das zu erstellende Formular übernehmen, genau wie das auch beim Hinzufügen von Feldern aus der Feldliste in manuell erstellten Formularen gelingt.
Das Konfigurationsformular im Überblick
Das von uns zum Erstellen von Detailformularen verwendete Formular sehen Sie in Bild 2. Es bietet die Möglichkeit, verschiedene Konfigurationen zu speichern – wir können also Formulare auf Basis unterschiedlicher Tabellen oder Abfragen definieren und diese immer wieder neu erstellen. Das Formular erlaubt die Eingabe des Namens des zu erstellenden Detailformulars, die Auswahl der Datensatzquelle, also einer Tabelle oder Abfrage, das Festlegen eines Formulartitels und die Angabe eines einheitlichen Abstandes. Dieser wird angewendet für den Abstand aller Steuerelemente von den Formularrändern und für den Abstand zwischen den Steuerelementen. Die Eigenschaft Anzahl Spalten haben wir vorerst nur vorbereitet, sie erfüllt noch keinen Zweck.
Bild 2: Formular zum Konfigurieren neuer Detailformulare
Rechts sehen wir einige typische Formulareigenschaften wie Automatisch zentrieren, Datensatzmarkierer, Navigationsschaltflächen, Bildlaufleisten oder Zyklus.
Darunter sehen wir zwei Bereiche, in denen wir die Eigenschaften für die beiden Schaltflächen OK und Abbrechen einstellen können. Hier geben wir an, ob die jeweilige Schaltfläche überhaupt angelegt werden soll. Dann legen wir fest, welchen Text diese anzeigen und welchen Code sie ausführen sollen. Schließlich können wir auch noch ein Icon auswählen und festlegen, ob die Funktion der OK-Schaltfläche auch durch die Eingabetaste ausgelöst werden soll und die Funktion der Abbrechen-Schaltfläche durch die Escape-Taste.
Darunter sehen wir die Möglichkeit, einen Button-Style auszuwählen. Ein Klick auf die Schaltfläche rechts daneben zeigt den Dialog aus Bild 3 an. Hier können wir einige grundlegende Einstellungen für das Aussehen von Schaltflächen vornehmen.
Bild 3: Formular zum Einstellen einiger Schaltflächeneigenschaften
Die Einstellung der Schriftarten haben wir ein wenig rudimentärer gestaltet. Ein Klick auf die Schaltfläche Schrifteigenschaften … öffnet ein Formular in der Entwurfsansicht, das schlicht ein Bezeichnungsfeld anzeigt. Für dieses können wir hier die gewünschten Eigenschaften wie Schriftart, Schriftgröße, Farbe et cetera einstellen. Nach dem Speichern und Schließen holt sich das Formular zum Erstellen neuer Detailformulare die Eigenschaften für die Schrift aus diesem Formular.
Schließlich sehen wir im Unterformular noch die Liste der Felder der zugrunde liegenden Datensatzquelle. Diese wird nach der Auswahl der Tabelle oder Abfrage automatisch aufgrund der Daten der Datensatzquelle gefüllt.
Wir können hier je Feld verschiedene Eigenschaften einstellen. Die erste lautet IgnoreField und erlaubt es, ein Feld durch das Setzen eines Hakens aus dem zu erstellenden Formular auszuschließen. Fieldlabel nimmt entweder, soweit vorhanden, den Wert der Eigenschaft Beschriftung aus dem Tabellenentwurf auf oder den Namen des Feldes. Fieldindex gibt die Reihenfolge an, in welcher die Felder angelegt werden sollen. Die Breite können wir individuell einstellen, genauso wie die Höhe. Bei Feldern mit dem Felddatentyp Langer Text kann es sinnvoll sein, höhere Felder zu erstellen. FieldColumn ist ein vorbereitender Wert, falls wir noch mehrspaltige Detailformulare unterstützen. ControlType wird mit dem Steuerelementtyp entsprechend des Feldes aus der Tabelle gefüllt – normalerweise sind dies Textfelder (109), gelegentlich aber auch Kombinationsfelder (111) oder Kontrollkästchen (106). Die übrigen Eigenschaften sind Nachschlagefeldern vorbehalten und werden ebenfalls aus dem Tabellenentwurf entnommen. Wir können diese aber hier auch nachrüsten.
Detailformular erstellen
Ein Klick auf die Schaltfläche Erstellen sorgt schließlich dafür, dass ein Formular auf Basis der getätigten Vorgaben erstellt wird (siehe Bild 4). Allein die akkurate Ausrichtung der Steuerelemente würde sonst einige Schritte erfordern, genau wie die manuelle Einstellung der verschiedenen Eigenschaften und des Hinzufügens der Schaltflächen mit dem dahinter liegenden VBA-Code.
Bild 4: Ein automatisch erstelltes Detailformular
Tabellen der Lösung
Die Lösung verwendet aktuell drei Tabellen zum Speichern von Konfigurationsdaten. Die Tabelle tblDetailforms speichert die grundlegenden Daten je Formular. Die Tabelle tblDetailfields ist über das Fremdschlüsselfeld DetailFormID mit dieser Tabelle verbunden. Schließlich gibt es noch die Tabelle tblButtonStyles mit den grundlegenden Designeinstellungen für die Schaltflächen. Der jeweilige Style wird über das Feld ButtonStyleID der Tabelle tblDetailforms selektiert (siehe Bild 5).
Bild 5: Datenmodell des Add-Ins
Planung der Lösung als Access-Add-In
Wir wollen die Lösung dieses Beitrags später als Access-Add-In bereitstellen, damit wir sie in beliebigen Datenbanken nutzen und damit Detailformulare erstellen können. Daher müssen wir an manchen Stellen etwas aufpassen. Zum Beispiel müssen wir unterschieden, ob wir im Code und in den Steuerelementen auf Daten der Add-In-Datenbank zugreifen oder auf die der Host-Datenbank, also der Datenbank, von der aus wir das Access-Ad-In aufgerufen haben.
Programmierung des Formulars frmDetailforms
Das Formular frmDetailforms soll beim Laden zunächst die Daten für die Kombinationsfelder bereitstellen.
Die durch das Ereignis Beim Laden ausgelöste Prozedur liest zuerst alle Tabellen und Abfragen der Datenbank ein, von der aus das Access-Add-In gestartet wurde (siehe Listing 1). Dies müssen wir mit dem Database-Objekt der Host-Datenbank erledigen, die wir mit CurrentDb referenzieren. Hier greifen wir auf die Tabelle MSysObjects zu, die alle Objekte der Datenbank enthält. Wir filtern hier nach den Werten 1, 4, 5 und 6 im Feld Type und erhalten so alle Tabellen und Abfragen. Das so ermittelte Recordset weisen wir dem Kombinationsfeld cboDatasource zu.
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!cboOKButtonIcon.Recordset = rstIcons Set Me!cboCancelButtonIcon.Recordset = rstIcons End Sub
Listing 1: Beim Laden des Formulars ausgelöste Prozedur
Die für die Schaltflächen zu verwendenden Icons sollen jedoch aus der Tabelle MSysResources der Access-Add-In-Datenbank geholt werden. Diese referenzieren wir nicht mit CurrentDb, sondern mit CodeDb. Hier holen wir alle Elemente, deren Dateiendung auf png lautet und sortieren diese nach dem Namen. Dieses Recordset weisen wir der entsprechenden Eigenschaft der beiden Kombinationsfelder cboOKButtonIcon und cboCancelButtonIcon zu.
Felder der gewählten Datensatzquelle einlesen
Wählen wir mit dem Kombinationsfeld cboDatasource eine Tabelle oder Abfrage aus, lösen wir die folgende Prozedur aus:
Private Sub cboDatasource_AfterUpdate() If ReadFieldsFromTable(Me!cboDatasource) = True Then Me!sfmDetailforms.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!txtFormTitle, "")) = 0 Then Me!txtFormTitle = Replace(Replace(_ Me!cboDatasource, "tbl", ""), "qry", "") End If End Sub
Diese ruft eine weitere Funktion auf, die wir in Listing 2 sehen. Diese nimmt den Namen der Tabelle oder Abfrage als Parameter entgegen und speichert ihn in der Variablen strDataSource. Dann öffnen wir ein Recordset auf Basis der Tabelle tblDetailfields, der wir die Feldinformationen zuweisen wollen. Das Recordset holt alle Datensätze, die mit dem aktuell im Hauptformular angezeigten Datensatz der Tabelle tblDetailforms verknüpft sind. Die Prozedur bewegt den Datensatzzeiger auf den letzten Datensatz, um anschließend die Anzahl der enthaltenen Datensätze zu ermitteln. Ist diese nicht 0, stellt die Prozedur dem Benutzer die Frage, ob die Felder neu eingelesen werden sollen. Bejaht der Benutzer dies, löscht die Prozedur zunächst alle für diese Konfiguration bereits vorhandenen Datensätze aus der Tabelle tblDetailfields.
Public Function ReadFieldsFromTable(strDataSource As String) As Boolean Dim db As DAO.Database, dbc As DAO.Database, rst As DAO.Recordset, rstSource As DAO.Recordset Dim bolReadFields As Boolean, intControlType 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 Integer strDataSource = Me!cboDatasource Set db = CurrentDb Set dbc = CodeDb Set rst = dbc.OpenRecordset("SELECT * FROM tblDetailfields WHERE DetailformID = " & Me!DetailformID, 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 tblDetailfields WHERE DetailformID = " & Me!DetailformID, 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!DetailformID = Me!txtDetailformID rst!FieldName = fld.Name rst!Fieldindex = i rst!FieldWidth = 2000 rst!Fieldheight = 300 rst!FieldColumn = 1 rst!Fieldlabel = Nz(varCaption, fld.Name) rst!ControlType = intControlType rst!RowSourceType = varRowSourceType rst!RowSource = varRowSource rst!BoundColumn = varBoundColumn rst!ColumnCount = varColumnCount rst!ColumnWidths = varColumnWidths rst.Update i = i + 1 Next fld ReadFieldsFromTable = True Me!sfmDetailforms.Form.Requery Else ReadFieldsFromTable = False End If End Function
Listing 2: Einlesen der Feldinformationen in die Tabelle tblDetailfields
Nun referenziert sie mit einem zweiten Recordset die Tabelle, für die wir das Detailformular erstellen wollen, mit der Variablen rstSource. Für diese durchlaufen wir alle in der Fields-Auflistung enthaltenen Field-Elemente und referenzieren das aktuelle Element jeweils mit der Variablen fld.
Hier rufen wir nun eine weitere Funktion namens GetControlProperties auf und übergeben dieser einen Verweis auf das zu untersuchende Feld. Außerdem übergeben wir einige Variant-Variablen, die wir mit den Werten der entsprechenden Eigenschaften des Feldes füllen wollen – diese beschreiben wir gleich im Anschluss.
Nachdem wir die verschiedenen Variant-Variablen mit den Eigenschaften des Feldes fld gefüllt haben, legen wir einen neuen Datensatz im Recordset der Tabelle tblDetailfields an und füllen nacheinander die Eigenschaften. Dazu gehören der Verweis auf den aktuellen Eintrag der Tabelle tblDetailforms, der Feldname, der Index, Voreinstellungen von 2.000 und 300 Pixeln für die Steuerelementbreite und -höhe und der Wert 1 für die Spalte. Danach folgen die Beschriftung für das Steuerelement, der Steuerelementtyp und dann einige Eigenschaften, die nur für Nachschlagefelder benötigt werden. Danach speichern wir den neuen Datensatz mit der Update-Methode und liefern der aufrufenden Prozedur den Wert True zurück.
Die aufrufende Prozedur cboDatasource_AfterUpdate prüft schließlich noch, ob txtFormname und txtFormTitel bereits gefüllt sind. Falls nicht, trägt sie für txtFormname einen Wert ein, der dem Tabellen- oder Abfragenamen entspricht, aber das Präfix tbl oder qry durch frm ersetzt. Für das Feld txtFormTitle trägt die Prozedur ebenfalls den Namen der Datenquelle ein, ersetzt aber die Präfixe tbl und qry ersatzlos.
Ermitteln von Feldeigenschaften
Die bereits angesprochene Funktion GetControlProperties nimmt einen Verweis auf ein Field-Objekt entgegen und ermittelt einige Eigenschaften dieses Feldes (siehe Listing 3).
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 3: Ermitteln verschiedener Feldeigenschaften
Dabei deaktivieren wir zuerst die eingebaute Fehlerbehandlung und greifen dann auf die beiden Eigenschaften Caption und DisplayControl zu. Caption wird nur gesetzt, wenn wir im Tabellenentwurf die Eigenschaft Beschriftung für ein Feld füllen. DisplayControl wird nur angelegt, wenn wir ein Nachschlagefeld anlegen oder ein Ja/Nein-Feld. Daher löst der Zugriff auf eine solche Eigenschaft einen Fehler aus, wenn diese nicht vorhanden ist. Wir lesen diese bei deaktivierter Fehlerbehandlung ein und prüfen danach zuerst den Wert von intControlType.
Abhängig von diesem Wert lesen wir weitere Informationen ein – beim Wert acComboBox zum Beispiel noch die gebundene Spalte, den Herkunftstyp, die Datensatzherkunft, die Anzahl der Spalten und die Spaltenbreiten.
Danach geben wir die entsprechenden Informationen mit den Variant-Parametern an die aufrufende Prozedur ReadFieldsFromTable zurück, welche diese in den entsprechenden neuen Datensatz der Tabelle tblDetailfields schreibt.
Die Tabelle tblDetailfields sieht nach diesem Schritt beispielsweise wie in Bild 6 aus.
Bild 6: Werte der Tabelle tblDetailfields nach dem Einlesen der Felder der Quelltabelle
Button-Style auswählen und einstellen
Klicken wir auf die Schaltfläche cmdButtonStyles, lösen wir die folgende Prozedur aus:
Private Sub cmdButtonStyles_Click() DoCmd.OpenForm "frmButtonStyles", _ WindowMode:=acDialog, OpenArgs:=Me!cboButtonStyleID If IstFormularGeoeffnet("frmButtonStyles") Then Me!cboButtonStyleID = _ Forms!frmButtonStyles!ButtonStyleID DoCmd.Close acForm, "frmButtonStyles" End If End Sub
Diese öffnet das Formular frmButtonStyles, das an die Tabelle tblButtonStyles gebunden ist und in der Entwurfsansicht wie in Bild 7 aussieht. Dieses Formular ruft beim Anzeigen eines Datensatzes die folgende Prozedur auf:
Bild 7: Entwurfsansicht des Formulars frmButtonStyles
Private Sub Form_Current() Call SetButtonStyle End Sub
Diese startet eine weitere Prozedur namens SetButtonStyle. Diese wendet die aktuellen Einstellungen auf das für die Vorschau verwendete Button-Element cmdPreview an:
Private Sub SetButtonStyle() With Me!cmdPreview If Not IsNull(Me!cboBorderStyle) Then .BorderStyle = Me!cboBorderStyle End If If Not IsNull(Me!cboBackStyle) Then .BackStyle = Me!cboBackStyle End If If Not IsNull(Me!cboAlignment) Then .Alignment = Me!cboAlignment End If End With End Sub
Diese Prozedur wird auch durch die übrigen Steuerelemente zum Einstellen der Eigenschaften aufgerufen:
Private Sub BorderStyle_AfterUpdate() Call SetButtonStyle End Sub Private Sub cboAlignment_AfterUpdate() Call SetButtonStyle End Sub Private Sub cboBackStyle_AfterUpdate() Call SetButtonStyle End Sub Private Sub cboBorderStyle_AfterUpdate() Call SetButtonStyle End Sub
Beim Anklicken der OK-Schaltfläche wird das Formular unsichtbar gemacht:
Private Sub cmdOK_Click() Me.Visible = False End Sub
Dadurch läuft der aufrufende Code weiter und prüft, ob das Formular frmButtonStyles noch geöffnet ist. Ist das der Fall, liest sie den aktuellen Datensatz aus dem Formular ein und wählt diesen im Kombinationsfeld cboButtonStyleID aus.
Start der Erstellung des Detailformulars
Nun schauen wir uns an, was geschieht, wenn wir alle notwendigen Einstellungen getroffen haben und die Erstellen-Schaltfläche betätigen. Diese hat den folgenden Code und speichert erst einmal die aktuellen Einstellungen:
Private Sub cmdCreate_Click() Dim frm As Form Dim lngWidth As Long Dim lngHeight As Long RunCommand acCmdSaveRecord If CreateNewForm(Me!Formname) = True Then DoCmd.OpenForm Me!Formname, acDesign Set frm = Forms(Me!Formname) With frm .AutoCenter = Me!chkAutocenter .Caption = Me!txtFormTitle .DefaultView = acDefViewSingle .RecordSelectors = Me!chkRecordSelectors .NavigationButtons = Me!chkNavigationButtons .DividingLines = False .ScrollBars = Me!cboScrollBars .RecordSource = Me!cboDatasource .Cycle = Me!cboCycle .HasModule = True AddControls Me!DetailformID, lngHeight, lngWidth .Section(0).Height = lngHeight .Width = lngWidth End With End If End Sub
Danach ruft sie die Funktion CreateNewForm auf, der sie den Formularnamen übergibt (siehe Listing 4). Die Funktion beschreiben wir weiter unten. Sie erstellt ein neues Formular und speichert es unter dem angegebenen Namen.
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 4: Erstellen des Formulars
Dieses öffnen wir dann in der Entwurfsansicht und referenzieren es mit der Variablen frm. Dann stellen wir die Eigenschaften des Formulars ein, die wir im Formular konfiguriert haben. Außerdem stellen wir die Eigenschaft HasModule auf den Wert True ein und legen so direkt ein VBA-Klassenmodul für dieses Formular an. Schließlich rufen wir die Prozedur AddControls auf, die das Hinzufügen der einzelnen Steuerelemente übernimmt – auch diese beschreiben wir weiter unten im Detail.
Hier ist nur wichtig, dass wir dieser Prozedur die Nummer der Konfiguration aus der Tabelle tblDetailforms übergeben und zwei Parameter mit denen wir die Höhe und die Breite zurückerhalten, die für die Steuerelemente benötigt wird. Diese verwenden wir dann, um die Höhe des Detailbereichs und die Breite des Formulars genau an die enthaltenen Steuerelemente anzupassen.
Erstellen des Formulars selbst
Die Funktion CreateNewForm übernimmt die eigentliche Erstellung des leeren Formulars. Dazu erhält sie den Namen des zu neuen Formulars als Parameter.
Die Funktion prüft als Erstes mit der Funktion ExistsForm, ob bereits ein Formular mit dem angegebenen Namen existiert:
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
Ist das der Fall, fragt die Funktion CreateNewForm den Benutzer, ob er das Formular überschreiben möchte. Stimmt er dem zu, löscht die Funktion DeleteForm das Formular mit der DeleteObject-Methode:
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
Die Funktion CreateNewForm erstellt dann mit der Methode CreateForm ein neues, leeres Formular und speichert dessen Namen in der Variablen strNameTemp.
Dann macht sie das Formular sichtbar und schließt es unter dem temporären Namen. Anschließend benennt sie das Formular mit der Methode Rename in den gewünschten Namen um.
Hinzufügen der Steuerelemente
Das Hinzufügen der Steuerelemente übernimmt die Prozedur AddControls aus Listing 5. Dieser übergeben wir die ID des Formulars, dessen Steuerelemente hinzugefügt werden sollen. Die Prozedur referenziert das Database-Objekt mit der Variablen dbc. Dann erstellt sie ein Recordset auf Basis des Datensatzes der Tabelle tblDetailforms, für den wir das neue Formular mit Steuerelementen füllen wollen. Hier lesen wir den Formularnamen aus und tragen ihn in strFormName ein.
Public Sub AddControls(lngDetailformID As Long, lngHeight As Long, lngWidth As Long) Dim dbc As DAO.Database, rstControls As DAO.Recordset Dim rstForm As DAO.Recordset, rstButtonStyles As DAO.Recordset Dim strFormName As String, ctl As Control, cmd As CommandButton Dim lngLeft As Long, lngMaxWidth As Long Dim lngLabelWidth As Long, lngTop As Long Set dbc = CodeDb Set rstForm = dbc.OpenRecordset("SELECT * FROM tblDetailforms WHERE DetailformID = " & lngDetailformID, _ dbOpenDynaset) strFormName = rstForm!Formname Set rstControls = dbc.OpenRecordset("SELECT * FROM tblDetailfields WHERE DetailformID = " & lngDetailformID, _ 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 End If rstControls.MoveNext Loop lngLeft = rstForm!Margin Set rstButtonStyles = dbc.OpenRecordset("SELECT * FROM tblButtonStyles WHERE ButtonStyleID = " _ & rstForm!ButtonStyleID, dbOpenDynaset) If rstForm!OKButton = True Then Set cmd = AddButton(rstForm, rstButtonStyles, "OK", lngLeft, lngTop, lngMaxWidth) End If If rstForm!CancelButton = True Then Set cmd = AddButton(rstForm, rstButtonStyles, "Cancel", lngLeft, lngTop, lngMaxWidth) End If lngHeight = lngTop + 50 + cmd.Height + rstForm!Margin lngWidth = lngMaxWidth + rstForm!Margin End Sub
Listing 5: Hinzufügen der Steuerelemente
Dann füllen wir ein weiteres Recordset namens rstControls mit allen Datensätzen der Tabelle tblDetailfields für das zu erstellende Formular. Mit diesem Recordset als Parameter rufen wir die Funktion GetMaxLabelWidth auf, mit der wir die optimale Breite für die Bezeichnungsfelder der anzulegenden Felder berechnen – siehe weiten unten.
Anschließend legen wir den Abstand vom oberen Formularrand für das erste anzulegende Element fest. Diesen Abstand beziehen wir aus dem Feld Margin der Tabelle tblDetailforms.
Damit starten wir in eine Do While-Schleife über alle Elemente des Recordsets rstControls. Hier prüfen wir als Erstes den Wert des Feldes IgnoreField. Wenn wir hier den Wert True vorfinden, wird das Feld nicht für das Detailformular berücksichtigt.
Anderenfalls prüfen wir in einer Select Case-Bedingung den Steuerelementtyp des Feldes. Abhängig vom jeweiligen Wert, also acTextBox, acComboBox oder acCheckBox, rufen wir eine der Funktionen AddTextBox, AddComboBox oder AddCheckBox auf.
Diesen übergeben wir die zum Anlegen notwendigen Informationen und erhalten im Gegenzug einen Verweis auf das jeweilige Steuerelement zurück.
Damit können wir die Höhe des angelegten Steuerelements ermitteln und für die Variable lngTop den Abstand vom oberen Formularrand für das nächste Steuerelement ermitteln. Dazu addieren wir zum aktuellen Wert von lngTop die Höhe des zuletzt hinzugefügten Steuerelements und den vorgegebenen allgemeinen Abstand aus dem Feld Margin der Tabelle tblDetailforms.
Danach prüfen wir noch, ob der Benutzer eine OK-Schaltfläche und/oder eine Abbrechen-Schaltfläche hinzufügen möchte. In diesem Fall rufen wir jeweils die Funktion AddButton auf, die wir weiter unten beschreiben.
Schließlich ermitteln wir aus der Position des zuletzt angelegten Button-Elements die Höhe des Detailbereichs, die wir in lngHeight schreiben, und auf ähnliche Weise berechnen wir die Breite des Formulars für die angelegten Steuerelemente.
Maximale Breite der Bezeichnungsfelder ermitteln
In der Prozedur AddControls nutzen wir noch die Funktion GetMaxLabelWidth, um die maximale Breite für die Beschriftungsfelder zu ermitteln (siehe Listing 6). Diese nimmt ein Recordset mit den Daten der Tabelle tblDetailfields für das anzulegende Detailformular entgegen.
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 6: Ermitteln der maximalen Breite für die Label-Elemente
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