Viele Anwendungen zeigen Daten in Haupt- und Unterformularen an, wobei das Unterformular Daten einfacher Tabellen enthält (etwa Adressen) oder Haupt- und Unterformular verknüpfte Daten darstellen (zum Beispiel Kunden und Projekte). Die Daten im Unterformular können dann zwar möglicherweise direkt an Ort und Stelle bearbeitet werden, aber wenn das im Unterformular dargestellte Objekt viele Felder enthält, sollten Sie dafür ein spezielles Detailformular bereitstellen. Mit diesem kann der Benutzer dann neue Datensätze anlegen und bestehende bearbeiten. In diesem Beitrag erfahren Sie, wie Sie ein solches Detailformular aufbauen und es für die verschiedenen Bearbeitungsarten einsetzen.
Das Zusammenspiel eines Formulars zur Anzeige der Übersicht der Datensätze einer Tabelle und eines weiteren Formulars zum Anlegen eines neuen Datensatzes oder zum Bearbeiten des jeweils in der Liste ausgewählten Datensatzes liefert eine Menge Herausforderungen:
- Wie öffne ich das Detailformular
- Wie bringe ich es dazu, einen neuen Datensatz anzuzeigen
- Wie zeigt es gleich nach dem Öffnen einen Datensatz an, der im aufrufenden Formular markiert ist
- Wie sorge ich dafür, dass die Daten im aufrufenden Formular nach dem Schließen des Detailformulars aktualisiert werden
- Wie teile ich dem Detailformular den Wert des Fremdschlüsselfeldes mit, wenn ich einen neuen Datensatz anlegen möchte, der mit dem aktuell im Hauptformular angezeigten Datensatz verknüpft ist
Diese und weitere Fragen beantwortet der vorliegende Beitrag.
Beispiele dieses Beitrags
In diesem Beitrag werden wir zwei verschiedene Beispiele betrachten: Im ersten zeigen Haupt- und Unterformular lediglich Adressdaten im Unterformular an, aber keine Daten im Hauptformular. Im zweiten Beispiel enthält das Hauptformular ebenfalls Daten, die per 1:n-Beziehung mit denen im Unterformular verknüpft sind. In diesem Fall kommt das gute, alte Beispiel der Projekte eines Kunden zum Einsatz: Das Hauptformular zeigt die Daten eines Kunden an, das Unterformular die der zu diesem Kunden gehörenden Projekte.
Die grundsätzliche Technik ist bei beiden Varianten gleich, bei der Version mit 1:n-Beziehung und Daten im Hauptformular kommt noch eine Feinheit hinzu.
Haupt- und Unterformular des ersten Beispiels heißen frmAdressenMitDetailformular und sfmAdressenMitDetailformular und sehen wie in Bild 2 aus und bringt eine Kopfzeile im Kopfbereich sowie die Schaltflächen cmdOK und cmdAbbrechen im Fußbereich des Formulars unter.
Bild 1: Übersicht der Adressen, die per Detailformular neu angelegt und bearbeitet werden sollen
Bild 2: Formular zum Anlegen oder Bearbeiten einer Adresse
Später werden die Eigenschaften Trennlinien, Datensatzmarkierer, Navigationsschaltflächen und Bildlaufleisten sämtlich auf den Wert Nein eingestellt, vorerst benötigen wir jedoch zumindest noch die Navigationsschaltflächen, um Informationen über den beziehungsweise die aktuell angezeigten Datensätze zu erhalten.
Das Bezeichnungsfeld im Formularkopf heißt lblTitel und zeigt aktuell den Text Adresse bearbeiten an. Warum erhält ein Bezeichnungsfeld einen richtigen Namen Normalerweise ändern wir diesen nie, weil wir kaum einmal per VBA auf Bezeichnungsfelder zugreifen. In diesem Fall soll das Bezeichnungsfeld jedoch je nach Art der Datenbearbeitung entweder den Titel Neue Adresse oder Adresse bearbeiten anzeigen.
Öffnen des Detailformulars zum Anlegen eines neuen Datensatzes
Wenn Sie einen neuen Datensatz anlegen möchten, klicken Sie im Formular frmAdressenMitDetailformular auf die Schaltfläche cmdNeu mit der Beschriftung Neue Adresse.
Die Schaltfläche soll nun das Formular frmAdresseDetail aufrufen und einen leeren, neuen Datensatz anzeigen. Dazu statten wir die Prozedur cmdNeu_Click, die wir durch Auswählen des Wertes [Ereignisprozedur] für die Eigenschaft Beim Klicken und anschließendes Anklicken der Schaltfläche mit den drei Punkten erzeugen, wie folgt mit einer einzigen Anweisung aus:
Private Sub cmdNeu_Click() DoCmd.OpenForm "frmAdresseDetail", WindowMode:=acDialog, DataMode:=acFormAdd End Sub
Normalerweise würde die Anweisung DoCmd.OpenForm "frmAdresseDetail" ausreichen, um das Formular zu öffnen. In diesem Fall brauchen wir jedoch zwei weitere Parameter:
- WindowMode:=acDialog legt fest, dass die Anweisung das Formular als modalen Dialog öffnet. Das bedeutet, dass der aufrufende Code nicht weiterläuft, bevor das aufgerufene Formular geschlossen oder unsichtbar gemacht wird. Wir werden später Code hinzufügen, der nach dem Schließen des Detailformulars die Adressenliste im aufrufenden Formular aktualisiert.
- DataMode:=acFormAdd sorgt dafür, dass das Formular gleich nach dem Öffnen einen neuen, leeren Datensatz anzeigt. Gleichzeitig wird ein Filter gesetzt, damit in der aktuellen Ansicht tatsächlich nur ein neuer Datensatz angelegt und kein bestehender Datensatz bearbeitet werden kann.
Bild 3 zeigt, wie dies aussieht. Der Datensatzmarkierer bestätigt, dass das Formular neben dem neuen, leeren Datensatz keine weiteren Datensätze enthält.
Bild 3: Anlegen eines neuen Datensatzes über das Detailformular frmAdresseDetail
Sie können damit nun einen neuen Datensatz eingeben, aber was geschieht dann Wir müssen zunächst die beiden Schaltflächen cmdOK und cmdAbbrechen mit Ereignisprozeduren ausstatten. Dies ist normalerweise sehr einfach. Die OK-Schaltfläche soll nur das Formular schließen, was die folgende Prozedur erledigt:
Private Sub cmdOK_Click() DoCmd.Close acForm, Me.Name End Sub
Unter normalen Umständen wird der neue Datensatz, sofern einer angelegt wurde, nun gespeichert und das Formular geschlossen.
Die Abbrechen-Schaltfläche hingegen soll die änderungen verwerfen und das Formular daraufhin schließen. Das Verwerfen der änderungen erledigt die Undo-Methode des Formulars:
Private Sub cmdAbbrechen_Click() Me.Undo DoCmd.Close acForm, Me.Name End Sub
Dummerweise kann man änderungen nach dem erstmaligen Speichern des Datensatzes nicht mehr rückgängig machen – mehr dazu erfahren Sie gleich.
Beobachtungen im Detailformular
Wenn Sie das Detailformular über die Schaltfläche Neue Adresse geöffnet haben, zeigt die Navigationsleiste 1 von 1 an und die Schaltfläche zum Neuanlegen eines weiteren Datensatzes ist deaktiviert. Sofort nach dem Eingeben des ersten Zeichens in eines der Felder des Formulars ändert sich dies: Die Schaltfläche zum Anlegen eines neuen Datensatzes wird aktiviert. Sie können also nun einen neuen Datensatz anlegen, obwohl Sie den ersten noch gar nicht abgeschlossen haben.
Man sollte meinen, dass man dies durch Einstellen der Eigenschaft Anfügen zulassen des Formulars frmAdresseDetail auf den Wert Nein verhindern kann. Dies gelingt jedoch nur, wenn Sie das Formular ohne das Argument DataMode öffnen oder durch einen Doppelklick auf seinen Namen im Datenbankfenster beziehungsweise im Navigationsbereich. DataMode:=acFormAdd stellt diese Eigenschaft automatisch auf Ja ein, was auch logisch ist.
Wie aber können wir verhindern, dass der Benutzer mehr als einen Datensatz gleichzeitig eingibt, ohne das Formular erneut öffnen zu müssen Wir nehmen ihm einfach die Möglichkeit, zu einem neuen Datensatz zu springen, indem wir die Navigationsleiste durch Einstellen der entsprechenden Eigenschaft auf den Wert Nein ausblenden.
Nun kann der Benutzer aber immer noch durch wiederholtes Betätigen der Tab- oder Eingabetaste durch die Steuerelemente navigieren. Wenn er sich auf dem letzten Steuerelement der Aktivierungsreihenfolge befindet und nochmals die Tab- oder Eingabetaste betätigt, landet er ebenfalls in einem neuen Datensatz. Aber auch dies können Sie verhindern: Dazu stellen Sie einfach die Eigenschaft Zyklus des Formulars auf Aktueller Datensatz ein. Der Fokus landet dann zwar auch wieder beim ersten Steuerelement der Aktivierreihenfolge, jedoch ohne den Datensatz zu wechseln. Sie können dies am besten beobachten, wenn Sie die Navigationsschaltflächen aktivieren und einmal alle Steuerelemente durchlaufen.
Anzeige der richtigen Überschrift
Eine weitere Beobachtung ist, dass das Formular frmAdresseDetail nun natürlich den Text Adresse bearbeiten als Beschriftung des Bezeichnungsfeldes lblTitle anzeigt, obwohl Sie ja gerade einen neuen Datensatz anlegen. Wir müssen also herausfinden, ob die DoCmd.OpenForm-Methode den Wert acFormAdd oder acFormEdit für den Parameter DataMode verwendet hat.
Es gibt sicher ein paar Workarounds, den direkten Weg liefert aber eine nicht dokumentierte und verborgene Eigenschaft namens DefaultEditing. Diese enthält in direkter Abhängigkeit vom Wert des Parameters DataMode einen der folgenden beiden Werte (es gibt noch weitere, die aber in diesem Zusammenhang irrelevant sind):
- 1: Das Formular wurde mit DataMode:=acFormAdd geöffnet.
- 2: Das Formular wurde mit DataMode:=acFormEdit geöffnet.
Im Code können wir uns dies in einer Ereignisprozedur zunutze machen, die durch das Ereignis Beim Laden des Formulars ausgelöst wird. Diese wertet die Eigenschaft DefaultEditing aus und weist der Caption-Eigenschaft von lblTitle den entsprechenden Wert zu:
Private Sub Form_Load() Select Case Me.DefaultEditing Case 1 Me!lblTitel.Caption = "Neue Adresse anlegen" Case 2 Me!lblTitel.Caption = "Adresse bearbeiten" End Select End Sub
Gespeicherte änderungen abbrechen
Schließlich fällt uns beim spielerischen Ausprobieren des Formulars noch auf, dass die Abbrechen-Schaltfläche spätestens dann keinen Sinn mehr macht, wenn der Benutzer einen neu angelegten Datensatz erstmalig gespeichert hat, was er normalerweise mit einem Klick auf den Datensatzmarkierer oder durch den Wechseln zu einem anderen Datensatz erledigen kann. Ersteres gelingt nicht, wenn wir den Datensatzmarkierer mit der entsprechenden Eigenschaft ausblenden, das Zweite unterbinden wir mit der Zyklus-Eigenschaft.
Es bleibt allerdings noch die Möglichkeit, den Datensatz mit Strg + S zu speichern – da dies eine in vielen Anwendungen verbreitete Tastenkombination zum Speichern ist, werden manche Benutzer sie vielleicht automatisch hin und wieder einsetzen (der Autor spricht aus Erfahrung). Beim Speichern werden aber die beiden Ereignisse Vor Aktualisierung und Nach Aktualisierung ausgelöst. Hier könnten Sie schlicht dafür sorgen, dass die Abbrechen-Schaltfläche deaktiviert wird:
Private Sub Form_AfterUpdate() Me!cmdAbbrechen.Enabled = False End Sub
Möglicherweise wird der Benutzer nicht verstehen, warum die Abbrechen-Schaltfläche plötzlich inaktiv ist. Daher sollten Sie beim Ereignis Vor Aktualisierung eine entsprechende Meldung anzeigen (s. Bild 4):
Bild 4: Diese Meldung erscheint beim Versuch, einen Datensatz zu speichern.
Private Sub Form_BeforeUpdate(Cancel As Integer) If MsgBox("änderungen können nach dem Speichern nicht mehr mit Abbrechen oder " _ & "Escape verworfen werden. Fortsetzen", vbOKCancel + vbExclamation, _ "Datensatz wird gespeichert") = vbCancel Then Cancel = True End If End Sub
Wenn der Benutzer nun die Tastenkombination Strg + S betätigt, erscheint die Meldung. Beim Klicken auf OK wird der Datensatz gespeichert und in der Folge die Abbrechen-Schaltfläche des Formulars deaktiviert. Klickt der Benutzer auf die Abbrechen-Schaltfläche des Meldungsfensters, wird der Speichervorgang abgebrochen.
Beim reinen Einsatz eines Formulars zum Anlegen neuer Datensätze wäre es auch denkbar, dass man den Datensatz, wenn dieser bereits gespeichert wurde, beim Anklicken der Abbrechen-Schaltfläche einfach wieder löscht. In diesem Falle brauchte man die Abbrechen-Schaltfläche nach dem vorzeitigen Speichern des neuen Datensatzes auch nicht zu deaktivieren, da sie ja die erwartete Aufgabe erfüllt. Die Abbrechen-Schaltfläche müsste dann etwa folgende Prozedur ausführen (die Ereignisprozeduren für Vor Aktualisierung und Nach Aktualisierung müssten Sie dann verwerfen):
Private Sub cmdAbbrechen_Click() Select Case Me.DefaultEditing Case 1 ''Neuer Datensatz If Me.Dirty Then Me.Undo Else Me.Recordset.Delete End If Case 2 ''Datensatz bearbeiten Me.Undo End Select DoCmd.Close acForm, Me.Name End Sub