Es gibt den einen oder anderen Bug in Microsoft Access, der nicht als solcher identifiziert werden kann, weil man einfach nicht herausfindet, wie man ein Fehlverhalten reproduzierbar auslöst. Im vorliegenden Fall ist es einem unserer findigen Kollegen gelungen, einen Bug zu erkennen, der bisher nach unserer Recherche noch nicht dokumentiert wurde. Es handelt sich um einen Bug, der je nach Konstellation mal gar keine Auswirkungen hat und mal schwere Folgen mit sich bringen kann. Den Auslöser zu identifizieren ist auch alles andere als trivial – aber wir haben ihn gleich im Titel präsentiert: Wenn die Höhe eines Unterformulars gleich 0 wird, entlädt Access das Formular. Welche Schritte zum Nachvollziehen nötig sind, welche Folgen dies haben kann und wie sich das Problem beheben lässt, erläutern wir in diesem Beitrag.
Video passend zu diesem Artikel:
Eigentlich kann das gar nicht sein, dachte sich der Access-Entwickler Stefan M. (Name von der Redaktion geändert). Irgendwie spielt das Formular verrückt: Der Timer im Unterformular funktioniert nicht mehr, Variablen werden geleert, obwohl es keinen unbehandelten Laufzeitfehler gab (Stefan M. achtet sehr auf die Behandlung von Fehlern). Zumindest hat sein Kunde dieses Verhalten geschildert – er selbst hatte keinen Schimmer, wie er das Verhalten reproduzieren sollte.
Schließlich setzte er sich selbst mit dem Kunden zusammen und schaute sich an, was dieser in der Praxis mit dem Formular veranstaltet. Schließlich zeigte sich, dass wann immer das merkwürdige Verhalten auftrat, der Kunde die Höhe des Formulars verkleinert hatte, damit er zwar noch den Kopfbereich des Formulars sehen konnte, gleichzeitig aber genug Platz für andere Formulare verfügbar war. Es musste also irgendetwas mit der Höhe des Formulars zu tun haben.
Zurück am eigenen Rechner setzte Stefan M. sich hin und testete die halbe Nacht. Irgendwann hatte er den Fehler identifiziert: Wann immer er das Formular so klein machte, dass der Detailbereich nicht mehr zu sehen war, wurden die Variablen geleert und die aktuelle Markierung des Datensatzes im Unterformular wurde zurückgesetzt. Die erste Erkenntnis war: Das Unterformular musste auf irgendeine Art entladen worden sein. Einige Experimente später hatte er die Konstellation ermittelt, die zum Fehler führt – wir werden diese nun nachstellen.
Reproduzieren des Problems
Ausgangspunkt ist die folgende Situation: Wir verwenden ein Haupt- und ein Unterformular. Das Unterformular befindet sich im Detailbereich des Hauptformulars. Im Hauptformular sind außerdem Formularkopf und -fuß aktiviert. Diese Bereiche müssen nicht sichtbar sein, können also die Höhe 0 aufweisen – es reicht, dass sie aktiviert sind.
In unserem Beispiel zeigen wir im Detailbereich und im Unterformular noch Kategorien und die darin enthaltenen Produkte an – das ist kein dekoratives Beiwerk, sondern nötig für das Nachvollziehen des Beispiels (siehe Bild 1).
Bild 1: Entwurf des problembehafteten Formulars
Um zu zeigen, dass das Formular zu einem bestimmten Zeitpunkt entladen wird, haben wir zunächst das Ereignis Beim Entladen des Unterformulars in Form der folgenden Ereignisprozedur implementiert:
Private Sub Form_Unload(Cancel As Integer) MsgBox "Das Formular wird entladen." End Sub
Sobald das Formular entladen wird, sollte also diese Meldung erscheinen. Damit ließ sich schnell ermitteln, zu welchem Zeitpunkt das Formular entladen wurde – nämlich genau dann, wenn wir die Höhe des Formulars so verkleinert haben, dass der Detailbereich nicht mehr sichtbar war (siehe Bild 2).
Bild 2: Der Bug tritt genau beim Erreichen der Höhe 0 des Detailbereichs auf.
Unterformular entladen – die Folgen
Nachdem die Ursache gefunden war, galt es, die Folgen zu untersuchen. Hier gibt es zumindest die folgenden Probleme:
- Der Datensatzzeiger im Unterformular wird wieder auf den ersten Datensatz gesetzt.
- Filter im Unterformular werden gelöscht.
- Variablen, die im Klassenmodul des Unterformulars deklariert sind, werden geleert.
Das haben wir wie folgt belegt. Der erste Teil ist der Timer. Wir haben die Eigenschaft Zeitgeberintervall auf 1.000 eingestellt (die Einheit ist Millisekunden, also wird das Timer-Ereignis einmal pro Sekunde ausgelöst).
Außerdem haben wir für die Ereigniseigenschaft Bei Zeitgeber die folgende Ereignisprozedur hinterlegt:
Private Sub Form_Timer() Me.Parent.txtTimer = Now Me.Parent.txtTest = strTest End Sub
Diese schreibt bei jedem Aufruf des Timer-Ereignisses, also einmal pro Sekunde, das aktuelle Datum und die aktuelle Zeit in das Textfeld txtTimer. Außerdem schreibt sie den Wert einer Variablen namens strTest in das Textfeld txtTest. Diese Variable deklarieren wir wie folgt im Klassenmodul des Unterformulars:
Dim strTest As String
Beim Laden des Unterformulars wollen wir die Variable strTest erstmalig füllen und außerdem in ein weiteres Textfeld namens txtLoadUnload den Text Load und Datum und Zeit des Ladens eintragen.
Private Sub Form_Load() strTest = "Wert der Variablen strTest" Me.Parent.txtLoadUnload = "Load " & Now Me.Parent.txtTest = strTest End Sub
Damit sieht der Inhalt des Formulars wie in Bild 3 aus. Das erste Textfeld enthält den Text Load …, das zweite liefert sekündlich die aktuelle Zeit und das dritte wird regelmäßig mit dem Inhalt der Variablen strTest gefüllt.
Bild 3: Das Formular direkt nach dem Öffnen
Um nun auch die Auswirkungen des Entladens des Formulars zu demonstrieren, kommentieren wir die MsgBox in der Prozedur für das Ereignis Beim Entladen aus und fügen eine Anweisung hinzu, die den Text im obersten Textfeld auf Unload … ändert:
Private Sub Form_Unload(Cancel As Integer) ''MsgBox "Das Formular wird entladen." Me.Parent.txtLoadUnload = "UnLoad " & Now End Sub
Wenn wir nun die Höhe des Detailbereichs minimieren, zeigt das oberste Textfeld sofort den Text Unload … an, was zeigt, dass die Form_Unload-Prozedur ausgelöst wurde.
Weitere Experimente
Wir sehen nun, dass das Unload-Ereignis ausgelöst wurde und somit alle Variablen geleert wurden. Weitere Ergebnisse sind:
- Wenn wir im Unterformular den zweiten Datensatz markieren, die Höhe des Detailbereichs minimieren und diesen dann wieder vergrößern, ist wieder der erste Datensatz im Unterformular markiert.
- Wenn wir im Unterformular einen Filter anwenden, der beispielsweise nur den ersten Datensatz anzeigt, und den Detailbereich des Hauptformulars verkleinern und wieder vergrößern, ist der Filter wieder entfernt.
- Andererseits bleibt eine einmal getätigte Sortierung im Unterformular erhalten.
Lösungen des Problems
Eine Lösung dieses Problems sahen wir zunächst nicht. Selbst das Setzen des Parameters Cancel auf den Wert True im Unload-Ereignis des Unterformulars hat keinerlei Auswirkung – das Formular wird dennoch entladen.
Die einzige Möglichkeit schien, die offensichtlichen Fehler und Probleme zu identifizieren und diese per Workaround zu beheben – also zum Beispiel Variablen in der Code behind-Klasse des Hauptformulars zu deponieren, den aktuellen Filter in einer Variablen zu speichern und wiederherzustellen, wenn das Formular entladen wurde oder auch die Position des Datensatzzeigers zu speichern und wiederherzustellen, wenn das Unterformular wieder sichtbar wird.
Ungewöhnliche, aber unpraktische Lösung
Dann erschien doch noch eine Lösung, die schlicht und einfach mit intuitiv bezeichnet werden kann – wer auf eine Erklärung hofft, wie man solche Probleme aufdecken kann, den müssen wir leider enttäuschen.
Die Lösung lautet: Wir fassen das gesamte Formular plus Unterformular nochmals in ein weiteres Formular ein, das nur den Detailbereich anzeigt und keinen Kopf- oder Fußbereich. Das heißt: Wir erstellen ein neues Formular, das wir beispielsweise frmHolder nennen. In dieses ziehen wir das bisher verwendete Formular frmKategorienProdukte hinein. Die Größe des Formulars frmHolder passen wir so an, dass das Formular frmKategorienProdukte genau hineinpasst (siehe Bild 4).
Bild 4: Das Formular wird als Unterformular zum Formular frmHolder hinzugefügt.
Nun sind im Formular frmHolder noch ein paar Anpassungen zu erledigen, damit der Benutzer nicht merkt, dass er es mit einem zusätzlichen Formular zu tun hat:
- Eigenschaft Datensatzmarkierer auf Nein einstellen
- Eigenschaft Navigationsschaltflächen auf Nein einstellen
- Eigenschaft Bildlaufleisten auf Nein einstellen
- Gegebenenfalls Automatisch zentrieren ebenfalls auf Nein einstellen
- Wenn Icon und/oder Titel eingestellt waren, diese Einstellungen in frmHolder übernehmen
Damit erhalten wir anschließend das Formular aus Bild 5. Wir können dieses wie das vorherige Formular in der Höhe verkleinern, nur dass diesmal nicht das Unterformular entladen wird.
Bild 5: Das Formular im Holder-Formular funktioniert jetzt ohne den Bug.
Weitere Änderungen ergeben sich, wenn man beispielsweise das Formular mit DoCmd.OpenForm öffnet und Parameter übergibt – diese müssten dann vom aufgerufenen Formular frmHolder an das untergeordnete Formular frmKategorienProdukte weitergeleitet werden.
DoCmd.OpenForm-Parameter übertragen
Wenn das Hauptformular plötzlich ein Unterformular wird, wirken sich eventuell per DoCmd.OpenForm übergebene Parameter nicht mehr wie gewohnt aus.
Das gilt direkt für den Parameter WhereCondition. Die folgende Anweisung funktioniert natürlich nicht wie gewünscht, da das Formular frmHolder gar keine Datensatzquelle aufweist:
DoCmd.OpenForm "frmHolder", _ WhereCondition:="KategorieID = 2"
Das nächste Problem ist, dass wir die übergebene Bedingung noch nicht einmal aus dem Formular frmHolder auslesen können, um diese auf das darin enthaltenen Formular frmKategorienProdukte zu übertragen. Die Eigenschaft Filter liefert diesen Wert nämlich nur, wenn das aufgerufene Formular eine passende Datensatzquelle enthält – beispielsweise eine Abfrage auf Basis der Tabelle tblProdukte. Dann können wir im Form_Load-Ereignis des Formulars frmHolder den folgenden Code platzieren:
Private Sub Form_Load() If Not Len(Me.Filter) = 0 Then Me.frmKategorienProdukte.Form.Filter = Me.Filter Me.frmKategorienProdukte.Form.FilterOn = True End If End Sub
Sobald wir jedoch den Parameter OpenArgs nutzen wollen, wird es komplizierter: Wir können OpenArgs dann in frmHolder auslesen und beispielsweise an eine öffentliche Eigenschaft von frmKategorienProdukte übergeben, aber nicht an OpenArgs, denn diese Eigenschaft ist schreibgeschützt. Sie kann nur beim Öffnen per Parameter übergeben werden.
Die einfachste Lösung
Eine weitere mögliche Lösung des Problems ist, einfach die Elemente aus dem Formularkopf in den Detailbereich zu übertragen und den Formularkopf- und den Formularfußbereich über den entsprechenden Kontextmenü-Befehl auszublenden (siehe Bild 6).
Bild 6: Formularkopf und -fuß ausblenden
Das gelingt zwar nur, wenn es sich um die Ansicht Einzelnes Formular handelt. Beim Endlosformular benötigt man einfach manchmal Steuerelemente im Formularkopf oder -fuß, da sich die Inhalte im Detailbereich entsprechend der Anzahl der Datensätze und der Höhe des verfügbaren Platzes wiederholen.
Allerdings ist es gar nicht möglich, ein Unterformular im Detailbereich eines Formulars in der Endlosansicht anzuzeigen. Wenn wir versuchen, die Eigenschaft Standardansicht für das Hauptformular auf Endlosformular einzustellen, erscheint die Meldung aus Bild 7, die darauf hinweist, dass unter anderem Unterformulare nicht im Detailbereich von Endlosformularen verwendet werden können.
Bild 7: Fehler beim Versuch, das Formular auf die Endlosansicht umzustellen
Also verschieben wir einfach wie in Bild 8 die Elemente des Formularkopfes in den Detailbereich. Wenn Sie den Formularkopf genutzt haben, um eine alternative Hintergrundfarbe zu verwenden, können Sie auch einfach ein Rechteck-Steuerelement hinter den Steuerelementen platzieren, die dadurch hervorgehoben werden sollen – dazu stellen wir einfach die Eigenschaft Hintergrundart des Rechtecks auf Normal und die Hintergrundfarbe auf die gewünschte Farbe ein.
Bild 8: Verschieben der Elemente des Formularkopfes in den Detailbereich
Der Nachteil dieser Lösung ist, dass sich der Datensatzmarkierer, sofern er angezeigt wird, auch über den nachgebauten Formularkopf erstreckt. Das sollte jedoch kein großes Problem darstellen (siehe Bild 9). Dafür kann man die Parameter beim Aufruf eines solchen Formulars ohne Einschränkungen nutzen – und die Probleme durch den Bug fallen ohnehin weg.
Bild 9: Die Version ohne Formularkopf in der Formularansicht
Zusammenfassung und Ausblick
Wir hoffen, dass wir mit dem Aufdecken dieses Bugs den einen oder anderen unerklärlichen Fehler in Ihren Anwendungen erklären konnten und Sie in diesem Fall die Alternativen nutzen können. Oder tritt bei Ihnen ein anderer, nicht nachvollziehbarer Fehler auf? Dann zögern Sie nicht und setzen Sie sich mit uns in Verbindung – vielleicht können wir eine Lösung finden. Schicken Sie einfach eine E-Mail mit der Schilderung des Problems und gegebenenfalls mit einer Beispieldatenbank an info@access-im-unternehmen.de.
Downloads zu diesem Beitrag
Enthaltene Beispieldateien:
BugUnterformularWirdEntladen_Loesung.accdb