Detailformular per Mausklick erstellen

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.

Beispieltabelle tblMitglieder

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.

Formular zum Konfigurieren neuer Detailformulare

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.

Formular zum Einstellen einiger Schaltflächeneigenschaften

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.

Ein automatisch erstelltes Detailformular

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

Datenmodell des Add-Ins

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.

Werte der Tabelle tblDetailfields nach dem Einlesen der Felder der Quelltabelle

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:

Entwurfsansicht des Formulars frmButtonStyles

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

Schreibe einen Kommentar