Zur Hauptseite ... Zum Onlinearchiv ... Zum Abonnement ... Zum Newsletter ... Zu den Tools ... Zum Impressum ... Zum Login ...

Gedrucktes Heft

Diesen Beitrag finden Sie in Ausgabe 5/2014.

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'!

Diesen Beitrag twittern

1:n-Beziehungen in Unterformularen

Meistens zeigt man 1:n-Beziehungen in einem Haupt- und einem Unterformular an. Was aber, wenn man die Datensätze beider Seiten im Überblick anzeigen möchte – etwa wenn Sie alle Kategorien in einer Liste und die Artikel zur ausgewählten Kategorie in einer anderen Liste darzustellen? Oder, um es noch eine Nummer komplizierter zu machen, im Hauptformular die Kunden, im ersten Unterformular ihre Bestellungen und im zweiten Unterformular die Bestellpositionen der im ersten Unterformular ausgewählten Bestellung? Dann ist meist ein kleiner Trick erforderlich, um dies zu bewerkstelligen – wie der vorliegende Beitrag zeigt.

Herkömmliche Darstellung

Normalerweise verwendet man zur Darstellung von 1:n-Beziehungen ein Haupt- und ein Unterformular. Das Hauptformular zeigt dabei die Datensätze der Mastertabelle an, also beispielsweise die Daten der Tabelle tblKategorien. Die Anzeige ist dabei allerdings auf Formularansicht eingestellt, was bewirkt, dass nur die Daten jeweils eines Datensatzes im Formular erscheinen.

Das Unterformular zeigt die Daten der Detailtabelle an, also der Tabelle, die über ein Fremdschlüsselfeld mit den Datensätzen der Mastertabelle verknüpft ist. In unserem Beispiel handelt es sich dabei um die Tabelle tblArtikel.

Das Formular aus Bild 1 ist genau wie beschrieben aufgebaut – es zeigt die Daten der Tabelle tblKategorien im Hauptformular und die der Tabelle tblArtikel im Unterformular an. Damit das Unterformular nur die Daten der Tabelle tblArtikel anzeigt, die über das Fremdschlüsselfeld KategorieID mit dem aktuellen Datensatz der Tabelle tblKategorien im Hauptformular verknüpft sind, trägt man für die Eigenschaft Verknüpfen von des Unterformular-Steuerelements den Namen des Fremdschlüsselfeldes der Tabelle im Unterformular ein (hier KategorieID) und für die Eigenschaft Verknüpfen nach den Namen des Primärschlüsselfeldes der im Hauptformular abgebildeten Tabelle tblKategorien. Das Ergebnis finden Sie in Bild 2 vor: Sie können durch die Datensätze im Hauptformular navigieren und das Unterformular zeigt jeweils nur die passenden Datensätze an.

1:n-Beziehung in Haupt- und Unterformular mit Verknüpfungseigenschaften

Bild 1: 1:n-Beziehung in Haupt- und Unterformular mit Verknüpfungseigenschaften

1:n-Beziehung in Haupt- und Unterformular

Bild 2: 1:n-Beziehung in Haupt- und Unterformular

Zwei Unterformulare

Nun gehen wir eine Stufe weiter: Wir wollen die Kategorien nicht mehr im Hauptformular anzeigen, sondern in einem weiteren Unterformular. So hat der Benutzer nicht nur die mit einer Kategorie verknüpften Artikel im Überblick, sondern kann auch gleich die Liste der Kategorien einsehen und die gewünschte Kategorie auswählen.

Um dies zu erreichen, legen Sie zunächst ein neues Hauptformular namens frmKategorienArtikelUFUF an (UFUF für Unterformular/Unterformular). Dann erstellen Sie ein Formular namens sfmKategorienUFUF, das die Tabelle tblKategorien als Datenherkunft verwendet und seine Daten in der Datenblattansicht anzeigt. Auf ähnliche Weise erstellen Sie das Formular sfmArtikelUFUF, das Sie mit der Tabelle tblArtikel füllen – ebenfalls in der Datenblattansicht. Ziehen Sie beide Unterformulare in das Hauptformular. Da das Hauptformular seinerseits keine Daten anzeigen soll, entfernen Sie der Übersichtlichkeit halber Elemente wie die Navigationsschaltflächen, Bildlaufleisten und Datensatzmarkierer, indem Sie die entsprechenden Eigenschaften jeweils auf den Wert Nein einstellen. Beide Formulare ziehen Sie nun nebeneinander in das Hauptformular frmKategorienArtikelUFUF, sodass dieses wie in Bild 3 aussieht.

Zwei Unterformulare, die miteinander verknüpft werden sollen

Bild 3: Zwei Unterformulare, die miteinander verknüpft werden sollen

Ein Wechsel in die Formularansicht zeigt, dass die Daten schon einmal wie gewünscht erscheinen (s. Bild 4). Allerdings filtert das rechte Unterformular seine Daten noch nicht nach dem aktuell im linken Unterformular angezeigten Wert.

Aktuell zeigen beide Formulare alle Daten an, aber die Artikel werden nicht nach der aktuellen Kategorie gefiltert.

Bild 4: Aktuell zeigen beide Formulare alle Daten an, aber die Artikel werden nicht nach der aktuellen Kategorie gefiltert.

Dummerweise erkennt Access auch nicht automatisch, was wir hier vorhaben, daher füllt es die beiden Eigenschaften Verknüpfen von und Verknüpfen nach nicht automatisch aus wie im vorherigen Beispiel. Kommen wir hier überhaupt mit den beiden Eigenschaften aus? Ein Versuch, für die Eigenschaft Verknüpfen Nach einen direkten Bezug zum anderen Unterformular unterzubringen wie in Bild 5, scheitert allerdings – Access fragt den Ausdruck Parent!sfmKategorienUFUF!KategorieID in einer Inputbox ab und das Unterformular sfmArtikelUFUF bleibt leer.

Diese Einstellung funktioniert nicht.

Bild 5: Diese Einstellung funktioniert nicht.

Allerdings sollte man immer mehrere Varianten ausprobieren und vor allem eventuelle Tipp- oder Syntaxfehler ausschließen. In diesem Fall war es kein Syntaxfehler, Access kann den zunächst verwendeten Ausdruck schlicht nicht verarbeiten. Mit dem folgenden Ausdruck für die Eigenschaft Verknüpfen nach haben wir allerdings mehr Erfolg:

Forms!frmKategorienArtikelUFUF! sfmKategorienUFUF!KategorieID

Das Unterformular sfmArtikelUFUF zeigt nach dem Wechsel in die Entwurfsansicht die korrekten Daten an, also nur diejenigen Artikel, die zur aktuellen Kategorie gehören. Dummerweise ist die Begeisterung nach dem Wechsel zu einer anderen Kategorie schon wieder erledigt: Die zuvor angezeigten Artikel werden nicht gegen die der nun eingestellten Kategorie ausgetauscht.

Erst wenn man links eine neue Kategorie auswählt, dann in das rechte Unterformular klickt und dann mit der Taste F5 die Datenherkunft aktualisiert, zeigt dieses Unterformular die gewünschten Artikel-Datensätze an. Das ist allerdings ein wenig umständlich.

Also kommen wir nicht umhin, mit einer kleinen VBA-Prozedur nachzuhelfen. Da das rechte Unterformular immer dann aktualisiert werden soll, wenn der Benutzer im linken Unterformular einen anderen Datensatz ausgewählt hat, benötigen wir eine Ereignisprozedur, die durch das Ereignis Beim Anzeigen des linken Unterformulars ausgelöst wird.

Das wäre also vom linken Unterformular aus ein Verweis auf das Parent-Objekt und von dort aus auf das Unterformular-Steuerelement sfmArtikelUFUF und dann auf das darin enthaltene Form-Objekt. Für dieses soll dann die Requery-Methode ausgeführt werden, was wie folgt aussehen könnte:

Private Sub Form_Current()
     Me.Parent!sfmArtikelUFUF.Form.Requery
End Sub

Access macht uns aber beim Öffnen des Formulars mit der Fehlermeldung aus Bild 6 einen Strich durch die Rechnung.

Fehlermeldung beim Öffnen des Formulars

Bild 6: Fehlermeldung beim Öffnen des Formulars

In einem solchen Fall klickt man am besten auf die Debuggen-Schaltfläche und untersucht die aktuelle Situation durch entsprechende Prüfungen im Direktfenster des VBA-Fensters. Möglicherweise haben wir ja irgendwo einen Syntaxfehler eingebaut?

Also hangeln wir uns durch die referenzierten Elemente, wobei wir uns von links nach rechts vorarbeiten und am besten immer eine gängige Eigenschaft des jeweils referenzierten Elements abfragen. Wir starten mit dieser Abfrage:

  Me.Parent.Name
frmKategorienArtikelUFUF

Dies klappt also. Gehen wir einen Schritt weiter:

  Me.Parent.sfmArtikelUFUF.Name
sfmArtikelUFUF

Und noch einen Schritt:

  Me.Parent.sfmArtikelUFUF.Form.Name

Hier erscheint dann die bereits bekannte Fehlermeldung. Das im Unterformularsteuerelement enthaltene Formular ist also anscheinend nicht verfügbar. Das kann natürlich sein, denn die Unterformulare werden ja in einer bestimmten Reihenfolge geladen – möglicherweise ist das Unterformular sfmArtikelUFUF ja noch gar nicht existent, wenn die obige Ereignisprozedur versucht, darauf zuzugreifen.

Wenn Sie an dieser Stelle auf die Schaltfläche Beenden der Fehlermeldung klicken, erscheint das Formular mit beiden Unterformularen, wobei die Artikel entsprechend dem aktuell markierten ersten Eintrag des Kategorien-Unterformulars gefiltert werden – aber dies war ja auch schon ohne den Einsatz der Ereignisprozedur der Fall.

Wenn Sie nun allerdings eine andere Kategorie wählen, zeigt das rechte Unterformular plötzlich die zugeordneten Artikel an – die Ereignisprozedur scheint also doch korrekt zu funktionieren, nur eben nicht beim Öffnen des Formulars.

Wir brauchen uns also nur darum zu kümmern, dass das Unterformular sfmArtikelUFUF nur aktualisiert wird, wenn es bereits vorhanden ist. Da dies beim Öffnen des Hauptformulars automatisch geschieht, können wir die Prüfung aus Listing 1 in die Ereignisprozedur einbauen.

Private Sub Form_Current()
     Dim strForm As String
     On Error Resume Next
     strForm = Me.Parent!sfmArtikelUFUF.Form.Name
     On Error GoTo 0
     If Len(strForm) > 0 Then
         Me.Parent!sfmArtikelUFUF.Form.Requery
     End If
End Sub

Listing 1: Aktualisieren des Unterformulars sfmArtikelUFUF

Diese versucht, nach vorheriger Deaktivierung der Fehlerbehandlung den Namen des Unterformulars einzulesen. Ist das Formular bereits vorhanden, ist strForm danach mit dem Namen des Formulars gefüllt. Dies prüft eine If...Then-Bedingung und aktualisiert in diesem Fall die Datenherkunft des Unterformulars sfmArtikelUFUF.

Elegantere Lösung

Nun ist dies nicht die schönste Lösung. Das eine Unterformular weiß nicht, wann das andere Unterformular geladen wird – das ist nicht optimal. Wir wollen zunächst einmal betrachten, in welcher Reihenfolge die Unterformulare eigentlich geladen werden – und wie diese Zeitpunkte sich zum Laden des Hauptformulars verhalten. Also fügen Sie sowohl für das Hauptformular als auch für die beiden Unterformulare jeweils eine Ereignisprozedur für das Ereignis Beim Laden hinzu.

Wir benötigen keine weiteren Anweisungen, lediglich je einen Haltepunkt in der ersten Zeile der jeweiligen Prozedur. Wenn Sie das Formular nun öffnen, zeigt der VBA-Editor nacheinander die Haltepunkte in der entsprechenden Reihenfolge an. Wir stellen fest: Das Unterformular sfmKategorienUFUF lädt als erstes, dann folgt sfmArtikelUFUF und schließlich das Hauptformular.

Ein paar weitere Experimente, in denen wir die Unterformulare nacheinander ausschneiden und neu anlegen, zeigen, dass das Hauptformular immer zuletzt geladen wird.

Bei den Unterformularen richtet sich die Reihenfolge offensichtlich danach, in welcher Reihenfolge die Unterformulare zum Hauptformular hinzugefügt wurden.

Mit dieser Erkenntnis gehen wir das Ganze von ganz oben an, nämlich vom Hauptformular aus. Der designtechnische Hintergrund ist, dass das Hauptformular nämlich weiß, dass es die beiden Unterformulare enthält, umgekehrt aber nicht.

Wie aber wollen wir im Hauptformular erfahren, wann der Benutzer einen neuen Datensatz im Unterformular sfmKategorienUFUF ausgewählt hat? Ganz einfach: Indem wir im Klassenmodul des Hauptformulars ein Objekt deklarieren, mit dem wir dann das Unterformular referenzieren und dessen Ereignisse abfangen. Dazu deklarieren wir es mit dem Schlüsselwort WithEvents im Kopf des Klassenmoduls des Formulars frmKategorienArtikelUFUF als Form-Objekt.

Über die beiden Kombinationsfelder im Kopf des Code-Fensters können wir nun links das Objekt objSfmKategorien­UFUF auswählen und rechts das Ereignis OnCurrent. Dies legt im Klassenmodul die Ereignisprozedur objSfmKategorienUFUF_Current an, die durch das Wechseln des Datensatzes im Unterformular sfmKategorienUFUF ausgelöst wird.

Nun müssen wir allerdings noch die Objektvariable mit einem Verweis auf das betroffene Unterformular füllen. Dies erledigen wir in der Ereignisprozedur, die durch das Ereignis Beim Laden des Hauptformulars ausgelöst wird. Dort stellen wir auch die Eigenschaft OnCurrent des Objekts objSfmKategorienUFUF auf den Wert [Event Procedure] ein. Dies entspricht dem Einstellen der Eigenschaft Beim Anzeigen auf den Wert [Ereigniseigenschaft].

Wenn Sie nun noch die Anweisung zum Aktualisieren des Unterformulars sfmArtikelUFUF zur Prozedur objSfmKategorienUFUF_Current hinzufügen, sind Sie fertig – der Code des Hauptformulars sieht nun wie in Listing 2 aus und aktualisiert den Inhalt des rechten Unterformulars, wenn der Benutzer im linken Unterformular einen neuen Datensatz auswählt.

Dim WithEvents objSfmKategorienUFUF As Form
Private Sub Form_Load()
     Set objSfmKategorienUFUF = Me!sfmKategorienUFUF.Form
     With objSfmKategorienUFUF
         .OnCurrent = "[Event Procedure]"
     End With
End Sub
Private Sub objSfmKategorienUFUF_Current()
     Me!sfmArtikelUFUF.Form.Requery
End Sub

Listing 2: Aktualisieren des Unterformulars sfmArtikelUFUF, alternativer Weg

Wichtiger Hinweis: Damit das Ereignis Beim Anzeigen des Unterformulars sfm­KategorienUFUF auch ausgelöst wird, müssen Sie dem Unterformular noch ein Klassenmodul hinzufügen. Das erledigen Sie, soweit noch nicht geschehen, wenn Sie die Eigenschaft Enthält Modul auf den Wert Ja einstellen (s. Bild 7).

Hinzufügen eines Klassenmoduls zu einem Formular

Bild 7: Hinzufügen eines Klassenmoduls zu einem Formular

Einfachere Variante

Wer nicht so gern mit VBA programmiert, kann auch die folgende Variante verwenden. Dabei fügen wir dem Hauptformular ein Textfeld namens txtKategorieID hinzu.

Dessen Steuerelementinhalt stellen wir auf das Feld KategorieID des ersten Unterformulars ein, also auf den folgenden Ausdruck:

=sfmKategorienUFUF!KategorieID

Damit zeigt dieses Textfeld, das im echten Betrieb natürlich ausgeblendet sein soll, immer die KategorieID des im Unterformular sfmKategorienUFUF ausgewählten Datensatzes an.

Und wie bringen wir nun das zweite Unterformular dazu, nur die Artikel anzuzeigen, die zu dieser Kategorie gehören? Ganz einfach: Wir stellen einfach die Eigenschaft des entsprechenden Unterformular-Steuerelements auf den Namen dieses Textfeldes ein, also txtKategorieID (s. Bild 8).

Verknüpfung über Umwege

Bild 8: Verknüpfung über Umwege

Dann gelingt die Synchronisierung völlig automatisch und ohne Code, hat aber einen entscheidenden Nachteil: Es dauert manchmal ein paar Sekundenbruchteile, bis die Aktualisierung erfolgt.

Wenn man schnell durch die Datensätze navigiert, kann es daher schon mal haken. Dies liegt daran, dass das Unterformular nicht ständig den Wert des Textfeldes im Hauptformular abfragt. Aus Perfomance-Sicht sollte man also die erste Variante verwenden.

1:n und 1:n

Es gibt noch eine weitere denkbare interessante Konstellation. Dabei zeigt das Hauptformular die Daten einer Tabelle an, das erste Unterformular alle Daten, die mit dem aktuellen Datensatz des Hauptformulars per 1:n-Beziehung verknüpft sind, und das zweite Unterformular zeigt alle Datensätze an, die mit dem aktuell markierten Datensatz im ersten Unterformular verknüpft sind.

Im Beispiel sollen dies die Tabellen tblKunden (im Hauptformular), tblBestellungen (im ersten Unterformular) und tblBestelldetails (im zweiten Unterformular) sein. Dem Hauptformular, das frmKundenBestellungenArtikel heißt, fügen wir einige repräsentante Felder der Tabelle tblKunden hinzu, die Ansicht des Hauptformulars ist die Formularansicht.

Das erste Unterformular heißt sfmBestellungen und soll die Felder Bestelldatum, Lieferdatum, Versanddatum und VersandUeber enthalten. Diese Felder werden in der Datenblattansicht angezeigt. Das zweite Unterformular heißt sfmBestelldetails und liefert die Felder ArtikelID, Einzelpreis, Anzahl und Rabatt der Tabelle tblBestelldetails. Auch dieses Formular zeigt seine Daten in der Datenblattansicht an (s. Bild 9).

Hauptformular mit zwei verknüpften Unterformularen

Bild 9: Hauptformular mit zwei verknüpften Unterformularen

Wenn wir nun in die Formularansicht wechseln, funktionieren zumindest schon einmal das Hauptformular und das erste Unterformular wie gewohnt: Wenn Sie im Hauptformular einen Kunden auswählen, zeigt das Unterformular sfmBestellungen die dazu gehörenden Datensätze an. Dies geschieht, weil Access beim Hinzufügen des Unterformulars automatisch erkannt hat, dass seine Datenherkunft ein Fremdschlüsselfeld enthält, das mit dem Primärschlüsselfeld der Datenherkunft im Hauptformular verknüpft ist und die beiden Eigenschaften Verknüpfen von und Verknüpfen nach des Unterformular-Steuerelements entsprechend gefüllt hat (s. Bild 10).

Hauptformular mit zwei synchronen Unterformular

Bild 10: Hauptformular mit zwei synchronen Unterformular

Beim Unterformular-Steuerelement sfmBestelldetails sind diese Eigenschaften hingegen leer geblieben.

Das ist klar – es gibt keine verwertbare Beziehung zwischen den Datenherkünften in Haupt- und Unterformular. Also probieren wir es zunächst wieder in der Variante des vorherigen Beispiels ohne Code. Wir fügen dem Hauptformular ein Textfeld namens txtBestellungID hinzu, das den folgenden Ausdruck als Steuerelementinhalt verwendet:

=sfmBestellungen!BestellungID

Für die Eigenschaft Verknüpfen von des Unterformulars sfmBestelldetails tragen wir BestellungID ein, für die Eigenschaft Verknüpfen nach das Textfeld txtBestellungID.

Nach dem Wechsel in die Formularansicht wird deutlich, dass alles so funktioniert wie gewünscht – beim Navigieren durch das Unterformular sfmBestellungen ändert sich ständig der Inhalt des Unterformulars sfmBestelldetails, und wenn der Benutzer einen neuen Kunden auswählt, passen sich gleich beide Unterformulare an.

Interessant ist an dieser Stelle die Betrachtung der Performance: Wenn Sie durch die Kundendatensätze im Hauptformular navigieren, werden beide Unterformulare blitzschnell angepasst.

Wenn Sie hingegen durch die Datensätze des Unterformulars sfmBestellungen blättern, erhalten Sie wieder eine Verzögerung von ein paar Sekundenbruchteilen. Wenn Sie das nicht wünschen, müssen Sie auch hier wieder mit VBA arbeiten.

Dazu legen wir im Klassenmodul des Hauptformulars frmKundenBestellungenArtikel prinzipiell wieder die gleichen Elemente an wie im vorherigen Beispiel – also eine mit WithEvents deklarierte Objektvariable für das Unterformular sfmBestellungen sowie zwei Ereignisprozeduren, von denen die erste beim Laden des Hauptformulars ausgelöst wird und die Konfiguration des zweiten Ereignisses vornimmt. Dieses wird dann beim Anzeigen eines jeden Datensatzes im Unterformular sfmBestellungen ausgelöst und aktualisiert die Datenherkunft des zweiten Unterformulars sfmBestelldetails (s. Listing 3).

Dim WithEvents objSfmBestellungen As Form
Private Sub Form_Load()
     Set objSfmBestellungen = Me!sfmBestellungen.Form
     With objSfmBestellungen
         .OnCurrent = "[Event Procedure]"
     End With
End Sub
Private Sub objSfmBestellungen_Current()
     Me!sfmBestelldetails.Form.Requery
End Sub

Listing 3: Aktualisieren des Unterformulars sfmBestelldetails per VBA

Übrigens wirkt es sich offensichtlich nicht sonderlich auf die Performance aus, ob Sie nun ein Textfeld im Hauptformular unterbringen, das sich den Inhalt des Primärschlüsselfeldes des ersten Unterformulars holt, und dann über die Eigenschaft Verknüpfen nach des zweiten Unterformular-Steuerelements darauf zugreifen oder ob Sie gleich das entsprechende Feld im ersten Unterformular mit der Eigenschaft Verknüpfen nach des zweiten Unterformulars referenzieren. Letzteres würde in diesem Fall wie folgt lauten:

Forms!frmKundenBestellungenArtikel_VBA_II!
sfmBestellungen!BestellungID

Zusammenfassung und Ausblick

Mit diesem Beitrag haben Sie einige Möglichkeiten kennengelernt, um vom Hauptformular abhängige Unterformulare und voneinander abhängige Unterformulare zu synchronisieren. Es ist offensichtlich, dass die Lösungen, die rein auf der Verwendung von Eigenschaften wie Verknüpfen von und Verknüpfen nach basieren, teilweise eine schlechte Performance bieten.

Daher empfiehlt es sich, per VBA zu den entsprechenden Gelegenheiten nachzuhelfen – beispielsweise durch Aktualisieren des zu filternden Unterformulars.

Hier gibt es wieder mindestens zwei Möglichkeiten: Entweder Sie fügen die entsprechenden Ereignisprozeduren den jeweiligen Unterformularen hinzu, oder Sie steuern die Unterformulare fern, indem Sie diese vom Hauptformular aus referenzieren und deren Ereignisse dort abfangen und passende Ereignisprozeduren implementieren.

Dies ist die sauberere Lösung, denn so brauchen Sie den Unterformularen keinen Code hinzuzufügen, den sie – wenn sie noch anderswo als Unterformular eingesetzt werden – gar nicht gebrauchen können.

Kompletten Artikel lesen?

Einfach für den Newsletter anmelden, dann lesen Sie schon in einer Minute den kompletten Artikel und erhalten die Beispieldatenbanken.

E-Mail:

Download

Download

Die .zip-Datei enthält folgende Dateien:

1zuNInUnterformularen.mdb

Beispieldateien downloaden

© 2003-2015 André Minhorst Alle Rechte vorbehalten.