{"id":55000912,"date":"2013-12-01T00:00:00","date_gmt":"2020-05-22T21:31:45","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=912"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Undo_in_Haupt_und_Unterformular_mit_Klasse","status":"publish","type":"post","link":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/","title":{"rendered":"Undo in Haupt- und Unterformular mit Klasse"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Das Problem beim Einsatz von Haupt- und Unterformularen mit Daten aus verkn&uuml;pften Tabellen ist, dass der Benutzer diese als Einheit ansieht. Enth&auml;lt das Hauptformular eine Abbrechen-Schaltfl&auml;che, geht er davon aus, dass er die &auml;nderungen an Daten im Haupt- oder Unterformular damit komplett r&uuml;ckg&auml;ngig machen kann. Leider ist das nicht so &#8211; die &auml;nderungen im Unterformular bleiben gespeichert, und auch die Werte im Hauptformular lassen sich nach dem Speichern etwa durch einen Mausklick auf den Datensatzmarkierer nicht mehr r&uuml;ckg&auml;ngig machen. Grund genug, unsere bereits einmal beschriebene Technik nochmal unter die Lupe zu nehmen und in eine flexibel einsetzbare Klasse zu exportieren.<\/b><\/p>\n<p><b>Beispieltabellen und -formulare<\/b><\/p>\n<p>Im Rahmen dieses Beitrag verwenden wir die Tabellen der S&uuml;dsturm-Datenbank, genau genommen die Tabellen <b>tblBestellungen<\/b>, <b>tblKunden<\/b>, <b>tblBestelldetails <\/b>und <b>tblArtikel<\/b>. Au&szlig;erdem erstellen wir zun&auml;chst ein Hauptformular namens <b>frmBestellungen <\/b>und ein Unterformular namens <b>sfmBestellungen<\/b>, um die herk&ouml;mmliche Situation zu betrachten.<\/p>\n<p>Sp&auml;ter statten wir &auml;hnliche Formulare mit einem Klassenmodul und dessen Methoden und Eigenschaften aus, um das Undo sowohl im Haupt- als auch im Unterformular zu erm&ouml;glichen.<\/p>\n<p>Die an der Beispiell&ouml;sung beteiligten Tabellen finden Sie in der &uuml;bersicht aus Bild 1. Direkt in die Formulare eingebunden werden dabei nur die beiden Tabellen <b>tblBestellungen <\/b>und <b>tblBestelldetails<\/b>, die Daten der Tabelle <b>tblKunden <\/b>stehen im Hauptformular als Daten eines Kombinationsfeldes zur Verf&uuml;gung, die Daten der Tabelle <b>tblArtikel <\/b>in einem Kombinationsfeld im Unterformular.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_06\/pic_912_001.png\" alt=\"Tabellen der Beispieldatenbank\" width=\"400\" height=\"277,0318\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Tabellen der Beispieldatenbank<\/span><\/b><\/p>\n<p><b>Ausgangssituation<\/b><\/p>\n<p>Wir wollen ein Element der Benutzeroberfl&auml;che einer Anwendung optimieren, das im Hauptformular jeweils einen Datensatz der Tabelle <b>tblBestellungen <\/b>anzeigt und im Unterformular die damit verkn&uuml;pften Datens&auml;tze der Tabelle <b>tblBestelldetails<\/b>. Dazu weisen Sie einfach der Eigenschaft Datenherkunft von Haupt- und Unterformular die beiden Tabellen <b>tblBestellungen <\/b>und <b>tblBestelldetails <\/b>zu.<\/p>\n<p>Das Hauptformular soll in diesem Fall die Felder <b>BestellungID<\/b>, <b>KundeID<\/b>, <b>Bestelldatum<\/b>, <b>Lieferdatum <\/b>und <b>Versanddatum <\/b>der Tabelle <b>tblBestellungen <\/b>enthalten, das Unterformular die Felder <b>ArtikelID<\/b>, <b>Einzelpreis<\/b>, <b>Anzahl<\/b>, <b>Rabatt <\/b>und <b>BestellungID <\/b>der Tabelle <b>tblBestelldetails<\/b>.<\/p>\n<p>In der Entwurfsansicht sieht der Aufbau der beiden Formulare nun wie in Bild 2 aus. Das Unterformularsteuerelement haben wir <b>sfm <\/b>genannt, damit wir es sp&auml;ter im Code vom eingebetteten Unterformular <b>sfmBestellungen <\/b>unterscheiden k&ouml;nnen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_06\/pic_912_002.png\" alt=\"Haupt- und Unterformular in der Entwurfsansicht\" width=\"500\" height=\"359,401\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Haupt- und Unterformular in der Entwurfsansicht<\/span><\/b><\/p>\n<p>Die beiden Eigenschaften <b>Verkn&uuml;pfen von <\/b>und <b>Verkn&uuml;pfen nach <\/b>des Unterformularsteuerelements <b>sfm <\/b>enthalten beide den Namen des Feldes <b>BestellungID<\/b>. Auf diese Weise zeigt das Unterformular jeweils die zum Datensatz des Hauptformulars passenden Daten an. Das Hauptformular enth&auml;lt noch zwei Schaltfl&auml;chen. <b>cmdOK <\/b>l&ouml;st diesen Code aus:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdOK_Click()\r\n     DoCmd.Close acForm, Me.Name\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Damit schlie&szlig;t sie das Formular und speichert den aktuellen Zustand der Daten. Die zweite Schaltfl&auml;che enth&auml;lt die Beschriftung <b>Abbrechen <\/b>oder, in diesem Fall, <b>Verwerfen<\/b>. Sie f&uuml;hrt lediglich die <b>Undo<\/b>-Methode des Hauptformular aus und verwirft somit die &auml;nderungen seit dem letzten Speichern im Hauptformular:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdVerwerfen_Click()\r\n     Me.Undo\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Dies ist f&uuml;r den Benutzer mitunter irref&uuml;hrend, da er den Eindruck erhalten kann, dass sich mit einer solchen Schaltfl&auml;che alle seit dem letzten &ouml;ffnen get&auml;tigten &auml;nderungen verwerfen lassen. Dies ist mitnichten so: &auml;nderungen im Unterformular werden auf keinen Fall verworfen, denn diese werden sp&auml;testens dann in der zugrunde liegenden Tabelle gespeichert, wenn das Unterformular den Fokus verliert. Und das ist zwangsl&auml;ufig der Fall, wenn der Benutzer ein Steuerelement des Hauptformulars bet&auml;tigt, in diesem Fall die <b>Verwerfen<\/b>-Schaltfl&auml;che.<\/p>\n<p>Andersherum wird der Datensatz im Hauptformular sofort gespeichert, wenn der Benutzer den Fokus in das Unterformular verschiebt. Beim klassischen Fall, also dem Anlegen der grundlegenden Bestelldaten wie Kunde, Bestelldatum et cetera und dem anschlie&szlig;enden Hinzuf&uuml;gen von Bestellpositionen bewirkt das Bet&auml;tigen der <b>Verwerfen<\/b>-Schaltfl&auml;che schlicht und einfach nichts. Zu diesem Zeitpunkt sind alle Daten bereits gespeichert. Da die <b>Undo<\/b>-Methode genau die gleiche Wirkung hat wie das Bet&auml;tigen der Escape-Taste, macht auch diese keine der get&auml;tigten und gespeicherten Eingaben mehr r&uuml;ckg&auml;ngig.<\/p>\n<p>Dies wollen wir nun &auml;ndern &#8211; und zwar mit einer Klasse, die alle dazu n&ouml;tigen Funktionen enth&auml;lt. Das Ganze ist nicht gerade trivial, denn wir m&uuml;ssen einige F&auml;lle beachten. Bei der Programmierung einer solchen Klasse wird man kaum auf einen Schlag alle denkbaren Konstellationen erschlagen. Man beginnt also damit, einfache Vorf&auml;lle abzudecken, und programmiert deren Behandlung. Wenn dies funktioniert, schaut man sich den n&auml;chsten Vorfall an und programmiert weiter.<\/p>\n<p>Beim Testen des Programmierfortschritts sollte man jedoch auch immer wieder die vorher behandelten Vorf&auml;lle testen. Damit stellen Sie sicher, dass Sie durch das Hinzuf&uuml;gen oder &auml;ndern des bestehenden Codes die bereits vorhandene Funktionalit&auml;t beeinflussen.<\/p>\n<p>Grunds&auml;tzlich soll die Klasse daf&uuml;r sorgen, dass alle seit dem &ouml;ffnen eines Datensatzes get&auml;tigten &auml;nderungen mit einem Klick auf die <b>Verwerfen<\/b>-Schaltfl&auml;che wieder r&uuml;ckg&auml;ngig gemacht werden k&ouml;nnen.<\/p>\n<p>Es gibt eine Ausnahme: Das L&ouml;schen einer kompletten Bestellung, also eines Datensatzes der Tabelle <b>tblBestellungen <\/b>und damit auch aller damit verkn&uuml;pften Datens&auml;tze der Tabelle <b>tblBestellpositionen<\/b>. In dem Moment, in dem der Benutzer den Datensatz im Hauptformular l&ouml;scht, spielt es keine Rolle mehr, ob die  bisherigen &auml;nderungen der Transaktion durchgef&uuml;hrt werden oder nicht.<\/p>\n<p><b>Tests festlegen<\/b><\/p>\n<p>Um zu pr&uuml;fen, ob alles jederzeit wie gew&uuml;nscht funktioniert, legen wir einige Tests zurecht &#8211; diese sollten alle paar neuen Codezeilen gepr&uuml;ft werden, um vorzeitig ein Programmieren in die falsche Richtung zu verhindern:<\/p>\n<ul>\n<li>Eine Bestellung (Bestelldatum <b>1.1.2013<\/b>) ohne Bestellpositionen wird erstellt und gespeichert. Ist die Bestellung noch vorhanden<\/li>\n<li>Eine Bestellung (Bestelldatum <b>2.1.2013<\/b>) mit einer Bestellposition wird erstellt und gespeichert. Sind die Bestellung und die Bestellpositionen noch vorhanden<\/li>\n<li>Eine Bestellung (Bestelldatum <b>3.1.2013<\/b>) wird erstellt und gespeichert (zum Beispiel durch einen Klick auf den Datensatzmarkierer) und mit der Schaltfl&auml;che <b>Verwerfen<\/b> verworfen. Ist die Bestellung verschwunden<\/li>\n<li>Eine Bestellung (Bestelldatum <b>4.1.2013<\/b>) mit einer Bestellposition wird erstellt und endg&uuml;ltig gespeichert (durch Wechseln zu einem anderen Datensatz oder Schlie&szlig;en und erneutes &ouml;ffnen des Formulars). Dann wird zu dieser Bestellung eine Bestellposition hinzugef&uuml;gt und gespeichert. Entfernt <b>Verwerfen <\/b>diese Position wieder, w&auml;hrend die Bestellung selbst erhalten bleibt<\/li>\n<li>Eine Bestellung (<b>5.1.2013<\/b>) erstellen, eine Bestellposition hinzuf&uuml;gen und Formular schlie&szlig;en. Bestellung wieder anzeigen, Bestellposition l&ouml;schen und mit <b>Verwerfen <\/b>wiederherstellen.<\/li>\n<li>Eine Bestellung (<b>6.1.2013<\/b>) erstellen, eine Bestellposition hinzuf&uuml;gen und Formular schlie&szlig;en. Bestellung wieder anzeigen, Bestellposition l&ouml;schen, mit <b>Verwerfen <\/b>wiederherstellen, wieder l&ouml;schen und wieder herstellen.<\/li>\n<\/ul>\n<p>Am sch&ouml;nsten w&auml;re es nat&uuml;rlich, wenn man solche und &auml;hnliche Tests der Benutzeroberfl&auml;che automatisch ablaufen lassen k&ouml;nnte. Dies ist jedoch sehr aufwendig zu realisieren. Gegebenenfalls k&uuml;mmern wir uns zu einem sp&auml;teren Zeitpunkt um ein entsprechendes Tool.<\/p>\n<p><b>Transaktionen<\/b><\/p>\n<p>Der Wechsel zum Unterformular speichert bereits die Daten im Hauptformular und der Wechsel von einem Datensatz zum n&auml;chsten im Unterformular das Gleiche mit den Datens&auml;tzen im Unterformular. Wie wollen wir dann daf&uuml;r sorgen, dass die vollst&auml;ndigen &auml;nderungen am aktuellen Datensatz im Hauptformular samt den verkn&uuml;pften Daten im Unterformular r&uuml;ckg&auml;ngig gemacht werden k&ouml;nnen<\/p>\n<p>Daf&uuml;r gibt es verschiedene Ans&auml;tze. Der erste w&auml;re, den aktuellen Datensatz im Hauptformular vor der Bearbeitung im Hauptformular in einer tempor&auml;ren Tabelle zu speichern &#8211; ebenso wie die Daten des Unterformulars. Beim Mausklick auf die Schaltfl&auml;che <b>OK <\/b>werden dann alle Daten in die Originaltabellen &uuml;bertragen, beim Anklicken von <b>Verwerfen <\/b>l&ouml;schen wir einfach die Daten der tempor&auml;ren Tabellen.<\/p>\n<p>Der zweite Ansatz verwendet Transaktionen. Das bedeutet, dass wir bei der ersten &auml;nderung an dem im Hauptformular angezeigten Datensatz oder an einem der Datens&auml;tze im Unterformular eine Transaktion starten m&uuml;ssen. Das gilt nat&uuml;rlich auch daf&uuml;r, wenn wir im Hauptformular einen neuen Datensatz anlegen oder Datens&auml;tze zum Unterformular hinzuf&uuml;gen.<\/p>\n<p>Der Einsatz von Transaktionen ist recht einfach: Sie definieren eine Workspace-Variable, die den Workspace der aktuellen Datenbank referenziert (mit <b>DBEngine.Workspaces(0)<\/b>.  Dieses Workspace-Objekt stellt dann die folgenden drei Methoden zur Verf&uuml;gung:<\/p>\n<ul>\n<li><b>BeginTrans<\/b>: Startet eine Transaktion.<\/li>\n<li><b>CommitTrans<\/b>: Speichert die seit dem Start der Transaktion durchgef&uuml;hrten &auml;nderungen.<\/li>\n<li><b>Rollback<\/b>: Verwirft alle &auml;nderungen seit Beginn der Transaktion.<\/li>\n<\/ul>\n<p>Nun muss man allerdings wissen, welche &auml;nderungen vom Start der Transaktion an protokolliert werden und entsprechend r&uuml;ckg&auml;ngig gemacht werden k&ouml;nnen. Dabei handelt es lediglich um solche Transaktionen, die &uuml;ber das <b>Database<\/b>-Objekt der aktuellen Datenbank durchgef&uuml;hrt wurden. Wenn Sie also etwa nach dem Aufruf von <b>BeginTrans <\/b>mit der <b>db.Execute<\/b>-Methode eine Aktionsabfrage durchf&uuml;hren oder die Daten eines DAO-Recordsets mit <b>Edit<\/b>\/<b>AddNew <\/b>und <b>Update <\/b>&auml;ndern, k&ouml;nnen Sie diese &auml;nderungen durch <b>CommitTrans <\/b>speichern oder durch <b>Rollback <\/b>verwerfen.<\/p>\n<p>Dummerweise laufen die &auml;nderungen, die Sie an den Daten eines &uuml;ber die Eigenschaft <b>Datenherkunft <\/b>an eine Datenquelle gebundenen Formulars durchf&uuml;hren, nicht im Kontext einer Transaktion. Und hier wird es interessant: Sie k&ouml;nnen ein Formular n&auml;mlich auch &uuml;ber die <b>Recordset<\/b>-Eigenschaft mit den Daten aus einer Tabelle oder Abfrage f&uuml;llen. Und wenn Sie dieses Recordset im Kontext einer Transaktion mit <b>OpenRecordset <\/b>erstellen und der Datenherkunft des Formulars beziehungsweise des Unterformulars zuweisen, k&ouml;nnen Sie auch die &auml;nderungen am Formular durch ein <b>Rollback <\/b>verwerfen oder durch ein <b>CommitTrans <\/b>speichern.<\/p>\n<p>Das hat allerdings auch kleinere Nachteile: Normalerweise weisen Sie dem Hauptformular die eine Tabelle der 1:n-Beziehung als Datenherkunft zu und dem Unterformular die andere Tabelle. Dabei stellen Sie die beiden Eigenschaften <b>Verkn&uuml;pfen von <\/b>und <b>Verkn&uuml;pfen nach <\/b>des Unterformular-Steuerelements auf das Fremdschl&uuml;sselfeld im Unterformular und das Prim&auml;rschl&uuml;sselfeld im Hauptformular ein, damit das Unterformular jeweils die passenden Daten zum Hauptformular anzeigt.<\/p>\n<p>Wenn Sie Haupt- und Unterformular jedoch etwa im Ereignis <b>Beim Laden <\/b>des Hauptformulars mit den zuvor erstellten <b>Recordset<\/b>-Objekten versehen, sind die beiden Eigenschaften <b>Verkn&uuml;pfen von <\/b>und <b>Verkn&uuml;pfen nach <\/b>wirkungslos. Deshalb m&uuml;ssen Sie dem Unterformular beim Wechsel des Datensatzes im Hauptformular jeweils ein neues Recordset zuweisen, dass die zum Datensatz im Hauptformular passenden Datens&auml;tze enth&auml;lt.<\/p>\n<p>So schlimm ist das aber auch nicht &#8211; wir m&uuml;ssen diesen Vorgang ja auch nur einmal programmieren.<\/p>\n<p><b>Formular anpassen<\/b><\/p>\n<p>Nachdem wir wissen, dass wir das Formular und das Unterformular mit einer Klasse ausstatten wollen, die daf&uuml;r sorgt, dass &auml;nderungen an den kompletten angezeigten Daten des aktuellen Datensatzes des Hauptformulars durch einen Mausklick auf eine Schaltfl&auml;che r&uuml;ckg&auml;ngig gemacht werden, bereiten wir zun&auml;chst das Formular vor.<\/p>\n<p><b>Formular und Unterformular erstellen<\/b><\/p>\n<p>Grunds&auml;tzlich sollten Haupt- und Unterformular zun&auml;chst mit den Tabellen oder Abfragen als Datenherkunft ausgestattet werden, die sie im normalen Betrieb verwenden w&uuml;rden.<\/p>\n<p>Dies geschieht aus reiner Bequemlichkeit: Wir k&ouml;nnen so n&auml;mlich die Steuerelemente der jeweiligen Datenherkunft aus der Feldliste in die gew&uuml;nschten Bereiche des Formulars ziehen. Danach leeren wir einfach die Eigenschaft <b>Datenherkunft <\/b>des Formulars.<\/p>\n<p>Auf die gleiche Weise gehen wir beim Unterformular vor. Hier ist darauf zu achten, dass Sie auch die beiden Eigenschaften <b>Verkn&uuml;pfen von <\/b>und <b>Verkn&uuml;pfen nach <\/b>des Unterformular-Steuerelements leeren (s. Bild 3).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_06\/pic_912_003.png\" alt=\"Die Verkn&uuml;pfungseigenschaften zwischen Haupt- und Unterformular werden geleert.\" width=\"575\" height=\"388,3231\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Die Verkn&uuml;pfungseigenschaften zwischen Haupt- und Unterformular werden geleert.<\/span><\/b><\/p>\n<p><b>Klasse erstellen<\/b><\/p>\n<p>Anschlie&szlig;end erstellen wir schon das Klassenmodul (VBA-Editor, <b>Einf&uuml;gen|Klassenmodul<\/b>). Dieses nennen wir schlicht und einfach <b>clsUndo<\/b>.<\/p>\n<p>Normalerweise h&auml;tten wir die Ereignisse, die dazu f&uuml;hren, dass eine Transaktion gestartet, beendet oder verworfen wird, direkt in den Klassenmodulen des Haupt- und des Unterformulars untergebracht. Dann m&uuml;ssten Sie diesen Code jedoch, wenn Sie diesen in einem anderen Formular weiterverwenden wollten, jeweils in das entsprechende Klassenmodul des Formulars\/Unterformulars &uuml;bertragen.<\/p>\n<p>Wenn Sie eine solche Funktionalit&auml;t einmal programmiert haben und diese dann auf einen anderen Anwendungsfall &uuml;bertragen m&ouml;chten, sind meist individuelle Anpassungen n&ouml;tig &#8211; was zu Fehlern f&uuml;hren kann, insbesondere dadurch bedingt, dass man nicht mehr genau wei&szlig;, an welchen Schrauben man wie drehen muss. Wenn Sie hingegen die komplette Funktion in einem eigenen Klassenmodul kapseln und dieses von dem damit auszustattenden Formular einfach nur instanzieren und mit einigen Eigenschaften ausstatten m&uuml;ssen, haben Sie leichtes Spiel.<\/p>\n<p><b>Referenzen an das Klassenmodul &uuml;bergeben<\/b><\/p>\n<p>Die Ereignisse, Variablen und Prozeduren, die Sie normalerweise im Klassenmodul des betroffenen Formulars angelegt h&auml;tten, deklarieren Sie nun komplett im Klassenmodul <b>clsUndo<\/b>. Dabei ist allerdings zu beachten, dass dieses ja nicht wei&szlig;, auf welches Formular es sich beziehen soll. Genauso wei&szlig; es nicht, welches Unterformular betroffen ist (wenn mehrere vorhanden sind) und welche Schaltfl&auml;chen f&uuml;r das &uuml;bernehmen oder Verwerfen der &auml;nderungen verantwortlich sind.<\/p>\n<p>Damit wir der Klasse beim Laden des Formulars die ben&ouml;tigten Informationen &uuml;bergeben k&ouml;nnen, legen wir in der Klasse zun&auml;chst einige Variablen fest, welche die Verweise auf diese Objekte speichern sollen. Dies sieht wie folgt aus:<\/p>\n<p>Wir ben&ouml;tigen also zwei Variablen des Typs <b>Form <\/b>und zwei des Typs <b>CommandButton<\/b>. F&uuml;r alle vier Objekte wollen wir innerhalb des Klassenmoduls <b>clsUndo <\/b>eigene Ereignisprozeduren implementieren. Das geschieht prinzipiell genauso wie im Klassenmodul eines Formulars, allerdings steht die einfache M&ouml;glichkeit zum Erstellen solcher Ereignisprozeduren durch Eintragen des Wertes <b>[Ereignisprozedur] <\/b>in die jeweilige Ereigniseigenschaft und anschlie&szlig;endes Klicken auf die Schaltfl&auml;che mit den drei Punkten (&#8230;) rechts neben der Eigenschaft nicht zur Verf&uuml;gung.<\/p>\n<p>Bliebe noch die M&ouml;glichkeit, die entsprechenden Objektvariablen im linken Kombinationsfeld des VBA-Editors auszuw&auml;hlen und das passende Ereignis aus dem rechten Kombinationsfeld zu erg&auml;nzen. Das gelingt aber auch nur unter einer Bedingung: Wenn die passende Objektvariable mit dem <b>WithEvents<\/b>-Schl&uuml;sselwort deklariert wurde &#8211; und genau dies erledigen wir wie folgt:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>WithEvents m_Form<span style=\"color:blue;\"> As <\/span>Form\r\n<span style=\"color:blue;\">Private <\/span>WithEvents m_Subform<span style=\"color:blue;\"> As <\/span>Form\r\n<span style=\"color:blue;\">Private <\/span>WithEvents m_OKButton<span style=\"color:blue;\"> As <\/span> CommandButton\r\n<span style=\"color:blue;\">Private <\/span>WithEvents m_CancelButton<span style=\"color:blue;\"> As <\/span> CommandButton<\/pre>\n<p>Anschlie&szlig;end f&uuml;gen Sie &uuml;ber die beiden Kombinationsfelder im Codefenster die ben&ouml;tigten Ereignisprozeduren hinzu (s. Bild 4).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_06\/pic_912_004.png\" alt=\"Hinzuf&uuml;gen einer Ereignisprozedur f&uuml;r eine mit WithEvents deklarierte Objektvariable\" width=\"575\" height=\"355,5792\" \/><\/p>\n<p><!--30percent--><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Hinzuf&uuml;gen einer Ereignisprozedur f&uuml;r eine mit WithEvents deklarierte Objektvariable<\/span><\/b><\/p>\n<p>Wie gelangen aber nun die Verweise auf die entsprechenden Elemente des Formulars in diese Variablen &#8211; und wie sorgen wir daf&uuml;r, dass das Formular wei&szlig;, dass es im Klassenmodul <b>clsUndo <\/b>noch Prozeduren gibt, die beim Eintreten verschiedener Ereignisse des Formulars, des Unterformulars oder der beiden Schaltfl&auml;chen ausgel&ouml;st werden sollen Dies geschieht in drei Schritten:<\/p>\n<ul>\n<li>Wir legen in der Klasse <b>clsUndo <\/b>f&uuml;r jedes betroffene Objekt eine <b>Property Set<\/b>-Prozedur an, welche die &uuml;bergebenen Objekte der Variablen <b>m_Form <\/b>et cetera zuweist.<\/li>\n<li>Diese <b>Property Set<\/b>-Prozeduren statten wir gleichzeitig mit Anweisungen aus, welche die Ereigniseigenschaften wie etwa <b>Beim Anzeigen <\/b>(<b>OnCurrent<\/b>) des Unterformulars mit dem Wert <b>[Event Procedure] <\/b>f&uuml;llen. Dies entspricht dem Setzen des Wertes <b>[Ereignisprozedur] <\/b>f&uuml;r die Ereigniseigenschaften im Eigenschaftsfenster des Formulars.<\/li>\n<li>Der Ereignisprozedur <b>Beim Laden <\/b>des Formulars f&uuml;gen wir dann Code hinzu, der die Klasse instanziert und dieser die Verweise auf das Formular, das Unterformular und die beiden Schaltfl&auml;chen &uuml;bergibt.<\/li>\n<\/ul>\n<p><b>Prozeduren als Eigenschaften<\/b><\/p>\n<p>Legen wir zun&auml;chst die vier <b>Property Set<\/b>-Prozeduren in der Klasse <b>clsUndo <\/b>an. Diese erscheinen dann sp&auml;ter im Klassenmodul des Formulars, das die Klasse <b>clsUndo <\/b>instanziert, als Eigenschaften des Klassenobjekts.<\/p>\n<p>Wir beginnen mit den beiden Schaltfl&auml;chen zum Speichern und zum Verwerfen der &auml;nderungen.<\/p>\n<p>Die <b>OK<\/b>-Schaltfl&auml;che soll &uuml;ber die folgende <b>Property Set<\/b>-Prozedur an die Klasse <b>clsUndo <\/b>&uuml;bergeben werden:<\/p>\n<pre><span style=\"color:blue;\">Public Property <span style=\"color:blue;\">Set<\/span> <\/span>OKButton(cmd<span style=\"color:blue;\"> As <\/span> CommandButton)\r\n     <span style=\"color:blue;\">Set<\/span> m_OKButton = cmd\r\n     <span style=\"color:blue;\">With<\/span> m_OKButton\r\n         .OnClick = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Die Zeilen dieser Prozedur weisen zun&auml;chst die mit dem Parameter <b>cmd <\/b>&uuml;bergebene Schaltfl&auml;che der weiter oben mit dem Parameter <b>WithEvents <\/b>deklarierten Variablen <b>m_CancelButton <\/b>zu. Die folgende Anweisung entspricht dem Einstellen der Ereigniseigenschaft <b>Beim Klicken <\/b>auf den Wert <b>[Ereignisprozedur]<\/b>.<\/p>\n<p>Die folgende <b>Property Set<\/b>-Prozedur erledigt das Gleiche f&uuml;r die Schaltfl&auml;che, mit der eventuelle &auml;nderungen an den Daten im Haupt- oder Unterformular verworfen werden sollen:<\/p>\n<pre><span style=\"color:blue;\">Public Property <span style=\"color:blue;\">Set<\/span> <\/span>CancelButton(cmd <span style=\"color:blue;\"> As <\/span>CommandButton)\r\n     <span style=\"color:blue;\">Set<\/span> m_CancelButton = cmd\r\n     <span style=\"color:blue;\">With<\/span> m_CancelButton\r\n         .OnClick = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Nun schauen wir uns die Prozedur an, welche mit dem Parameter <b>frm <\/b>den Verweis auf das Unterformular entgegennimmt. Dieser landet in der Variablen <b>m_Subform<\/b>. F&uuml;r das Unterformular stellt die Prozedur gleich eine ganze Reihe Ereigniseigenschaften auf den Wert <b>[Event Procedure] <\/b>ein (dies ist der eigentliche Wert &#8211; im Eigenschaftsfenster erscheint dann gegebenenfalls <b>[Ereigniseigenschaft]<\/b>). Die Prozedur finden Sie in Listing 1.<\/p>\n<pre><span style=\"color:blue;\">Public Property <span style=\"color:blue;\">Set<\/span> <\/span>Subform(frm<span style=\"color:blue;\"> As <\/span>Form)\r\n     <span style=\"color:blue;\">Set<\/span> m_Subform = frm\r\n     <span style=\"color:blue;\">With<\/span> m_Subform\r\n         .AfterDelConfirm = \"[Event Procedure]\"\r\n         .AfterUpdate = \"[Event Procedure]\"\r\n         .BeforeDelConfirm = \"[Event Procedure]\"\r\n         .OnDelete = \"[Event Procedure]\"\r\n         .OnDirty = \"[Event Procedure]\"\r\n         .OnError = \"[Event Procedure]\"\r\n         .OnOpen = \"[Event Procedure]\"\r\n         .OnUndo = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Property Set-Prozedur zum &uuml;bergeben des Verweises auf das Unterformular<\/span><\/b><\/p>\n<p>Die <b>Property Set<\/b>-Methode f&uuml;r die &uuml;bergabe des Verweises auf das Hauptformular ist die aufwendigste: Sie erwartet mit dem Parameter <b>frm <\/b>den Verweis auf das Hauptformular. Diesen speichert sie alsbald in der privat deklarierten Variablen <b>m_Form<\/b>. F&uuml;r <b>m_Form<\/b> legt sie dann die Ereigniseigenschaften fest, die im Klassenmodul <b>clsUndo <\/b>implementiert werden sollen (s. Listing 2).<\/p>\n<pre><span style=\"color:blue;\">Public Property <span style=\"color:blue;\">Set<\/span> <\/span>Form(frm<span style=\"color:blue;\"> As <\/span>Form)\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Set<\/span> m_Form = frm\r\n     <span style=\"color:blue;\">With<\/span> m_Form\r\n         .AfterUpdate = \"[Event Procedure]\"\r\n         .BeforeDelConfirm = \"[Event Procedure]\"\r\n         .OnCurrent = \"[Event Procedure]\"\r\n         .OnDelete = \"[Event Procedure]\"\r\n         .OnDirty = \"[Event Procedure]\"\r\n         .OnError = \"[Event Procedure]\"\r\n         .OnOpen = \"[Event Procedure]\"\r\n         .OnUnload = \"[Event Procedure]\"\r\n         .OnUndo = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     ''Aus Form_Open\r\n     <span style=\"color:blue;\">Set<\/span> db = DBEngine(0)(0)\r\n     <span style=\"color:blue;\">Set<\/span> wrk = DBEngine.Workspaces(0)\r\n     <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(m_RecordsourceForm, dbOpenDynaset)\r\n     <span style=\"color:blue;\">Set<\/span> m_Form.Recordset = rst\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Zuweisen des Verweises auf das Hauptformular plus einige weitere Aktionen<\/span><\/b><\/p>\n<p>Au&szlig;erdem folgen noch eine ganze Reihe weiterer Aktionen, die normalerweise im <b>Form_Load<\/b>-Ereignis des Formulars ausgef&uuml;hrt werden m&uuml;ssten. Allerdings k&ouml;nnen wir dieses Formular nicht im Klassenmodul implementieren, weil das Klassenmodul selbst ja erst im <b>Form_Load<\/b>-Ereignis des Formulars instanziert wird. Sprich: Das Ereignis <b>Form_Load <\/b>ist dann bereits abgehakt und wird im Gegensatz zu einigen anderen Ereignissen wie etwa <b>Form_Current <\/b>(<b>Beim Anzeigen<\/b>) nicht erneut aufgerufen.<\/p>\n<p>Dabei weist die Prozedur zun&auml;chst der wie folgt im Kopf des Moduls <b>clsUndo <\/b>deklarierten Objektvariablen <b>db <\/b>einen Verweis auf die aktuell ge&ouml;ffnete Datenbank zu:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database<\/pre>\n<p>Au&szlig;erdem f&uuml;llt sie ein Objekt namens <b>wrk <\/b>mit einem Verweis auf den aktuellen Workspace. Das <b>Workspace<\/b>-Objekt stellt ja, wie oben erl&auml;utert, die Methoden zum Starten, Beenden und Abbrechen der Transaktion zur Verf&uuml;gung. <b>wrk <\/b>wird wie folgt deklariert:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>wrk<span style=\"color:blue;\"> As <\/span>DAO.Workspace<\/pre>\n<p>Au&szlig;erdem &ouml;ffnet die Prozedur ein Recordset auf Basis der in der Variablen <b>m_RecordsourceForm <\/b>gespeicherten Datenherkunft f&uuml;r das Hauptformular.<\/p>\n<p>Die Deklaration dieser Variablen erfolgt ebenfalls im Kopf des Klassenmoduls:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>m_RecordsourceForm<span style=\"color:blue;\"> As String<\/span><\/pre>\n<p>F&uuml;r die &uuml;bergabe der zu verwendenden Tabelle oder Abfrage stellt die Klasse mit folgender <b>Property Let<\/b>-Prozedur eine Eigenschaft namens <b>RecordsourceForm <\/b>zur Verf&uuml;gung:<\/p>\n<pre><span style=\"color:blue;\">Public Property Let <\/span>RecordsourceForm(  str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_RecordsourceForm = str\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Schlie&szlig;lich weist die Prozedur das Recordset der Eigenschaft <b>Recordset <\/b>des mit <b>m_Form <\/b>referenzierten Hauptformulars zu. Damit w&auml;re die Voraussetzung f&uuml;r die Transaktion geschaffen und das Hauptformular mit den entsprechenden Daten gef&uuml;llt.<\/p>\n<p><b>Weitere Variablen<\/b><\/p>\n<p>Damit die Klasse auch noch wei&szlig;, welche Datenherkunft sie f&uuml;r das Unterformular verwenden soll, deklarieren wir folgende Variable:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>m_RecordsourceSubform<span style=\"color:blue;\"> As String<\/span><\/pre>\n<p>Diese f&uuml;llen wir &uuml;ber die folgende <b>Property Let<\/b>-Prozedur:<\/p>\n<pre><span style=\"color:blue;\">Public Property Let <\/span>RecordsourceSubform (str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_RecordsourceSubform = str\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Au&szlig;erdem ben&ouml;tigen wir noch zwei Variablen, mit denen wir den Namen des Prim&auml;rschl&uuml;sselfeldes der Datenherkunft des Hauptformulars und den Namen des Fremdschl&uuml;sselwertes des Unterformulars speichern:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>m_PKForm<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_FKSubform<span style=\"color:blue;\"> As String<\/span><\/pre>\n<p>Auch diese k&ouml;nnen wir vom instanzierenden Formular aus per Eigenschaft &uuml;bergeben. Dabei unterst&uuml;tzen uns die folgenden <b>Property Let<\/b>-Prozeduren:<\/p>\n<pre><span style=\"color:blue;\">Public Property Let <\/span>PKForm(str<span style=\"color:blue;\"> As <\/span> String)\r\n     m_PKForm = str\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>FKSubform(str<span style=\"color:blue;\"> As <\/span> String)\r\n     m_FKSubform = str\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p><b>Schauplatzwechsel<\/b><\/p>\n<p>Bevor wir in die eigentlichen Funktionen der Klasse einsteigen, schauen wir uns die Ereignisprozedur an, die durch das Ereignis <b>Beim Laden <\/b>des Hauptformulars ausgel&ouml;st wird. Zun&auml;chst erh&auml;lt das Klassenmodul des Hauptformulars <b>frmBestellungenUndoKlasse <\/b>eine Variable, welche die Klasse <b>clsUndo <\/b>referenziert:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>objUndo<span style=\"color:blue;\"> As <\/span>clsUndo<\/pre>\n<p>Dann schauen wir uns die Prozedur <b>Form_Load <\/b>an (s. Listing 3). Diese instanziert zun&auml;chst die Klasse <b>clsUndo <\/b>und speichert den Verweis auf die neue Instanz in der Variablen <b>objUndo<\/b>.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>Form_Load()\r\n     <span style=\"color:blue;\">Set<\/span> objUndo = <span style=\"color:blue;\">New<\/span> clsUndo\r\n     <span style=\"color:blue;\">With<\/span> objUndo\r\n         <span style=\"color:blue;\">Set<\/span> objUndo = <span style=\"color:blue;\">New<\/span> clsUndo\r\n         <span style=\"color:blue;\">With<\/span> objUndo\r\n             .RecordsourceForm = \"tblBestellungen\"\r\n             .RecordsourceSubform = \"tblBestelldetails\"\r\n             .PKForm = \"BestellungID\"\r\n             .FKSubform = \"BestellungID\"\r\n             <span style=\"color:blue;\">Set<\/span> .Subform = Me!sfm.Form\r\n             <span style=\"color:blue;\">Set<\/span> .Form = Me\r\n             <span style=\"color:blue;\">Set<\/span> .OKButton = Me!cmdOK\r\n             <span style=\"color:blue;\">Set<\/span> .CancelButton = Me!cmdVerwerfen\r\n         End <span style=\"color:blue;\">With<\/span>\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Diese Prozedur instanziert die Klasse clsUndo und &uuml;bergibt die notwendigen Verweise.<\/span><\/b><\/p>\n<p>Dann &uuml;bergibt sie die Informationen &uuml;ber die beiden Formulare, die beiden Schaltfl&auml;chen sowie den Namen der Datenherk&uuml;nfte f&uuml;r das Haupt- und das Unterformular und die Namen des Prim&auml;rschl&uuml;sselfeldes der Datenherkunft des Hauptformulars und des Fremdschl&uuml;sselfeldes der Datenherkunft des Unterformulars.<\/p>\n<p><b>Interessante Zust&auml;nde<\/b><\/p>\n<p>Im Rahmen der nachfolgend beschriebenen Ereignisprozeduren, welche auf verschiedene Ereignisse im Haupt- und Unterformular reagieren, gibt es einige Zust&auml;nde, die wir auf jeden Fall in Variablen speichern m&uuml;ssen, um diese zu bestimmten Gelegenheiten abzufragen:<\/p>\n<ul>\n<li><b>bolDirtyForm<\/b>: Wurden die Daten im Haupt- oder Unterformular seit dem &ouml;ffnen oder seit dem Commit oder Rollback der vorherigen Transaktion ge&auml;ndert<\/li>\n<li><b>bolSavedForm<\/b>: Wurden &auml;nderungen seit dem Start einer Transaktion durchgef&uuml;hrt und gespeichert<\/li>\n<li><b>bolDeletedForm<\/b>: Wurde ein Datensatz gel&ouml;scht<\/li>\n<\/ul>\n<p>Die drei Eigenschaften werden wie folgt deklariert:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>bolDirtyForm<span style=\"color:blue;\"> As Boolean<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>bolSavedForm<span style=\"color:blue;\"> As Boolean<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>bolDeletedForm<span style=\"color:blue;\"> As Boolean<\/span><\/pre>\n<p>Zur Eigenschaft <b>bolDirtyForm <\/b>ist noch zu erw&auml;hnen, dass man prinzipiell &uuml;ber die Eigenschaft <b>Dirty <\/b>eines Formulars pr&uuml;ft, ob der aktuelle Datensatz seit dem letzten Speichern ge&auml;ndert wurde. Gleiches gilt f&uuml;r das Unterformular. Allerdings wollen wir ja formular&uuml;bergreifend feststellen, ob Daten ge&auml;ndert wurden.<\/p>\n<p>Wenn der Benutzer im Hauptformular einen neuen Datensatz anlegt, wird <b>Dirty <\/b>auf <b>True <\/b>eingestellt. Wenn er dann zum Unterformular wechselt, ist der Datensatz im Hauptformular aber bereits wieder gespeichert.<\/p>\n<p>Wenn wir also vom Start einer Transaktion bis zum <b>CommitTrans <\/b>oder <b>Rollback <\/b>speichern wollen, ob die Daten eines Formulars ge&auml;ndert wurden, ben&ouml;tigen wir diese Variable namens <b>bolDirtyForm<\/b>.<\/p>\n<p><b>Anzeigen eines neuen Datensatzes im Hauptformular<\/b><\/p>\n<p>Bevor wir &uuml;berhaupt mit dem &auml;ndern von Daten beginnen und die damit verbundenen Transaktionen starten, ausf&uuml;hren oder verwerfen k&ouml;nnen, m&uuml;ssen wir beim Anzeigen eines Datensatzes im Hauptformular die entsprechenden Daten im Unterformular nachreichen. Dies erledigen wir in der Prozedur, die durch das Ereignis <b>Beim Anzeigen <\/b>des Hauptformulars ausgel&ouml;st wird.<\/p>\n<p>Diese Prozedur sieht wie in Listing 4 aus und erledigt zun&auml;chst einige Dinge mehr als ben&ouml;tigt. Wichtig ist an dieser Stelle, dass die hinteren Anweisungen das Recordset f&uuml;r das Unterformular zusammenstellen &#8211; und zwar in der Regel so, dass es alle Datens&auml;tze der in <b>m_RecordsourceSubform <\/b>angegebenen Abfrage oder Tabelle liefert, deren in <b>m_FKSubform <\/b>gespeichertes Fremdschl&uuml;sselfeld den Wert des Prim&auml;rschl&uuml;sselfeldes des Hauptformulars aus <b>m_PKForm <\/b>enth&auml;lt. Es kann nat&uuml;rlich auch geschehen, dass das Hauptformular einen neuen, leeren Datensatz anzeigt &#8211; dann soll auch das Unterformular eine leere Datenherkunft erhalten (das Kriterium <b>1=2 <\/b>liefert eine leere Datensatzgruppe).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Current()\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     ...\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> m_Form.NewRecord<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"SELECT * FROM \" & m_RecordsourceSubform _\r\n             & \" WHERE \" & m_FKSubform & \" = \" _\r\n             & Nz(m_Form.Recordset.Fields(m_PKForm), 0), dbOpenDynaset)\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> rst = db.OpenRecordset(\"SELECT * FROM \" & m_RecordsourceSubform & \" WHERE 1=2\", dbOpenDynaset)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> m_Subform.Recordset = rst\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Diese Ereignisprozedur wird beim Wechsel des Datensatzes im Haupformular ausgel&ouml;st.<\/span><\/b><\/p>\n<p>Die &uuml;brigen Anweisungen erl&auml;utern wir weiter unten.<\/p>\n<p><b>Starten einer Transaktion<\/b><\/p>\n<p>Eine Transaktion soll in den folgenden F&auml;llen gestartet werden:<\/p>\n<ul>\n<li>wenn der Benutzer die Daten eines Feldes im Hauptformular &auml;ndert,<\/li>\n<li>wenn der Benutzer die Daten eines Feldes im Unterformular &auml;ndert oder<\/li>\n<li>wenn der Benutzer einen Datensatz im Unterformular l&ouml;scht.<\/li>\n<\/ul>\n<p>Was geschieht, wenn der Benutzer im Haupt- oder Unterformular zu einem neuen Datensatz wechselt Dies &auml;ndert noch nichts an den Daten &#8211; der Datensatzzeiger wird lediglich auf einem neuen, leeren Datensatz positioniert.<\/p>\n<p>Das tats&auml;chliche Anlegen des Datensatzes beginnt erst, wenn der Benutzer den Inhalt eines der Felder der beteiligten Datenherk&uuml;nfte &auml;ndert.<\/p>\n<p>Die ersten genannten Aktionen l&ouml;sen das Ereignis <b>m_Form_Dirty <\/b>des Hauptformulars beziehungsweise das Ereignis <b>m_Subform_Dirty <\/b>des Unterformulars aus.<\/p>\n<p>Schauen wir uns zun&auml;chst das Ereignis f&uuml;r das Hauptformular an, das Sie in folgendem Listing finden:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Dirty(Cancel<span style=\"color:blue;\"> As <\/span> Integer)\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">False<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">True<\/span>\r\n         wrk.BeginTrans\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Dieses Ereignis pr&uuml;ft, ob bereits zuvor Daten ge&auml;ndert wurden. Diesen Zustand speichert die bereits beschriebene Variable <b>bolDirtyForm<\/b>.<\/p>\n<p>Ist dies nicht der Fall, wird <b>bolDirtyForm <\/b>auf <b>True <\/b>eingestellt. Au&szlig;erdem startet die Prozedur eine Transaktion. Hier ist wichtig, dass dies vor dem Speichern der ge&auml;nderten Daten geschieht.<\/p>\n<p>Eine &auml;hnliche Ereignisprozedur m&uuml;ssen wir nat&uuml;rlich auch f&uuml;r das Unterformular implementieren. Dieses soll ebenfalls den Wert der Variablen <b>bolDirtyForm <\/b>pr&uuml;fen und diese gegebenenfalls auf <b>True <\/b>einstellen und eine Transaktion starten.<\/p>\n<p>Sie hat aber noch eine weitere Aufgabe, die dadurch entsteht, dass wir ja keine Synchronisierung von Haupt- und Unterformular durch die Eigenschaften <b>Verkn&uuml;pfen von <\/b>und <b>Verkn&uuml;pfen nach <\/b>des Unterformular-Steuerelements mehr vornehmen.<\/p>\n<p>Diese Aufgabe besteht darin, zu pr&uuml;fen, ob soeben ein neuer Datensatz angelegt wurde, und dem Fremdschl&uuml;sselfeld dieses Datensatzes gegebenenfalls den Prim&auml;rschl&uuml;sselwert des Datensatzes aus dem Hauptformular zuzuweisen. Um die entsprechenden Felder zu referenzieren, nutzen wir die in den Variablen <b>m_PKForm<\/b> und <b>m_FKSubform <\/b>gespeicherten Namen des Prim&auml;rschl&uuml;sselfeldes und des Fremdschl&uuml;sselfeldes der im Formular abgebildeten Beziehung (s. Listing 5).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_Dirty(Cancel<span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">If <\/span>m_Subform.NewRecord<span style=\"color:blue;\"> Then<\/span>\r\n         m_Subform.Controls(m_FKSubform) = m_Form.Controls(m_PKForm)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">False<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">True<\/span>\r\n         wrk.BeginTrans\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Diese Ereignisprozedur feuert beim &auml;ndern der Daten im Unterformular.<\/span><\/b><\/p>\n<p><b>Speichern eines Datensatzes<\/b><\/p>\n<p>Egal, ob Sie einen Datensatz im Haupt- oder Unterformular speichern: Dadurch soll die durch eine &auml;nderung der Daten eingeleitete Transaktion nat&uuml;rlich nicht ausgef&uuml;hrt werden.<\/p>\n<p>Vielmehr wollen wir in der durch das Ereignis <b>Nach Aktualisierung <\/b>ausgel&ouml;sten Prozedur festhalten, dass seit dem Starten der Transaktion &auml;nderungen an den Datens&auml;tzen in Haupt- und Unterformular gespeichert wurden.<\/p>\n<p>Dies erledigen wir wie in der folgenden Prozedur durch Einstellen der Variablen <b>bolSavedForm <\/b>auf den Wert <b>True<\/b>:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_AfterUpdate()\r\n     bolSavedForm = <span style=\"color:blue;\">True<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Dieses Ereignis wird durch verschiedene Aktionen ausgel&ouml;st &#8211; das Anklicken des Datensatzes markierers, das Wechseln zu einem anderen Datensatz et cetera.<\/p>\n<p><b>Speichern eines Datensatzes im Unterformular<\/b><\/p>\n<p>Auch beim Speichern eines Datensatzes im Unterformular soll die Variable <b>bolSavedForm <\/b>den Wert <b>True <\/b>erhalten:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_AfterUpdate()\r\n     bolSavedForm = <span style=\"color:blue;\">True<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b>Speichern der &auml;nderungen mit OK<\/b><\/p>\n<p>Wenn der Benutzer auf die Schaltlf&auml;che <b>OK <\/b>klickt, l&ouml;st dies die Prozedur aus dem folgenden Listing aus:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_OKButton_Click()\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> m_Form.NewRecord<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Call<\/span> AenderungenUebernehmen\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     DoCmd.Close acForm, m_Form.Name\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Diese Prozedur pr&uuml;ft, ob das Formular aktuell einen neuen Datensatz anzeigt. In diesem Fall m&uuml;ssen nat&uuml;rlich keine &auml;nderungen gespeichert werden &#8211; es d&uuml;rfte dann auch keine Transaktion gestartet worden sein. Anderenfalls ruft sie die Prozedur <b>AenderungenUebernehmen <\/b>auf, die wie folgt aussieht:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>AenderungenUebernehmen()\r\n     DoCmd.RunCommand acCmdSaveRecord\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         wrk.CommitTrans\r\n         bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Die Prozedur speichert zun&auml;chst den aktuellen Stand des Datensatzes. Dann pr&uuml;ft sie, ob im Formular seit dem &ouml;ffnen beziehungsweise nach dem Beenden der vorherigen Transaktion &auml;nderungen durchgef&uuml;hrt wurden &#8211; dar&uuml;ber gibt die Variable <b>bolDirtyForm <\/b>Auskunft. Ist dies der Fall, wird die aktuelle Transaktion abgeschlossen und die Variable <b>bolDirtyForm <\/b>auf <b>False <\/b>eingestellt.<\/p>\n<p><b>Formular anderweitig schlie&szlig;en<\/b><\/p>\n<p>F&uuml;r den Fall, dass der Benutzer das Formular nicht mit der <b>OK<\/b>-Schaltfl&auml;che schlie&szlig;t, ruft die Klasse die Prozedur <b>AenderungenUebernehmen <\/b>nochmals auf, wenn das Ereignis <b>Beim Entladen <\/b>ausgel&ouml;st wird:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Unload(Cancel <span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">Call<\/span> AenderungenUebernehmen\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b>Undo im Haupt- oder Unterformular<\/b><\/p>\n<p>Der Benutzer kann beispielsweise durch Bet&auml;tigen der <b>Escape<\/b>-Taste die &auml;nderungen am aktuellen Datensatz r&uuml;ckg&auml;ngig machen. Wenn er also beispielsweise ein Feld im Hauptformular ge&auml;ndert hat und die <b>Escape<\/b>-Taste dr&uuml;ckt, werden die get&auml;tigten und noch nicht gespeicherten &auml;nderungen verworfen.<\/p>\n<p>Innerhalb des hier zu programmierenden Undo f&uuml;r Haupt- und Unterformular ist dies insofern relevant, als dass wir in bestimmten F&auml;llen die zuvor auf True eingestellte Variable <b>bolDirtyForm <\/b>wieder auf <b>False <\/b>einstellen m&uuml;ssen.<\/p>\n<p>Dieser Fall tritt ein, wenn dies die erste und einzige bisherige &auml;nderung ist und seit Beginn der Transaktion noch keine &auml;nderungen gespeichert wurden. Ob dies der Fall ist, sagt uns die Variable <b>bolSavedForm<\/b>. Damit wir diese Variable pr&uuml;fen und gegebenenfalls <b>bolDirtyForm <\/b>auf <b>False <\/b>einstellen k&ouml;nnen, legen wir f&uuml;r das Hauptformular eine Ereignisprozedur an, die durch das Ereignis <b>Bei R&uuml;ckg&auml;ngig <\/b>ausgel&ouml;st wird:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Undo(Cancel<span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">If <\/span>bolSavedForm = <span style=\"color:blue;\">False<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Nat&uuml;rlich kann der Benutzer auch &auml;nderungen im Unterformular mit der <b>Escape<\/b>-Taste r&uuml;ckg&auml;ngig machen. Also m&uuml;ssen wir auch f&uuml;r das entsprechende Ereignis im Unterformular eine passende Ereignisprozedur anlegen:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_Undo(Cancel<span style=\"color:blue;\"> As <\/span> Integer)\r\n     <span style=\"color:blue;\">If <\/span>bolSavedForm = <span style=\"color:blue;\">False<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b>L&ouml;schen von Datens&auml;tzen im Unterformular<\/b><\/p>\n<p>Ein interessanter Fall ist auch das L&ouml;schen von Datens&auml;tzen im Unterformular. Sobald der Benutzer einen Datensatz im Unterformular l&ouml;scht, m&uuml;ssen genau wie beim &auml;ndern eines Datensatzes eine Transaktion gestartet und die Variable <b>bolDirtyForm <\/b>auf den Wert <b>True <\/b>eingestellt werden.<\/p>\n<p>Dies ist allerdings wiederum nur n&ouml;tig, wenn dies noch nicht geschehen ist &#8211; also pr&uuml;ft die Prozedur, die durch das Ereignis <b>Beim L&ouml;schen <\/b>des Unterformular ausgel&ouml;st wird, den Wert von <b>bolDirtyForm <\/b>und leitet die entsprechenden Schritte ein:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_Delete(Cancel<span style=\"color:blue;\"> As <\/span> Integer)\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">False<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">True<\/span>\r\n         wrk.BeginTrans\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Nach dem L&ouml;schen l&ouml;st das Unterformular noch das Ereignis <b>Vor L&ouml;schbest&auml;tigung <\/b>aus. Dieses sorgt lediglich daf&uuml;r, dass keine Meldung angezeigt wird (s. Listing 6).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_BeforeDelConfirm(Cancel<span style=\"color:blue;\"> As Integer<\/span>, Response<span style=\"color:blue;\"> As Integer<\/span>)\r\n     Response = acDataErrContinue\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Verhindern der R&uuml;ckfrage vor dem L&ouml;schen eines Datensatzes<\/span><\/b><\/p>\n<p>Schlie&szlig;lich ist das L&ouml;schen eines Datensatzes im Unterformular ja eine &auml;nderung der aktuell angezeigten Daten, und zwar eine abgeschlossene. Damit wir beim Wechseln des Datensatzes im Hauptformular wissen, ob wir eine Transaktion abschlie&szlig;en m&uuml;ssen, stellen wir in der durch das Ereignis <b>Nach L&ouml;schbest&auml;tigung <\/b>ausgel&ouml;sten Prozedur die Variable <b>bolSavedForm <\/b>auf <b>True <\/b>ein (s. Listing 7).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Subform_AfterDelConfirm(Status<span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">If <\/span>Status = acDeleteOK<span style=\"color:blue;\"> Then<\/span>\r\n         bolSavedForm = <span style=\"color:blue;\">True<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Registrieren des L&ouml;schvorgangs mit der Variablen bolSavedForm<\/span><\/b><\/p>\n<p><b>L&ouml;schen von Datens&auml;tzen im Hauptformular<\/b><\/p>\n<p>Nat&uuml;rlich kann der Benutzer auch einen Datensatz im Hauptformular l&ouml;schen. In diesem Fall k&ouml;nnen die Arbeiten am aktuellen Datensatz als erledigt betrachtet werden, denn dieser wird ja ohnehin gel&ouml;scht.<\/p>\n<p>Sollte also <b>bolDirtyForm <\/b>den Wert <b>True <\/b>liefern, machen wir die bisherigen &auml;nderungen im Rahmen der Transaktion mit <b>Rollback <\/b>r&uuml;ckg&auml;ngig. Au&szlig;erdem stellen wir im Ereignis <b>Beim L&ouml;schen <\/b>des Hauptformulars die Variable <b>bolDeletedForm <\/b>auf <b>True <\/b>ein:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Delete(Cancel<span style=\"color:blue;\"> As <\/span> Integer)\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm<span style=\"color:blue;\"> Then<\/span>\r\n         wrk.Rollback\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     bolDeletedForm = <span style=\"color:blue;\">True<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Au&szlig;erdem soll die Ereignisprozedur, die durch das Ereignis <b>Vor L&ouml;schbest&auml;tigung <\/b>des Hauptformulars ausgel&ouml;st wird, eventuelle L&ouml;schmeldungen unterbinden (s. Listing 8).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_BeforeDelConfirm(Cancel<span style=\"color:blue;\"> As Integer<\/span>, Response<span style=\"color:blue;\"> As Integer<\/span>)\r\n     Response = acDataErrContinue\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: Unterbinden von L&ouml;schmeldungen<\/span><\/b><\/p>\n<p>Au&szlig;erdem findet noch ein Datensatzwechsel im Hauptformular statt, denn der bisher angezeigte Datensatz ist ja nicht mehr vorhanden. Wie dieser behandelt wird, erfahren Sie weiter unten.<\/p>\n<p><b>Verwerfen von &auml;nderungen<\/b><\/p>\n<p>Wenn der Benutzer auf die mit der Variablen <b>m_CancelButton <\/b>referenzierte Schaltfl&auml;che klickt, sollen alle &auml;nderungen am aktuellen Datensatz des Hauptformulars und an den damit verkn&uuml;pften Datens&auml;tzen des Unterformulars r&uuml;ckg&auml;ngig gemacht werden &#8211; einschlie&szlig;lich gel&ouml;schter Datens&auml;tze im Unterformular.<\/p>\n<p>Dies erledigt die Prozedur aus Listing 9.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_CancelButton_Click()\r\n     <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> bolSavedForm<span style=\"color:blue;\"> Then<\/span>\r\n             <span style=\"color:blue;\">If <\/span>m_Form.Dirty<span style=\"color:blue;\"> Then<\/span>\r\n                 m_Form.Undo\r\n             <span style=\"color:blue;\">Else<\/span>\r\n                 RunCommand acCmdSaveRecord\r\n             <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         wrk.Rollback\r\n         intTransactions = 0\r\n         bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 9: Implementierung des Beim Klicken-Ereignisses der Abbrechen-Schaltfl&auml;che<\/span><\/b><\/p>\n<p>Die Prozedur pr&uuml;ft zun&auml;chst den Wert der Variablen <b>bolDirtyForm<\/b>. Hat diese den Wert <b>False<\/b>, wurden keine &auml;nderungen vorgenommen und auch keine Transaktion gestartet. In diesem Fall geschieht nichts.<\/p>\n<p>Anderenfalls pr&uuml;ft die Prozedur, ob bereits &auml;nderungen gespeichert wurden &#8211; in diesem Fall enth&auml;lt die Variable <b>bol-SavedForm <\/b>den Wert <b>True<\/b>. Dann werden die Transaktion r&uuml;ckg&auml;ngig gemacht und <b>bolDirtyForm <\/b>auf <b>False <\/b>eingestellt.<\/p>\n<p><b>Datensatzwechsel im Hauptformular<\/b><\/p>\n<p>Wenn der Benutzer von einem Datensatz  im Hauptformular zum n&auml;chsten wechselt, l&ouml;st dies das Ereignis <b>Beim Anzeigen <\/b>des Hauptformulars aus.<\/p>\n<p>Den hinteren Teil dieser Prozedur haben wir bereits weiter oben besprochen, nun kommen wir zu den vorderen Zeilen (s. Listing 10).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>m_Form_Current()\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">If <\/span>bolDeletedForm = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         wrk.CommitTrans\r\n         bolSavedForm = <span style=\"color:blue;\">False<\/span>\r\n         bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n         bolDeletedForm = <span style=\"color:blue;\">False<\/span>\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         <span style=\"color:blue;\">If <\/span>bolDirtyForm = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n             DoCmd.RunCommand acCmdSaveRecord\r\n             wrk.CommitTrans\r\n             bolSavedForm = <span style=\"color:blue;\">False<\/span>\r\n             bolDirtyForm = <span style=\"color:blue;\">False<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     ...\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 10: Aktionen beim Datensatzwechsel<\/span><\/b><\/p>\n<p>Zu diesem Zeitpunkt m&uuml;ssen &auml;nderungen, die am aktuellen Datensatz des Hauptformulars und an den verkn&uuml;pften Datens&auml;tzen im Unterformular durchgef&uuml;hrt wurden, gespeichert werden.<\/p>\n<p>Ein Wechsel des angezeigten Datensatzes kann jedoch auch durch das L&ouml;schen eines Datensatzes im Hauptformular ausgel&ouml;st worden sein. In diesem Fall hat die Variable <b>bolDeletedForm <\/b>den Wert <b>True<\/b>. Die aktuelle Transaktion wird durchgef&uuml;hrt und die Variablen <b>bolDeletedForm<\/b>, <b>bolSavedForm <\/b>und <b>bolDirtyForm <\/b>auf den Wert <b>False <\/b>eingestellt.<\/p>\n<h3>Downloads zu diesem Beitrag<\/h3>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>UndoInFormularUndUnterformular.mdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/{9251B0CB-DE4C-482C-A75C-2CD5BD63FBE3}\/aiu_912.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Das Problem beim Einsatz von Haupt- und Unterformularen mit Daten aus verkn&uuml;pften Tabellen ist, dass der Benutzer diese als Einheit ansieht. Enth&auml;lt das Hauptformular eine Abbrechen-Schaltfl&auml;che, geht er davon aus, dass er die &Auml;nderungen an Daten im Haupt- oder Unterformular damit komplett r&uuml;ckg&auml;ngig machen kann. Leider ist das nicht so &#8211; die &Auml;nderungen im Unterformular bleiben gespeichert, und auch die Werte im Hauptformular lassen sich nach dem Speichern etwa durch einen Mausklick auf den Datensatzmarkierer nicht mehr r&uuml;ckg&auml;ngig machen. Grund genug, unsere bereits einmal beschriebene Technik nochmal unter die Lupe zu nehmen und in eine flexibel einsetzbare Klasse zu exportieren.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662013,66062013,44000023],"tags":[],"class_list":["post-55000912","post","type-post","status-publish","format-standard","hentry","category-662013","category-66062013","category-Mit_Formularen_arbeiten"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.9 (Yoast SEO v27.4) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Undo in Haupt- und Unterformular mit Klasse - Access im Unternehmen<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Undo in Haupt- und Unterformular mit Klasse\" \/>\n<meta property=\"og:description\" content=\"Das Problem beim Einsatz von Haupt- und Unterformularen mit Daten aus verkn&uuml;pften Tabellen ist, dass der Benutzer diese als Einheit ansieht. Enth&auml;lt das Hauptformular eine Abbrechen-Schaltfl&auml;che, geht er davon aus, dass er die &Auml;nderungen an Daten im Haupt- oder Unterformular damit komplett r&uuml;ckg&auml;ngig machen kann. Leider ist das nicht so - die &Auml;nderungen im Unterformular bleiben gespeichert, und auch die Werte im Hauptformular lassen sich nach dem Speichern etwa durch einen Mausklick auf den Datensatzmarkierer nicht mehr r&uuml;ckg&auml;ngig machen. Grund genug, unsere bereits einmal beschriebene Technik nochmal unter die Lupe zu nehmen und in eine flexibel einsetzbare Klasse zu exportieren.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/\" \/>\n<meta property=\"og:site_name\" content=\"Access im Unternehmen\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-22T21:31:45+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21\" \/>\n<meta name=\"author\" content=\"Andr\u00e9 Minhorst\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andr\u00e9 Minhorst\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"30\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/\"},\"author\":{\"name\":\"Andr\u00e9 Minhorst\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\"},\"headline\":\"Undo in Haupt- und Unterformular mit Klasse\",\"datePublished\":\"2020-05-22T21:31:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/\"},\"wordCount\":5293,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/d6af0afbbca543ebbba8d5793de4aa21\",\"articleSection\":[\"2013\",\"6\\\/2013\",\"Mit Formularen arbeiten\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/\",\"name\":\"Undo in Haupt- und Unterformular mit Klasse - Access im Unternehmen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/d6af0afbbca543ebbba8d5793de4aa21\",\"datePublished\":\"2020-05-22T21:31:45+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#primaryimage\",\"url\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/d6af0afbbca543ebbba8d5793de4aa21\",\"contentUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/d6af0afbbca543ebbba8d5793de4aa21\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Undo_in_Haupt_und_Unterformular_mit_Klasse\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Undo in Haupt- und Unterformular mit Klasse\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"name\":\"Access im Unternehmen\",\"description\":\"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access\",\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/access-im-unternehmen.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\",\"name\":\"Andr\u00e9 Minhorst Verlag\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"contentUrl\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"width\":370,\"height\":111,\"caption\":\"Andr\u00e9 Minhorst Verlag\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\",\"name\":\"Andr\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"caption\":\"Andr\u00e9 Minhorst\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Undo in Haupt- und Unterformular mit Klasse - Access im Unternehmen","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/","og_locale":"de_DE","og_type":"article","og_title":"Undo in Haupt- und Unterformular mit Klasse","og_description":"Das Problem beim Einsatz von Haupt- und Unterformularen mit Daten aus verkn&uuml;pften Tabellen ist, dass der Benutzer diese als Einheit ansieht. Enth&auml;lt das Hauptformular eine Abbrechen-Schaltfl&auml;che, geht er davon aus, dass er die &Auml;nderungen an Daten im Haupt- oder Unterformular damit komplett r&uuml;ckg&auml;ngig machen kann. Leider ist das nicht so - die &Auml;nderungen im Unterformular bleiben gespeichert, und auch die Werte im Hauptformular lassen sich nach dem Speichern etwa durch einen Mausklick auf den Datensatzmarkierer nicht mehr r&uuml;ckg&auml;ngig machen. Grund genug, unsere bereits einmal beschriebene Technik nochmal unter die Lupe zu nehmen und in eine flexibel einsetzbare Klasse zu exportieren.","og_url":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/","og_site_name":"Access im Unternehmen","article_published_time":"2020-05-22T21:31:45+00:00","og_image":[{"url":"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21","type":"","width":"","height":""}],"author":"Andr\u00e9 Minhorst","twitter_card":"summary_large_image","twitter_misc":{"Verfasst von":"Andr\u00e9 Minhorst","Gesch\u00e4tzte Lesezeit":"30\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#article","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/"},"author":{"name":"Andr\u00e9 Minhorst","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f"},"headline":"Undo in Haupt- und Unterformular mit Klasse","datePublished":"2020-05-22T21:31:45+00:00","mainEntityOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/"},"wordCount":5293,"commentCount":0,"publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#primaryimage"},"thumbnailUrl":"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21","articleSection":["2013","6\/2013","Mit Formularen arbeiten"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/","url":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/","name":"Undo in Haupt- und Unterformular mit Klasse - Access im Unternehmen","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#primaryimage"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#primaryimage"},"thumbnailUrl":"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21","datePublished":"2020-05-22T21:31:45+00:00","breadcrumb":{"@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#primaryimage","url":"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21","contentUrl":"http:\/\/vg05.met.vgwort.de\/na\/d6af0afbbca543ebbba8d5793de4aa21"},{"@type":"BreadcrumbList","@id":"https:\/\/access-im-unternehmen.de\/Undo_in_Haupt_und_Unterformular_mit_Klasse\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/access-im-unternehmen.de\/"},{"@type":"ListItem","position":2,"name":"Undo in Haupt- und Unterformular mit Klasse"}]},{"@type":"WebSite","@id":"https:\/\/access-im-unternehmen.de\/#website","url":"https:\/\/access-im-unternehmen.de\/","name":"Access im Unternehmen","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access","publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/access-im-unternehmen.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/access-im-unternehmen.de\/#organization","name":"Andr\u00e9 Minhorst Verlag","url":"https:\/\/access-im-unternehmen.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/","url":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","contentUrl":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","width":370,"height":111,"caption":"Andr\u00e9 Minhorst Verlag"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","caption":"Andr\u00e9 Minhorst"}}]}},"_links":{"self":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000912","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/comments?post=55000912"}],"version-history":[{"count":0,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000912\/revisions"}],"wp:attachment":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/media?parent=55000912"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/categories?post=55000912"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/tags?post=55000912"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}