Lösungen

Hauptsache Hauptformular

15.11.2006
Relationale Datenbanken bestehen in weiten Teilen aus 1:n-Beziehungen zwischen Tabellen. Da liegt es auf der Hand, diese Beziehungen für den Benutzer auch transparent und einfach bedienbar abzubilden.

Als Beispiel sollen hier Teile der Nordwind-Datenbank dienen, die allerdings etwas modifiziert wurden. Wenn Sie sich die Datenstruktur dort nämlich einmal ansehen, werden Sie ein paar Vereinfachungen finden, was den Kunden betrifft.

Da sowohl der Name der Kontaktperson als auch die Adresse direkt in der Kunden-Tabelle enthalten sind, kann dort jeweils nur ein Wert gespeichert sein. Mehrere Kontaktpersonen oder mehrere Adressen (Rechnungs-, Lieferungs-, Postadresse) sind per se unmöglich. Das entspricht nicht den üblichen Anforderungen im wirklichen Leben.

Zudem soll hier noch die Möglichkeit aufgenommen werden, Kundenkontakte und -Besprechungen inhaltlich festzuhalten. Auch dabei sind natürlich mehrere Datensätze je Kunde Voraussetzung.

Master- und Detail-Tabellen

Bild 1 zeigt die neue Struktur dieser Datenbank mit ihren Tabellen und Feldern. Wesentlicher Punkt dabei ist, dass alle Tabellen mit den Kunden über das Feld Kunden-Code verknüpft sind und die Tabelle Kunden für alle anderen Detail-Tabellen die Master-Tabelle ist.

Bild 1: Beziehungen in der Beispieldatenbank.

Schon in der Ansicht der Kunden-Tabelle selber unterstützt Access ab Version 2000 solche 1:n-Beziehungen mit einer Untertabelle. Diese wird wie in Bild 2 angezeigt, wenn Sie auf das Pluszeichen vor jedem Datensatz klicken. Sie können die dann angezeigte Untertabelle jederzeit im Tabellenentwurf in der Eigenschaft Unterdatenblattname ändern.

Bild 2: Unterdatenblätter einer Tabelle.

Das ist mit minimalem Aufwand eine gar nicht so schlechte Lösung für Entwickler. Nur für End-anwender ist das so nicht besonders übersichtlich.

Bild 3: AutoFormular, basierend auf der Tabelle Kunden.

Wenn Sie auf dieser Tabelle Kunden basierend ein AutoFormular erstellen lassen, wird die gleiche Struktur auch im Formular abgebildet. Das mag ein wenig hübscher sein, wie in Bild 3 zu sehen ist, verschärft die Probleme aber nur noch. Die meisten Benutzer sind nicht nur von zwei übereinander liegenden Navigationsleisten völlig überfordert. Vor allem bei der Suche nach einem bestimmten Kunden wenden sie die Techniken an, die sie aus anderen Suchformularen oder dem Internet kennen: Sie tippen in das gewünschte Feld den zu suchenden Namen ein und bestätigen mit [Return]. Dann wundern sie
sich, dass nichts passiert ist, und verlassen das Formular. Spätestens jetzt aber ist der Stammdatensatz des Kunden mit diesem geänderten Inhalt gespeichert worden.

Natürlich können Sie die Formularfelder gegen solche Missgeschicke einfach mit Schreibschutz versehen. Aber leider verlieren damit Ihre Access-Benutzer auch die richtigen Suchmöglichkeiten, wie sie etwa im Menü Datensätze Filter angeboten werden.

Stammdaten schützen

Das Problem besteht also sowohl in der leichten Veränderbarkeit der im Formular angezeigten Stammdaten als auch in der wenig intuitiven Suche. Daher muss die Lösung grundsätzlich im Typ des Hauptformulars ansetzen.

Am besten kennt es gar keine Daten, dann kann es diese auch nicht beschädigen. Erstellen Sie also ein leeres Formular im Entwurf und ohne Datenquelle.

Damit auch optisch nicht der leiseste Verdacht einer Datenbindung aufkommt, stellen Sie ein paar Eigenschaften um:

Anschließend positionieren Sie darauf eine Listbox lstKunden mit den Eigenschaften Spaltenanzahl: 4, Spaltenbreiten: 1cm;6cm;2cm und der Datensatzherkunft: qryKunden. Diese Abfrage qryKunden finden Sie in Listing 1.

SELECT [Kunden-Code], Firma, Ort, Land
FROM Kunden
ORDER BY Firma, Ort

Obwohl das Formular selber gar keine Daten „kennt“, können seine Controls (hier vor allemCombobox und Listbox) selbstverständlich eigene Datenquellen anzeigen. Technisch gesehen befinden sich deren Daten aber in einem kleinen Fenster oberhalb des Formulars. Das Formular kann also in unserem Fall keinen Kunden "sehen".

Erst wenn Sie eine Zeile der Listbox angeklickt haben, erhält diese den Wert der gebundenen Spalte, welche üblicherweise auf 1 steht. Hier wird also der markierte Kunde Blondel père et fils die Wert-Eigenschaft von lstKunden auf BLONP stellen. Und das reicht vollkommen aus, diesen Kunden eindeutig zu identifizieren.

Das so erstellte Formular wird nun unter dem Namen frmKunden (Haupt) gespeichert.

Unterformular ergänzen

Damit haben Sie beide Probleme auf einmal gelöst: Die Suche ist selbsterklärend, indem ein Name aus der Liste markiert wird, und ein schreibender Zugriff auf die Daten ist völlig unmöglich.

Entsprechend dem ersten Beispiel sollen nun zu diesem Kunden genau seine Bestellungen angezeigt werden. Wegen der besseren Gestaltungsmöglichkeiten geschieht dies nicht mit einer Untertabelle, sondern einem Unterformular.

Auf Grundlage der Tabelle Bestellungen können Sie den Formular-Assistenten ein ganz einfaches tabellarisches Endlosformular erstellen lassen. Es erhält den Namen frmKunden (Unter: Bestellungen), nur damit es alphabetisch nach dem zugehörigen Hauptformular angezeigt wird.

Tatsächlich ist das Unterformular nun fertig, alle übrigen Änderungen finden nur noch im Hauptformular statt. Wenn Sie das Hauptformular im Entwurf öffnen, können Sie mit [F11] das Datenbankfenster anzeigen lassen und den Namen des Unterformulars in den Entwurf dieses Hauptformulars hineinziehen.

Synchronisation

Abgesehen von ein paar optischen Verschönerungen, sieht das Unterformular entsprechend Bild 4 schon ganz gut aus. Aber wie Sie beim Anklicken verschiedener Kunden feststellen werden, zeigt es immer alle Datensätze an und nicht nur diejenigen des markierten Kunden. Die beiden sind also offensichtlich noch nicht synchronisiert.

Bild 4: Listbox und Unterformular noch nicht synchronisiert.

Wenn Sie zum Vergleich einmal in das ursprüngliche AutoFormular sehen, werden Sie für die Untertabelle mit den Bestellungen zwei Eigenschaften Verknüpft von und Verknüpft nach finden. Darin ist jeweils der Feldname Kunden-Code enthalten.

Access sorgt dann automatisch dafür, dass in der Untertabelle Bestellungen nur die Datensätze angezeigt werden, deren Kunden-Code zum Kunden-Code des Formulars passt.

Eigentlich ist das hier genauso. Nur die Vermutung, dass dabei zwei Felder miteinander synchronisiert würden, ist falsch. Das trifft sich gut, denn unser Hauptformular hätte ja mangels Datenbindung auch gar keine Felder!

Vielmehr werden die Inhalte der Controls miteinander verglichen. Normalerweise tragen bei Datenfeldern die Textboxen und anderen Controls den gleichen Namen wie ihre Felder, daher fällt der feine Unterschied gar nicht auf.

Hier wählen Sie für Verknüpfen von ein übliches Feld/Control im Unterformular, nämlich Kunden-Code. Bei Verknüpfen nach wird in Controlname des Hauptformulars angegeben, dessen Inhalt ein Kunden-Code ist, also lstKunden. Damit funktioniert die Synchronisation.

Mehrere Unterformulare

Die Datenstruktur dieser Datenbank hat an Kunden als Master-Tabelle insgesamt vier Detailtabellen als 1:n-Verknüpfung gebunden. Zu jedem markierten Kunden hätten Sie also vier verschiedene Unterformulare anzuzeigen.

Das ist weniger technisch als vor allem platzmäßig ein Problem. Entfernen Sie daher auf dem Hauptformular das Unterformular, und fügen Sie eine Multipage (Registersteuerelement) ein. Darin markieren Sie den ersten Registerreiter so, dass die Markierungsrechtecke im Innern des grauen Bereichs sichtbar sind. Das Eigenschaften-Fenster zeigt nun Seite: Seite2 an.

Wenn Sie nun wiederum den Namen des Unterformulars aus dem Datenbankfenster in diesen markierten Bereich ziehen, muss sich dieser schwarz färben. Wenn das nicht der Fall ist, fügen Sie solche Objekte vor oder hinter die Multipage ein, aber nicht in seine Seiten.

Bild 5: Ziehen des Unterformular-Namens in die markierte Multipage-Seite.

Zu Testzwecken wechseln Sie einfach per Klick auf die zweite Seite. Dann wird auch im Entwurf schon die andere, noch leere Seite sichtbar. Damit ist alles in Ordnung.

Die bisherige Seite2 sollte noch Name: pagBestellungen und Beschriftung: Bestellungen als Eigenschaften erhalten, damit es besser aussieht. Außerdem müssen Sie für das ja erneut eingefügte Unterformular die gleichen Verknüpfen von/nach-Eigenschaften wie vorher eingeben.

Bild 6: Listbox und Multipage mit synchronisierten Unterformularen.

Durch die Multipage haben Sie beliebig viel Platz gewonnen und können jetzt für jedes (noch zu erstellende) Unterformular eine eigene Seite anlegen, wie in Bild 6 zu sehen ist.

Stammdaten doch wieder bearbeiten

Bei dieser Gelegenheit können Sie übrigens auch einen kleinen Schönheitsfehler beseitigen, der aus der ursprünglichen Aufgabe übrig geblieben war: Die Stammdaten des Kunden lassen sich ja in der Liste absichtlich nicht bearbeiten.

Wenn aber der Kunde nun schon ausgewählt ist, erwartet der Benutzer auch, diese Stammdaten von hier aus ändern zu können. Sie erzeugen dazu einfach ein AutoFormular aus Kunden im einspaltigen Design und nennen es frmKunden (Unter: Kunden).

Nach dem gleichen Muster können Sie es nun wie alle übrigen echten Detailtabellen-Unter-formulare auf einer eigenen Seite platzieren. Dort wird durch das Synchronisieren zwar immer nur exakt ein Datensatz angezeigt, aber das ist ja korrekt. Dafür bleibt die Suche in der Liste intuitiv und die mögliche Bearbeitung im Unterformular vor versehentlichem Suchzugriff geschützt.

Unterformularwechsel per VBA

Eigentlich könnten Sie jetzt zufrieden sein, denn alles funktioniert einwandfrei. Die folgenden Änderungen sind daher eher das Sahnehäubchen oder die Rettung bei umfangreichen Daten.

Wie Sie beim Zurechtschieben der Unterformulare sicherlich schon gemerkt haben, ist es recht schwierig, diese auf allen Seiten gleich zu positionieren. Wenn Sie das aber nicht tun, springen diese Seiten optisch beim Umschalten.

Eine zweite Schwierigkeit taucht erst bei einer großen Zahl von Datensätzen auf. Beim Laden des Hauptformulars werden zwangsläufig auch alle Unterformulare mit ihren Daten geladen. Je mehr Daten oder je mehr Unterformulare Sie haben, desto langsamer kann das werden. Da der Benutzer aber sowieso immer nur ein einziges Unterformular tatsächlich sehen kann, täuschen Sie den Wechsel zu einer anderen Seite einfach nur vor.

Wer die übrigen MS-Office-Programme kennt, wird jetzt das andere Control vermissen, welches zur Multipage gehört: das TabControl. Es sieht genau gleich aus, hat aber nicht wirklich einzelne Seiten. Der Inhalt ist immer derselbe, aber der Seitenwechsel ruft ein Ereignis auf, in welchem per VBA dann der Inhalt geändert wird.

Unterformularwechsel mit Hilfe der Access-Multipage

Tatsächlich können Sie das aber auch mit der Access-Multipage machen:

  1. Entfernen Sie dazu alle SubForm-Elemente auf den Seiten.

  2. Markieren Sie die Multipage (am einfachsten rechts neben den Seitenreitern) als Ganzes, und ändern Sie die Hintergrundart von Normal auf Transparent.

  3. Jetzt ziehen Sie wieder irgendein Unterformular auf das Formular, aber nicht in die Multipage. Erst wenn es im Detailbereich liegt, verschieben Sie das SubForm-Control so, dass es optisch auf der Seite liegt. Tatsächlich liegt es nun davor oder dahinter und hat mit der Multipage eigentlich gar nichts zu tun.

Wie Sie im fertigen Formular direkt testen können, ändert der Wechsel zwischen den Seiten nun optisch nichts mehr. Das wird nun die Aufgabe des VBA-Codes sein.

Nachdem Sie die SubForm auf die gewünschte Größe und Position gebracht haben, ergänzen Sie die Verknüpfen von/nach-Eigenschaften und können sogar das Herkunftsobjekt löschen. Da der Name des Herkunftsobjekts das Einzige ist, in dem sich die SubForm-Controls unterschieden haben, müssen Sie nur dies beim Seitenwechsel ändern.

Nachdem Sie die Multipage in mpgUnter und die SubForm in subUnter umbenannt haben, können Sie den Wechsel der Seite im mpgUnter-BeiÄnderung-Ereignis programmieren, wie in Listing 2 zu sehen ist.

Private Sub mpgUnter_Change()
Select Case mpgUnter.Value
Case 0: Me.subUnter.SourceObject = _
"frmKunden (Unter: Kunden)"
Case 1: Me.subUnter.SourceObject = _
"frmKunden (Unter: Bestellungen)"
Case 2: Me.subUnter.SourceObject = _
"frmKunden (Unter: Adressen)"
Case 3: Me.subUnter.SourceObject = _
"frmKunden (Unter: Mitarbeiter)"
Case 4: Me.subUnter.SourceObject = _
"frmKunden (Unter: Kontakte)"
End Select
End Sub

Damit bedeutet das Einbinden eines zukünftigen, neuen Unterformulars einfach nur eine neue Seite in der Multipage und eine Zeile mehr in der Select-Case-Anweisung. Damit beim Öffnen des Hauptformulars die SubForm nicht noch leer ist, lösen Sie den Seitenwechsel direkt per VBA aus (Listing 3).

Private Sub Form_Load()
Me.lstKunden.Value = Me.lstKunden.Column(0, 0)
mpgUnter_Change
End Sub

Bei dieser Gelegenheit können Sie auch schon den ersten Kunden der Liste direkt per VBA markieren, damit die Synchronisation ein sinnvolles Ergebnis liefern kann. Auch diese Markierung enthält Listing 3 bereits.

Wie Sie gesehen haben, ist der Programmieraufwand für diese Erleichterungen sehr gering. Die Benutzung ist intuitiv und ohne Fensterwechsel möglich, sodass eigentlich beide, Programmierer und Benutzer, zufrieden sein können.