Bug: Unterformular entlädt bei Bereichshöhe gleich 0

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:

YouTube

Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren

Video laden

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).

Entwurf des problembehafteten Formulars

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).

Der Bug tritt genau beim Erreichen der Höhe 0 des Detailbereichs auf.

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.

Das Formular direkt nach dem Öffnen

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).

Das Formular wird als Unterformular zum Formular frmHolder hinzugefügt.

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.

Das Formular im Holder-Formular funktioniert jetzt ohne den Bug.

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).

Formularkopf und -fuß ausblenden

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.

Fehler beim Versuch, das Formular auf die Endlosansicht umzustellen

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.

Verschieben der Elemente des Formularkopfes in den Detailbereich

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.

Die Version ohne Formularkopf in der Formularansicht

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

Download

Schreibe einen Kommentar