|  | Unser Angebot für Sie! Lesen Sie diesen Beitrag und 500 andere sofort im Onlinearchiv, und erhalten Sie alle zwei Monate brandheißes Access-Know-how auf 72 gedruckten Seiten! Plus attraktive Präsente, zum Beispiel das bald erscheinende Buch 'Access 2010 - Das Grundlagenbuch für Entwickler'! |
| | | | | |
Undo in Formularen und Unterformularen
André Minhorst, Duisburg
Die Verwendung von Formularen und Unterformularen ist ein probates Mittel zur Darstellung von Daten aus 1:n- und m:n-Beziehungen. Dabei tritt immer wieder das Ärgernis zu Tage, dass Access von Haus aus keine Möglichkeit mitbringt, Änderungen an den Daten im Unterformular in einem Schritt mit denen des Hauptformulars rückgängig zu machen. Dieser Beitrag stellt eine Lösung vor, wie die Benutzer einer Datenbank dennoch mit einem Mausklick komplexe Änderungen rückgängig machen können.
HinweisDie Beispieldatenbanken zu diesem Beitrag heißen Forms.mdb und FormsAndSubforms.mdb. Sie finden die Datenbanken auf der Heft-CD oder im Internet unter http://www.access-im-unternehmen.de unter dem Shortlink 276. Nützliche Informationen gibt es im Beitrag Transaktionen in Access, Shortlink 275. (
Das Dilemma
Der Einsatz von Haupt- und Unterformularen ist durch die Verknüpfung der Datenherkünfte dieser beiden sehr hilfreich, ermöglicht er doch die Bearbeitung zusammenhängender Daten wie beispielsweise Bestellungen und bestellter Artikel (s. Abb. 1).

Abb. 1: Anzeige von Bestelldaten in Haupt- und Unterformular
Leider lassen diese Formulare eine sehr wichtige Eigenschaft vermissen: Sie bieten keine Undo-Funktion, die alle seit dem Anzeigen des Datensatzes im Hauptformular getätigten Änderungen rückgängig macht. Es reicht gerade zu einer Undo-Funktion für den im Hauptformular angezeigten Datensatz. Wenn man einmal versehentlich den Hauptformular-Datensatz wechselt und Änderungen an den Datensätzen im Unterformular vornimmt, ist dort maximal der letzte bearbeitete Datensatz noch zu retten. Den Rest können Sie aus dem Gedächtnis reproduzieren.
Die Lösung
Wenn Sie nicht aus Sicherheitsgründen vor jeder Verwendung solcher Formulare eine Kopie der Datenbank anlegen möchten, lesen Sie weiter: Zum Warmmachen erfahren Sie zunächst, wie Sie Änderungen in einem einfachen Formular vom Öffnen des Formulars an verwerfen können, und dann geht es an die Unterformulare: Hier lernen Sie, wie Sie alle Änderungen am aktuellen Datensatz des Hauptformulars und den verknüpften Datensätzen des Unterformulars mit einem Mausklick rückgängig machen - wenn Sie den Datensatz nicht zwischenzeitlich verlassen oder gespeichert haben.
Abhilfe schafft die Verwendung so genannter Transaktionen, mit denen man eine Abfolge von Datenbankaktionen zusammenfassen und entweder komplett übernehmen oder wieder verwerfen kann.
HinweisDer Beitrag Transaktionen in Access in der vorliegenden Ausgabe von Access im Unternehmen (oder unter http://www.access-im-unternehmen.de, Shortlink 275) beschreibt im Detail, wie Transaktionen funktionieren und wie man sie einsetzen kann. (
Die wichtigsten Merkmale von Transaktionen in diesem Zusammenhang sind die, dass alle innerhalb einer Transaktion zu berücksichtigenden Vorgänge im Kontext eines Workspace-Objekts (DAO) oder eines Connection-Objekts (ADO) erfolgen müssen und dass alle auf diese Weise durchgeführten Änderungen entweder komplett zu speichern oder komplett zu verwerfen sind.
Beispieldatenbank
Die Beispieldatenbanken enthalten zwei sehr einfach aufgebaute Tabellen. Die erste heißt tblKunden und enthält neben dem Primärschlüsselfeld KundeID lediglich das Feld Kundenname. Die zweite Tabelle heißt tblProjekte und besteht aus den Feldern ProjektID, Projekt und KundeID. Letzteres ist ein Fremdschlüsselfeld für die Verknüpfung mit der Tabelle tblKunden.
HinweisDie hier vorgestellten Techniken funktionieren nur unter Access 2000 und höher. (
Undo in einfachen Formularen
Bereits bei der Arbeit mit einem Formular kann es sinnvoll sein, alle seit dem Öffnen des Formulars durchgeführten Änderungen wieder rückgängig zu machen. Wie das funktioniert, erfahren Sie in nachfolgendem Beispiel.
Das hier verwendete Formular basiert auf der Tabelle tblKunden und zeigt lediglich die beiden Felder dieser Tabelle an (s. Abb. 2). Zusätzlich enthält das Formular eine Schaltfläche namens cmdOK und eine namens cmdAbbrechen.
Letztere soll dafür sorgen, dass alle seit dem Öffnen des Formulars durchgeführten Änderungen verworfen werden.
Bis Access 2000 gab es keine Möglichkeit, die während der Arbeit mit einem Formular ablaufenden Aktionen in einer Transaktion zusammenzufassen. Der Grund sind die bereits weiter oben beschriebenen Workspace- beziehungsweise Connection-Objekte, in deren Kontext die Transaktion abläuft. Gebundene Formulare arbeiten Datenoperationen außerhalb der üblichen Bereiche ab, sodass sich dort keine Transaktion starten lässt.

Abb. 2: Beispielformular für das formularweite Undo
Der Unterschied ab Version 2000, der dies erlaubt, ist die Möglichkeit der Verwendung von Recordset-Objekten als Datenherkunft von Formularen.
Das Beispielformular ist derzeit noch an die Tabelle tblKunden gebunden, da Sie diese Tabelle als Datenherkunft des Formulars festgelegt haben.
Ab Access 2000 können Sie das Formular auch an ein Recordset-Objekt binden, das die Daten dieser Tabelle enthält.
Um das nachzuvollziehen, leeren Sie einfach die Eigenschaft Datenherkunft des Formulars und legen für die Ereigniseigenschaft die Prozedur aus Quellcode 1 an.
Private Sub Form_Open(Cancel As Integer)
Dim db As DAO.Database
Dim rst As DAO.Recordset
Set db = CurrentDb
Set rst = db.OpenRecordset _ ("tblKunden", dbOpenDynaset)
Set Me.Recordset = rst
End Sub
Quellcode 1
Wenn Sie das Formular erneut öffnen, stellen Sie fest, dass es wie vorher die gewünschten Daten anzeigt.
Private Sub Form_Delete(Cancel As Integer)
If bolDirty = False Then
bolDirty = True
DBEngine.BeginTrans
End If
End Sub
Private Sub Form_Dirty(Cancel As Integer)
If bolDirty = False Then
bolDirty = True
DBEngine.BeginTrans
End If
End Sub
Quellcode 2
Private Sub cmdOK_Click()
DoCmd.RunCommand acCmdSaveRecord
If bolDirty = True Then
DBEngine.CommitTrans
End If
DoCmd.Close acForm, Me.Name
End Sub
Quellcode 3
Private Sub cmdAbbrechen_Click()
If bolDirty = True Then
DBEngine.Rollback
End If
DoCmd.Close acForm, Me.Name
End Sub
Quellcode 4
Es gibt zwei Ereignisse, die beim Ändern oder Löschen von Daten ausgelöst werden: Bei Geändert und Beim Löschen. Das Ereignis, das als Erstes ausgelöst wird, muss die Transaktion starten. Damit bei mehreren Änderungen am Datenbestand nicht jedes Mal eine neue Transaktion gestartet wird, fragt die jeweilige Ereignisprozedur nach, ob schon eine Transaktion gestartet wurde. Für diesen Zweck legen Sie eine Variable namens bolDirty im Kopf des Moduls an:
Dim bolDirty As Boolean
Mit dieser Variable im Gepäck können Sie direkt die Prozeduren anlegen, die durch die Ereigniseigenschaften Bei Geändert und Beim Löschen ausgelöst werden (s. Quellcode 2). Die Prozeduren prüfen, ob die Variable bolDirty den Wert False hat, und starten in diesem Fall eine Transaktion.
Schließlich legen Sie für die Schaltflächen cmdOK und cmdAbbrechen noch zwei Prozeduren an, die prüfen, ob eine Transaktion begonnen wurde, und diese gegebenenfalls beenden oder abbrechen.
Die OK-Schaltfläche löst die Prozedur aus Quellcode 3 aus. Da der Benutzer diese Schaltfläche möglicherweise anklickt, obwohl der aktuelle Datensatz zwar bearbeitet, aber noch nicht gespeichert ist, speichert die Prozedur den Datensatz zunächst. Anschließend prüft die Prozedur den Wert der Variablen bolDirty und damit, ob eine Änderung durchgeführt und eine Transaktion ausgelöst wurde. Ist das der Fall, wird das Speichern der innerhalb der Transaktion durchgeführten Änderungen ausgelöst.
Die Abbrechen-Schaltfläche soll alle Änderungen, die seit dem Öffnen des Formulars durchgeführt wurden, rückgängig machen.
Dies ist nur nötig, wenn entweder Änderungen gespeichert wurden oder dies nicht der Fall ist und es offene Änderungen gibt. In beiden Fällen sorgt die Rollback-Methode für das Verwerfen der vorgenommenen Änderungen (s. Quellcode 4).
Probieren Sie das Formular nun aus. Wenn Sie etwa den Namen eines Kunden ändern und anschließend auf OK klicken, wird diese Änderung gespeichert und beim nächsten Öffnen wieder angezeigt. Klicken Sie auf Abbrechen und öffnen das Formular erneut, finden Sie den Datensatz unverändert vor.
Interessant wird es aber erst bei mehreren Änderungen: Löschen Sie einen Datensatz, fügen Sie einen neuen hinzu und ändern Sie vorhandene Datensätze - mit einem Klick auf die Abbrechen-Schaltfläche und dem erneuten Öffnen des Formulars finden Sie den alten Stand wieder vor.
Transaktionen in Haupt- und Unterformular
Genauso nützlich wie das Verwerfen von Änderungen mehrerer Datensätze in einem Formular ohne Unterformular ist die Vorgehensweise in Formularen mit Unterformular. Dabei kommt größtenteils die bereits beschriebene Technik zum Zuge, allerdings lässt sich die Lösung nur auf einen einzelnen Datensatz im Hauptformular und die verknüpften Datensätze im Unterformular ausführen.
Der Grund ist, dass wie bei dem vorherigen Beispiel eine Transaktion nur Datenbankoperationen innerhalb des angegebenen - in diesem Fall des aktuellen - Workspace berücksichtigen kann. Wie beim Hauptformular klappt dies auch in Unterformularen nur, wenn die Datenherkunft mittels Recordset-Objekt zugewiesen wird. Die Zuweisung muss bei jedem Datensatzwechsel im Hauptformular erneut durchgeführt werden.
Leider lässt sich der Recordset-Eigenschaft kein entsprechendes Objekt zuweisen, während eine Transaktion läuft. Daher müssen Sie auf den kompletten Komfort - also Änderungen in mehreren Hauptformulardatensätzen plus den im Unterformular angezeigten Daten rückgängig zu machen - leider verzichten und mit dem Verwerfen der Änderungen des aktuellen Datensatzes im Hauptformular und der verknüpften Daten im Unterformular vorlieb nehmen.
In einem Formular wie dem Bestellformular aus Abb. 1 könnten Sie also nach Lust und Laune Änderungen vornehmen und bestellte Artikel hinzufügen und wieder entfernen - nach einem Klick auf eine (hier nicht vorhandene) Abbrechen-Schaltfläche wäre alles wieder beim Alten.
Voraussetzungen
Für die Funktion der nachfolgend beschriebenen Technik müssen einige Voraussetzungen erfüllt sein:
Haupt- und Unterformular sind nicht gebunden, die Steuerelemente enthalten aber als Steuerelementinhalt die Felder der später hinzugefügten Datenherkunft.
Die Anzeige der Bestätigungen von Datensatzänderungen ist aktiviert (Dialog Optionen, Registerblatt Bearbeiten/Suchen). Sonst wird das Ereignis Nach Löschbestätigung nicht ausgelöst, ohne die die Lösung nicht funktioniert.
Aufbau der Formulare
Das Hauptformular ist genauso wie das Formular aus dem vorherigen Beispiel aufgebaut. Erstellen Sie es zu Beispielzwecken neu oder kopieren Sie das vorhandene Formular und entfernen Sie den enthaltenen Code.
Das Unterformular sieht im Entwurf wie in Abb. 3 aus. Zum Erstellen des Formulars stellen Sie zunächst die Datenherkunft auf die Tabelle tblKunden ein, ziehen alle Felder aus der Feldliste in den Detailbereich und löschen dann den für die Eigenschaft Datenherkunft angegebenen Wert - die Datenherkunft wird später dynamisch zugewiesen. Ordnen Sie die Steuerelemente wie in der Abbildung an.

Abb. 3: Entwurfsansicht des Unterformulars
Option Compare Database
Option Explicit
Dim mDirtyForm As Boolean
Dim mDeletedForm As Boolean
Dim db As DAO.Database
Dim wrk As DAO.Workspace
Public Property Get DirtyForm() As Boolean
DirtyForm = mDirtyForm
End Property
Public Property Let DirtyForm (bolDirtyForm As Boolean)
mDirtyForm = bolDirtyForm
End Property
Quellcode 5
Private Sub Form_Open(Cancel As Integer)
Dim rst As DAO.Recordset
Set db = DBEngine(0)(0)
Set wrk = DBEngine.Workspaces(0)
Set rst = db.OpenRecordset("tblKunden", _ dbOpenDynaset)
Set Me.Recordset = rst
End Sub
Quellcode 6

Abb. 4: Haupt- und Unterformular in der Entwurfsansicht
Fügen Sie nun das Formular frmProjekte als Unterformular in das Formular frmKundenProjekte ein (s. Abb. 4).
VBA-Code der Formulare
Die Funktionalität des Formulars aus dem ersten Beispiel muss nun angepasst und auf das Unterformular ausgedehnt werden. Das bedeutet, dass Transaktionen nicht mehr nur aus dem Hauptformular, sondern auch aus dem Unterformular heraus gestartet werden können. Dementsprechend müssen auch die Variablen zum Speichern der Zustände (bolDirtyForm und bolSavedForm) in beiden Formularen verfügbar sein.
Eine öffentliche Deklaration ist nicht besonders vorteilhaft, daher verwenden Sie Property Get- und Property Set-Anweisungen für den Zugriff auf diese Variablen. Deren Deklaration erfolgt nach wie vor im Codemodul des Hauptformulars. Der erste Teil des Moduls enthält die in Quellcode 5 abgebildeten Zeilen.
Öffnen des Hauptformulars
Das Öffnen des Hauptformulars löst die Ereignisprozedur Form_Open aus. Die Prozedur stellt die Variablen db und wrk auf das aktuelle Database- und Workspace-Objekt ein und weist der Recordset-Eigenschaft des Formulars ein Recordset-Objekt mit der Tabelle tblKunden zu (s. Quellcode 6).
Anzeigen eines neuen Datensatzes
Private Sub Form_Current()
Dim rst As DAO.Recordset
If Me.DirtyForm = True Then
DoCmd.RunCommand acCmdSaveRecord
wrk.CommitTrans
Me.DirtyForm = False
End If
If mDeletedForm = False Then
UnterformularAktualisieren
Else
mDeletedForm = False
End If
End Sub
Quellcode 7
Private Sub Form_Dirty(Cancel As Integer)
If Me.DirtyForm = False Then
Me.DirtyForm = True
wrk.BeginTrans
End If
End Sub
Quellcode 8
Private Sub Form_Delete(Cancel As Integer)
mDeletedForm = True
End Sub
Quellcode 9
Private Sub Form_AfterDelConfirm(Status As Integer)
UnterformularAktualisieren
End Sub
Quellcode 10
Das Ereignis Beim Anzeigen wird nach dem Öffnen des Formulars und bei jedem Datensatzwechsel ausgelöst.
Damit tritt die entsprechende Prozedur vor allem immer dann in Aktion, wenn der Benutzer einen Datensatz verlässt und einen neuen anzeigt. Das ist Grund genug, den aktuellen Datensatz und die verknüpften Daten zu speichern und eine eventuell aktive Transaktion zu beenden.
Die Prozedur Form_Current tut dies, indem sie zunächst den Datensatz speichert, die Transaktion beendet und die Eigenschaft DirtyForm auf den Wert False zurücksetzt.
Außerdem stellt die Prozedur die Datenherkunft des Unterformulars so ein, dass die zu dem im Hauptformular angezeigten Kunden passenden Projekte angezeigt werden (s. Quellcode 7).
Dies passiert allerdings nur, wenn die Variable mDeletedForm den Wert True hat - wann das der Fall ist, erfahren Sie direkt im Anschluss.
Das Ereignis Bei Änderung sorgt wie im vorherigen Beispiel dafür, dass die Eigenschaft DirtyForm auf den Wert True gesetzt und die Transaktion gestartet wird (s. Quellcode 8).
|