|
Steuerelement-Arrays sind einerseits ein Segen, erlauben sie doch
in Kombination mit der Load-Anweisung die Programmierung von
dynamischen Formularen und UserControls. Andererseits entpuppen sich
Steuerelement-Arrays nur zu oft fast als Fluch, denn bei der
Anwendung der Anweisungen Load und Unload warten zahlreiche Fallen
auf Sie.
So gibt es zum Beispiel Probleme, wenn Sie dynamisch in ein
Steuerelement-Array geladene Steuerelemente in ein weiteres,
ebenfalls dynamisch in ein Steuerelement-Array geladenes
Container-Steuerelement verschieben - sie diesem also unterzuordnen.
Bei dem Versuch, dieses Container-Steuerelement mittels der
Unload-Anweisung wieder zu entladen, werden Sie mit der
Fehlermeldung "Entladen in diesem Kontext nicht möglich"
konfrontiert. Ein Blick in die Dokumentation zu dem Fehler mit der Nummer
365 fördert zwar eine ausführlichere Beschreibung der
Fehlermeldung zu Tage, jedoch stimmen die dort aufgeführten,
möglichen Ursachen für die Fehlermeldung nicht mit der eben
beschriebenen Situation überein. Die tatsächliche Ursache für den
Laufzeitfehler sind in diesem Fall die untergeordneten
Steuerelemente. Erst wenn diese untergeordneten Steuerelemente aus
dem betroffenen Steuerelement entfernt werden, indem sie entweder
entladen werden oder in ein anderes Containersteuerelement
beziehungsweise direkt in das allen übergeordnete Parent-Objekt
(meist wohl ein Form) als Container verschoben werden, lässt sich
das betroffene Steuerelement per Unload-Anweisung aus dem Speicher
und dem Parent-Container hinaus befördern.
Ein Parent-Container, der dynamisch geladene und teils
verschachtelte Steuerelemente beheimatet, müsste sich folglich
"merken", wer wem untergeordnet oder übergeordnet ist.
Der Verwaltungsaufwand hierfür ist enorm - eine universelle
Lösung, bei der die konkrete Verschachtelung der Steuerelemente
nicht fest codiert zu werden braucht, wäre weitaus eleganter und
praktikabler. Ein erster Lösungsansatz könnte daher etwa so
aussehen:
Public Sub UnloadCtl(ByVal Object As Object)
Dim Control As Control
For Each Control In Object.Parent.Controls
If Control.Container Is Object Then
UnloadCtl Control
End If
Next Control
Unload Object
End Sub
Diese Prozedur UnloadCtl wird nun anstelle der Unload-Anweisung
aus Visual Basic verwendet, um Steuerelemente aus
Steuerelement-Arrays zu entfernen. Sie durchläuft sämtliche
Steuerelemente des Parent-Containers und prüft bei jedem, ob
vielleicht sein spezifischer Container das zu entladende
Steuerelements ist. Falls der Container übereinstimmt, wird das
aktuelle Steuerelement über einen rekursiven Aufruf der
UnloadCtl-Prozedur entladen. Dabei werden natürlich auch wiederum
dessen untergeordnete Steuerelemente entladen sowie die
Steuerelemente unterhalb der untergeordneten Steuerelemente und so
weiter und so fort. Das eigentlich zu entladende Steuerelement wird
schließlich ganz zum Schluss über einen Aufruf der
Unload-Anweisung entladen.
Diese vorläufige Implementierung der UnloadCtl-Prozedur hat
allerdings eine offensichtliche Schwäche und einen groben Fehler.
Die Schwäche offenbart sich, wenn sich auf in dem Parent-Container,
der das zu entladende Steuerelement enthält, eine größere Anzahl
an weiteren Steuerelementen befindet. Je mehr Steuerelemente das
Formular beheimatet, um so schlechter wird das Laufzeitverhalten der
Prozedur. Aufgrund der Rekursion steigt die Anzahl der notwendigen
Schleifendurchgänge mit jedem zusätzlichen Steuerelement nicht
linear, sondern sogar expotenzial an.
Jener erwähnte Fehler tritt hingegen nur unter bestimmten
Bedingungen auf. Wenn ein Steuerelement mittels der Unload-Anweisung
entladen wird, verschwindet es auch aus der Controls-Auflistung des
Formulars. Allerdings erzeugt eine For-Each-Schleife eine nur für
die Dauer der For-Each-Schleife gültige Auflistung, die von der
Laufvariablen durchlaufen wird und die von der Unload-Anweisung
nicht berührt und nicht verändert wird. Infolgedessen liefert die
temporäre Auflistung unter Umständen eine Referenz auf ein nicht
mehr gültiges Steuerelement. Den Versuch über die Laufvariable auf
das ungültige Steuerelement zu zugreifen, quittiert Visual Basic
mit dem Laufzeitfehler 340.
Es gibt jedoch eine andere, wenngleich weitaus komplizierte
Lösung. Die zweite Fassung der UnloadCtl-Prozedur gleicht der
ersten Fassung zumindest oberflächlich. Unter der Haube leistet
diese zweite Variante der Prozedur wesentlich mehr als der erste
Versuch einer Implementierung.
So wurde das Laufzeitverhalten dahingehend verbessert, dass eine
steigende Anzahl an Steuerelementen nur mehr einen linearen Anstieg
der Schleifendurchgänge zur Folge hat. Des weiteren wurde der eben
beschriebene Fehler dadurch gelöst, dass zuerst sämtliche zu
entladenden Steuerelemente aufgespürt und vermerkt werden und diese
dann erst zum Schluss in einem Durchgang entladen werden. Dabei
kommen diejenigen Steuerelemente, die sich in der Hierarchie weiter
unten befinden, zuerst zum Zuge und erst dann folgen jeweils deren
Containersteuerelemente.
Public Sub UnloadCtl(ByVal Object As Object, _
Optional ByVal UnloadSubControls As Boolean = False)
Dim nSubControls() As Collection
Dim nControl As Control
Dim nParent As Object
Dim nContainer As Object
Dim i As Long
Dim z As Long
If UnloadSubControls Then
ReDim nSubControls(1 To 5) As Collection
Set nParent = Object.Parent
For Each nControl In nParent.Controls
Set nContainer = nControl
z = 0
Do
Set nContainer = nContainer.Container
z = z + 1
Loop Until nContainer Is nParent Or nContainer Is Object
If nContainer Is Object Then
If z > UBound(nSubControls) Then
ReDim Preserve nSubControls(1 To z) As Collection
End If
If nSubControls(z) Is Nothing Then
Set nSubControls(z) = New Collection
End If
nSubControls(z).Add nControl
End If
Next 'nControl
For i = UBound(nSubControls) To 1 Step -1
If Not (nSubControls(i) Is Nothing) Then
For Each nControl In nSubControls(i)
Unload nControl
Next 'nControl
End If
Next i
End If
Unload Object
End Sub
Für die Aufnahme der Referenzen auf die zu entladenden
Steuerelemente deklariert die Unload-Prozedur ein dynamisches Array.
Das Array enthält jedoch selbst nicht die Referenzen. Statt dessen
wird das Array mit Collection-Objekten gefüllt, die wiederum die
Referenzen auf die zu entladenden Steuerelementen aufnehmen. Dabei
werden in der ersten Collection diejenigen Steuerelemente abgelegt,
die sich direkt unter dem eigentlich zu entladenden Steuerelement
befinden. In der zweiten Collection werden hingegen diejenigen
Steuerelemente abgelegt, die sich unterhalb derjenigen
Steuerelemente befinden, die in der ersten Collection abgelegt
wurden, und so fort. Demnach entspricht der Index der Collections im
Array der Tiefe der Steuerelemente auf dem Formular relativ zum
obersten zu entladenden Steuerelement. Unter der Annahme, dass die
meisten Anwendungen mit fünf Ebenen auskommen, wird das Array
dementsprechend vordimensioniert - die Größe des Arrays wird aber
bei Bedarf automatisch erhöht. Um Speicher und Zeit zu sparen, wird
die Instanzbildung der jeweils benötigten Collections auf einen
späteren Zeitpunkt verschoben.
Wie schon bei der ersten Lösung werden sämtliche Steuerelemente
auf dem Formular in einer For-Each-Schleife durchlaufen. In einer
zweiten Schleife erfolgt ausgehend vom aktuellen Steuerelement eine
Bewegung in der Hierarchie der Steuerelemente aufwärts, in dem die
Container-Eigenschaft des aktuellen Containersteuerelements abfragt
werden, bis entweder das Parent-Objekt oder das zu entladende
Steuerelement erreicht wird. Bei jedem Schleifendurchgang wird die
Zählervariable z inkrementiert, so dass am Ende die Tiefe des
aktuellen Steuerelements in der Hierarchie bekannt ist. Diese
Information ist notwendig, um das aktuelle Steuerelement in der
richtigen Collection abzulegen, falls das Steuerelement sich
unterhalb des zu entladenden Steuerelements befindet. Doch bevor das
Steuerelement der jeweiligen Collection hinzugefügt werden kann,
ist noch zu prüfen, ob das Array mit den Collections der Tiefe des
Steuerelements entsprechend groß dimensioniert wurde bzw. ob das
Array für diese Stufe bereits eine Collection bereithält. Bei
Bedarf wird also das Array vergrößert und mit einem zusätzlichem
Collection-Objekt belegt. Anschließend kann das Steuerelement der
entsprechenden Collection des Arrays hinzugefügt werden.
Nachdem sämtliche Steuerelemente auf dem Formular durchlaufen
wurden, wird das Array nach Collection-Objekten durchsucht. Dabei
wird es von hinten nach vorne durchlaufen, so dass die
Steuerelemente in tieferliegenden Schichten zuerst entladen werden.
Das Entladen der Steuerelemente erfolgt wie gewohnt mittels der
Unload-Anweisung in einer weiteren For-Each-Schleife, die auf die
aktuelle Collection angewandt wird. Zu guter Letzt wird noch das
eigentlich zu entladende Steuerelement per Unload eliminiert.
|