Home > Artikel > Ausgabe 6/2017 > Haupt- und Unterformular synchron

Haupt- und Unterformular synchron

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Manchmal zeigt das Hauptformular die gleichen Daten wie das Unterformular an – beispielsweise, wenn das Unterformular als Übersichtsliste die Daten in der Datenblattansicht liefert und ein Wechseln des Datensatzes im Unterformular zur Anzeige der Details des gleichen Datensatzes in im Hauptformular führen soll. Soll dies auch umgekehrt der Fall sein, also das beim Wechsel des Datensatzes im Hauptformular der entsprechende Datensatz im Unterformular markiert wird, wird es kompliziert. Mögliche Probleme und die Auflösung liefert dieser Beitrag.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1706_HauptUndUnterformularSynchron.accdb.

Beispielkonstellation

Wir wollen die Daten einer Tabelle namens tblHersteller aus unserer Beispiellösung Handyverwaltung herausnehmen und an diesem Beispiel zeigen, wie Haupt- und Unterformular synchron gehalten werden können. Dazu legen wir zunächst ein neues Unterformular namens sfmHersteller an, das die Abfrage qryHerstellerBezeichnungNachAlphabet als Datenherkunft verwendet. Diese Abfrage liefert nur die beiden Felder HerstellerID und Bezeichnung der Tabelle tblHersteller, und zwar alphabetisch nach dem Inhalt des Feldes Bezeichnung sortiert.

Das Hauptformular frmHersteller verwendet eine ähnliche Abfrage namens qryHerstellerNachBezeichnung, welche allerdings alle Felder der Tabelle tblHersteller enthält. Auch hier gibt es eine Sortierung nach dem Feld Bezeichnung.

Das Unterformular platzieren wir wie in Bild 1 im Hauptformular. Nun wollen wir, dass das Hauptformular und das Unterformular immer den gleichen Datensatz anzeigen beziehungsweise dass im Unterformular immer der Datensatz markiert ist, der aktuell im Hauptformular angezeigt wird und umgekehrt.

Haupt- und Unterformular zur Anzeige der Daten aus der gleichen Datenherkunft

Bild 1: Haupt- und Unterformular zur Anzeige der Daten aus der gleichen Datenherkunft

Erster Versuch

Dabei stellen wir uns nun vor, dass Folgendes funktionieren könnte und probieren es aus. Für das Ereignis Beim Anzeigen des Hauptformulars legen wir die folgende Ereignisprozedur an:

Private Sub Form_Current()

Me!sfmHersteller.Form.Recordset!HerstellerID = Me!HerstellerID

End Sub

Diese Prozedur stellt also den Datensatzmarkierer für das Recordset im Unterformular auf den Datensatz ein, dessen Primärschlüsselfeld HerstellerID dem passenden Wert im Hauptformular entspricht.

Für das entsprechende Ereignis des Unterformulars verwenden wir eine ganz ähnliche Ereignisprozedur, welche die folgende Zeile enthält:

Private Sub Form_Current()

Me.Parent.Recordset.FindFirst "HerstellerID = " & Me!HerstellerID

End Sub

Diese versucht, den Datensatzzeiger im Recordset des Hauptformulars auf den Datensatz einzustellen, dessen HerstellerID im Unterformular ausgewählt wurde. Öffnen wir nun das Formular in der Formularansicht, erhalten wir die Ansicht aus Bild 2 – es klappt also wie gewünscht.

Beim Anzeigen erscheinen die Datensätze im Haupt- und Unterformular synchron.

Bild 2: Beim Anzeigen erscheinen die Datensätze im Haupt- und Unterformular synchron.

Das ist auch der Fall, wenn wir zwischen den Datensätzen im Haupt- oder im Unterformular navigieren. Es tritt erst ein Problem auf, wenn wir den Datensatzzeiger im Haupt- oder Unterformular auf einen neuen, leeren Datensatz verschieben.

Dann erhalten wir die Fehlermeldung aus Bild 3. Der Grund für diese Fehlermeldung ist, dass Me!HerstellerID zu diesem Zeitpunkt, also beim Anlegen eines neuen Datensatzes, den Wert Null enthält. Dadurch sieht der Ausdruck, der als Kriterium der FindFirst-Methode verwendet wird, wie folgt aus:

Fehler beim Anzeigen eines neuen, leeren Datensatzes

Bild 3: Fehler beim Anzeigen eines neuen, leeren Datensatzes

HerstellerID =

Diesen Ausdruck kann Access nicht auswerten, also wird die Fehlermeldung aus der Abbildung generiert.

Ändern wir die beiden Ereignisprozeduren also etwas ab, um die Fehlermeldung zu verhindern. Dazu fassen wir die Angabe des Wertes Me!Hersteller einfach mit der Funktion Nz ein und geben als zweiten Parameter den Wert 0 an, was dafür sorgt, dass die Funktion den Wert 0 liefert, wenn HerstellerID den Wert Null aufweist:

'Hauptformular

Private Sub Form_Current()

Me!sfmHersteller.Form.Recordset.FindFirst "HerstellerID = " & Nz(Me!HerstellerID, 0)

End Sub

'Unterformular

Private Sub Form_Current()

Me.Parent.Recordset.FindFirst "HerstellerID = " & Nz(Me!HerstellerID, 0)

End Sub

Dies führt nun immerhin dazu, dass beim Ansteuern eines neuen, leeren Datensatzes im Haupt- oder Unterformular kein Fehler mehr ausgelöst wird. Allerdings finden wir hier ein etwas merkwürdiges Verhalten vor, denn wenn Sie etwa mit der Tabulator-Taste im Unterformular vom letzten auf den neuen, leeren Datensatz wechseln, springt der Datensatzzeiger auf den ersten Datensatz im Unterformular. Das Gleiche geschieht auch manchmal, wenn Sie mit der Maus von einem anderen Datensatz auf den neuen, leerenDatensatz wechseln. Wenn Sie hingegen soeben auf den letzten Datensatz gewechselt sind und der Datensatzzeiger dadurch auf den ersten Datensatz gesprungen ist und dann mit der Maus auf den neuen, leeren Datensatz klicken, verbleibt der Datensatzzeiger auf diesen Datensatz.

Was geschieht im Hintergrund genau? Um das herauszufinden, haben wir den beiden Ereignisprozeduren jeweils eine Debug.Print-Anweisung hinzugefügt, welche einen Hinweis auf die jeweilige Prozedur im Haupt- oder Unterformular im Direktfenster ausgibt:

'Hauptformular

Private Sub Form_Current()

Debug.Print "Hauptformular_Current"

Me!sfmHersteller.Form.Recordset.FindFirst "HerstellerID = " & Nz(Me!HerstellerID, 0)

End Sub

'Unterformular

Private Sub Form_Current()

Debug.Print "Unterformular_Current"

Me.Parent.Recordset.FindFirst "HerstellerID = " & Nz(Me!HerstellerID, 0)

End Sub

Wenn wir das Formular nun öffnen, werden die Ereignisse in dieser Reihenfolge ausgelöst:

Unterformular_Current

Hauptformular_Current

Das Verschieben des Datensatzzeigers über das Ereignis im Unterformular sorgt also dafür, dass auch das Ereignis Beim Anzeigen im Hauptformular ausgelöst wird. Obwohl dieses wiederum den Datensatzzeiger im Unterformlar verschiebt, löst dies nicht nochmal das Ereignis Beim Anzeigen im Unterformular aus. Offensichtlich hat Access einen Automatismus eingebaut, der Zirkelbezüge zwischen Prozeduren schnell erkennt und nach einmaliger Ausführung abbricht. Dabei wird die Prozedur im Hauptformular genau nach dem Aufruf von FindFirst im Unterformular ausgelöst. Nach dem Abarbeiten der Prozedur im Hauptformular wird dann noch die letzte Zeile der Prozedur im Unterformular erledigt (siehe Bild 4).

Ablauf der beiden Prozeduren beim Öffnen des Formulars

Bild 4: Ablauf der beiden Prozeduren beim Öffnen des Formulars

Wenn Sie mit der Tabulator-Taste von einem Datensatz zum nächsten wechseln, löst dies jeweils die gleichen Ereignisse aus. Wenn wir auf diese Weise allerdings auf dem neuen, leeren Datensatz landen, werden folgende Ereignisse ausgelöst:

Unterformular_Current

Hauptformular_Current

Unterformular_Current

Dabei löst FindFirst im Unterformular die Prozedur im Hauptformular aus und FindFirst im Hauptformular nochmals die entsprechende Prozedur im Unterformular. Erst danach bricht die Abarbeitung der Ereignisprozeduren ab.

Wenn der Datensatzzeiger hingegen nach dem erstmaligen Auswählen des neuen, leeren Datensatzes direkt auf den ersten verschoben wird und wir dann nochmal mit der Maus auf den neuen, leeren Datensatz klicken, wird nur noch die folgende Ereignisprozedur ausgelöst:

Unterformular_Current

Es wäre schön, wenn wir dieses Verhalten noch harmonisieren könnten und dem Benutzer ein reproduzierbares Verhalten liefern könnten.

Alle Ereignisse im Hauptformular

Der Einfachheit halber verschieben wir dazu das Ereignis Form_Current des Unterformulars in das Hauptformular. Mehr Informationen, wie das gelingt, finden Sie im Artikel Alle Ereignisse im Hauptformular. Das Ergebnis für unser Beispiel sieht dann wie in Listing 1 aus. Wir benötigen eine Variable, mit der wir das Unterformular referenzieren können und die Ereignisprozedur Form_Load, um dieses beim Öffnen des Hauptformulars zu referenzieren. Die übrigen beiden Ereignisprozeduren haben wir nur so angepasst, dass Sie sich auf die richtige Formularinstanz beziehungsweise das richtige Steuer­element beziehen. Wenn wir das Formular nun öffnen, wird nur noch Form_Current des Hauptformulars aufgerufen:

Dim WithEvents sfm As Form

Private Sub Form_Current()

     Debug.Print "Hauptformular_Current"

     Me!sfmHersteller.Form.Recordset.FindFirst "HerstellerID = " & Nz(Me!HerstellerID, 0)

End Sub

Private Sub Form_Load()

     Set sfm = Me!sfmHersteller.Form

     sfm.OnCurrent = "[Event Procedure]"

End Sub

Private Sub sfm_Current()

     Debug.Print "Unterformular_Current"

     Me.Recordset.FindFirst "HerstellerID = " & Nz(sfm!HerstellerID, 0)

End Sub

Listing 1: Alle Ereignisprozeduren im Klassenmodul des Hauptformulars

Hauptformular_Current

Das ist logisch, denn wenn die Form_Load-Ereignisprozedur im Hauptformular ausgelöst und somit das Unterformular der Variablen sfm zugeordnet wird, wäre die Form_Current-Prozedur des Unterformulars normalerweise schon ausgelöst worden.

Die übrigen beiden Vorgänge, also das Wechseln von einem zum anderen Datensatz im Unterformular oder das Wechseln zu einem leeren, neuen Datensatz lösen die gleichen Prozeduren aus.

Wir wollen nun dafür sorgen, dass jeweils nur noch die Beim Anzeigen-Ereignisprozedur des Formulars ausgelöst wird, in dem sich auch der Datensatzwechsel ereignet hat.

Dazu ändern wir den Code wie in Listing 2. Hier haben wir zwei Variablen eingeführt, nämlich intCurrent und bolOpen. bolOpen wird auf True gesetzt, wenn die Form_Load-Prozedur aufgerufen wird. Die Variable intCurrent wird immer beim ersten Ausführen von Form_Current/sfmCurrent auf 1 eingestellt, beim dadurch ausgelösten Aufruf der jeweils anderen Prozedur wird dieser Wert geprüft und die enthaltenen Anweisungen bei intCurrent = 1 nicht mehr ausgeführt. Dafür wird intCurrent aber wieder auf 0 eingestellt, damit beim nächsten Datensatzwechsel wieder die jeweils zuerst ausgelöste Prozedur Form_Current/sfm_Current den Datensatzzeiger des jeweils anderen Formulars aktualisieren kann.

Dim WithEvents sfm As Form

Dim intCurrent As Integer

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!