Daten in Detailformularen anzeigen und bearbeiten

Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.

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.

pic001.png

Bild 1: Übersicht der Adressen, die per Detailformular neu angelegt und bearbeitet werden sollen

pic002.png

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.

pic003.png

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

pic004.png

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

Die Prozedur prüft zunächst den Datenmodus. Beim Anlegen eines neuen Datensatzes untersucht sie dann mit der Dirty-Eigenschaft, ob der Datensatz seit dem letzten Speichern geändert wurde. Wenn Dirty den Wert True aufweist, wurde der Datensatz offensichtlich noch nicht gespeichert und die änderungen können mit Me.Undo verworfen werden, bevor die Prozedur das Formular schließt. Hat Me.Dirty den Wert False, wurde der Datensatz gespeichert und muss gelöscht werden. Dies erledigt die Delete-Methode des Recordset-Objekts des Formulars.

Eines haben wir jedoch übersehen: Wenn man den Datensatz speichert und diesen danach erneut bearbeitet, ist er bereits in der Tabelle angelegt. Durch die erneute Bearbeitung ohne wiederholtes Speichern hat aber Me.Dirty den Wert True, was dazu führt, dass die Abbrechen-Schaltfläche lediglich ein Me.Undo ausführt und nicht den Datensatz löscht. Wir müssten also eine Variable einsetzen, die beim ersten Speichern auf True gestellt wird und beim Ausführen der Abbrechen-Schaltfläche statt Me.Dirty abgefragt wird.

Die Variable deklarieren Sie wie folgt im Kopf des Klassenmoduls des Formulars frmAdresseDetail:

Dim bolGespeichert As Boolean

Sie wird im Ereignis Nach Aktualisierung, das nach dem Speichern ausgelöst wird, gesetzt:

Private Sub Form_AfterUpdate()
    bolGespeichert = True
End Sub

Der entscheidende Teil der Beim Klicken-Ereignisprozedur der Schaltfläche cmdAbbrechen sieht dann so aus:

If Not bolGespeichert Then
    Me.Undo
Else
    Me.Recordset.Delete
End If

Alternativ können Sie natürlich auch die Tastenkombination Strg + S abfangen – aber auch dies ist nicht ganz trivial und soll an dieser Stelle nicht durchgespielt werden.

Bestehende Datensätze bearbeiten

Wenden wir uns lieber der Schaltfläche Datensatz bearbeiten des Formulars frmAdressenMitDetailformular zu. Diese soll ebenfalls das Formular frmAdresseDetail öffnen – allerdings nicht zum Anlegen eines neuen, sondern zum Bearbeiten eines bestehenden Datensatzes. Hierzu verwenden wir ebenfalls die DoCmd.OpenForm-Methode, diesmal allerdings mit einem anderen Wert für den Parameter DataMode – nämlich acFormEdit. Das reicht allerdings noch nicht ganz: Wir müssen dem Detailformular nämlich auch noch mitteilen, welchen Datensatz es denn überhaupt anzeigen soll. Dazu verwenden wir den Parameter WhereCondition: Er erwartet einen Ausdruck, der festlegt, welcher der Datensätze der Datenherkunft des zu öffnenden Formulars angezeigt werden soll. Da die Tabelle tblAdressen das Feld AdresseID als Primärschlüsselfeld verwendet, benutzen wir dieses logischerweise für die Angabe des Parameters. Das Formular frmAdresseDetail soll genau den Datensatz anzeigen, dessen Feld AdresseID den Wert des gleichen Feldes des aktuell im Unterformular sfmAdressenMitDetailformular markierten Datensatzes aufweist. Dies erreichen Sie mit dem Parameterwert "AdresseID = " & Me!sfmAdressen.Form!AdresseID. Der letzte Teil wird beim Aufruf ausgewertet, sodass sich ein Ausdruck wie AdresseID = 12 ergibt. Insgesamt sieht die Prozedur, die durch einen Mausklick auf die Schaltfläche Adresse bearbeiten ausgelöst wird, so aus:

Private Sub cmdBearbeiten_Click()
    DoCmd.OpenForm "frmAdresseDetail", WindowMode:=acDialog, _
        DataMode:=acFormEdit, WhereCondition:="AdresseID = " & Me!sfmAdressen.Form!AdresseID
End Sub

Wenn Sie im Unterformular sfmAdresseMitDetailformular den zu bearbeitenden Datensatz markieren und dann auf Adresse bearbeiten klicken, öffnet die Ereignisprozedur das Formular frmAdresseDetail wie in Bild 5.

pic005.png

Bild 5: Bearbeiten des aktuell ausgewählten Datensatzes in der Detailansicht

Kleinere Probleme

Ohne weitere Nacharbeiten würde es hier und da haken. Wenn Sie zum Beispiel den Datensatzmarkierer im Unterformular auf den neuen Datensatz verschieben und dann auf die Schaltfläche cmdBearbeiten klicken, löst Access einen Fehler aus (s. Bild 6). Das Problem ist, dass der hintere Teil des Ausdrucks für die WhereCondition (Me!sfmAdressen.Form!AdresseID) den Wert Null liefert, weil der markierte leere Datensatz natürlich noch keinen Wert im Feld AdresseID aufweist. Dies können Sie auf drei Arten umgehen:

pic006.png

Bild 6: Dieser Fehler tritt auf, wenn beim Aufrufen des Detailformulars kein zu bearbeitender Datensatz markiert ist.

  • Sie zeigen eine entsprechende Meldung an, wenn der Benutzer auf die Schaltfläche cmdBearbeiten klickt und kein Datensatz im Unterformular markiert ist.
  • Sie deaktivieren die Schaltfläche cmdBearbeiten, wenn der Datensatzzeiger im Unterformular auf einem neuen, leeren Datensatz landet.
  • Sie stellen das Unterformular so ein, dass es erst gar keinen neuen, leeren Datensatz anzeigt, den der Benutzer anklicken kann. Das macht Sinn, wenn Sie ohnehin ein Detailformular zum Bearbeiten der Daten anbieten.

Die erste Variante fragt ab, ob das Unterformular gerade einen neuen, nicht gespeicherten Datensatz anzeigt. Falls ja, öffnet die Prozedur nicht das Detailformular, sondern zeigt eine entsprechende Meldung an:

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

Testzugang

eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar