11.8 Anwendungen mit mehreren Fenstern
11.8.1 Neue Formulare hinzufügen
Sie haben zwei Möglichkeiten, um zu Ihrer Anwendung weitere Formulare hinzuzufügen:
- Sie markieren im Projektmappen-Explorer den Projekteintrag, öffnen das Kontextmenü und wählen Hinzufügen • Windows Form... Im geöffneten Dialog brauchen Sie nur noch der neuen Quellcodedatei einen passenden Namen zu geben. Dieser ist gleichzeitig auch der Bezeichner der neuen Form-Klasse.
- Sie leiten von einer vorhandenen Form eine neue ab und ergänzen die abgeleitete um die gewünschten Fähigkeiten.
Leiten Sie eine neue Form von einer existierenden ab, darf die Basisklassenform Komponente des aktuellen Projekts sein. Sie kann aber auch aus einer externen Klassenbibliothek bezogen werden. In jedem Fall sollten Sie aber sicherstellen, dass das Projekt, in dem sich die Basisklassenform befindet, vor dem Ableiten kompiliert wird.
Auch für das Ableiten einer Form haben Sie die Entscheidungsfreiheit: Entweder Sie leiten ab, indem Sie in üblicher Weise die Basisklasse angeben, oder Sie benutzen dazu einen Assistenten. Beide Arbeitsweisen werde ich Ihnen nachfolgend beschreiben.
Alle Steuerelemente, die Sie aus der Toolbox auf die Form gezogen haben, die als Basis dient, sind per Vorgabe als Friend deklariert. Damit haben alle Klassen derselben Applikation Zugriff, und die Steuerelemente können von der Subform aus geändert werden.
Beerben einer Form
In jedem Fall sollten Sie beim Ableiten für die abgeleitete Form eine neue Quellcodedatei bereitstellen. Der Designer kann nämlich je Quellcodedatei nur eine Form anzeigen – und auf die bequeme Arbeit mit dem Designer wollen Sie sicher nicht verzichten. Nachdem Sie die neue Quellcodedatei hinzugefügt haben, leiten Sie die bereitgestellte Klasse einfach von der Basisform ab, zum Beispiel:
Class SubForm : Inherits BaseForm ...
Das Visual Studio 2008 ist pfiffig genug, um zu erkennen, dass Sie im Designer die abgeleitete Form anzeigen lassen wollen. Sie brauchen dazu nur in der Symbolleiste des Solution Explorers in den Designermodus umzuschalten. Alle Eigenschaften und Methoden, die in diesem in BaseForm codiert sind, vererben sich an den Typ SubForm weiter. Sie können Subform anschließend nach Belieben ergänzen und erweitern, sodass sie Ihren Vorstellungen entspricht.
Ableiten mit Visual Studio
Wenn Sie im Dialog zum Hinzufügen einer Komponente zum aktuellen Projekt das Element Geerbtes Formular auswählen, wird der Dialog Vererbungsauswahl angezeigt, in dem alle Formulare des aktuellen Projekts aufgelistet sind. In Abbildung 11.8 sind es die Klassen Form1, Form2 und Form3.
Abbildung 11.8 Der Dialog »Vererbungsauswahl«
Befindet sich die Formschablone in einer anderen Assembly, navigieren Sie über die Schaltfläche Durchsuchen zu der DLL, die die Basisform bereitstellen soll.
11.8.2 Lebenszyklus von Formularen
Um zur Laufzeit eine weitere Form anzuzeigen, muss zuerst die Klasse instanziiert werden:
Dim frm As New MyForm()
Damit befindet sich das Formular zwar im Hauptspeicher, auf dem Bildschirm erscheint es aber noch nicht. Die Komponenten der Form sind zu diesem Zeitpunkt allerdings bereits initialisiert. Zur Anzeige muss noch die Methode Show der Form aufgerufen werden:
frm.Show()
Das Gegenstück zu Show ist Hide. Nach dem Aufruf dieser Methode ist die Form zwar nicht mehr sichtbar, bleibt aber weiter im Hauptspeicher und kann ohne Neuinstanziierung mit Show erneut angezeigt werden.
frm.Hide()
Dasselbe Ergebnis erreichen Sie auch durch das Setzen der Eigenschaft Visible der Form auf True bzw. False.
Zum Schließen und Entladen einer Form dient die Methode Close:
frm.Close()
Alle drei Methodenaufrufe haben das Auslösen einer Ereigniskette zur Folge, wie sie weiter oben beschrieben ist.
11.8.3 Mehrere Fenster verwalten
Hat eine Anwendung mehrere gleichberechtigte Fenster, müssen Sie ein paar Vorkehrungen treffen, damit die Anwendung nicht früher als erwartet beendet wird.
Nachrichtenschleife
Wir müssen uns noch einmal ansehen, wie wir die Laufzeit einer Windows-Anwendung starten (minimalistische benutzerdefinierte Version):
Shared Sub Main() Application.Run(New HauptFormular()) End Sub
Run richtet eine Nachrichtenschleife für das Fenster (Startfenster) ein, dessen Referenz übergeben wird (der automatisch generierte Code erreicht dasselbe über einen Umweg). Die Nachrichtenschleife wird geschlossen, wenn die als Argument übergebene Form entladen wird, denn ein Automatismus fügt dem FormClosed-Ereignis der Form implizit einen Ereignishandler hinzu, der seinerseits Application.ExitThread aufruft. Mit ExitThread wird der aktuelle Thread beendet und damit auch die Laufzeit der Anwendung.
Dieses Verhalten hat weitreichende Konsequenzen. Wird nämlich zur Laufzeit einer Anwendung ein weiteres Fenster mit
Dim frm As New Form2() frm.Show()
instanziiert und angezeigt, gliedert es sich in die laufende Nachrichtenschleife ein und teilt sich diese mit dem Startfenster. Das bedeutet aber nicht, dass das neue Fenster gleichberechtigt ist. Vielmehr hat nur das Hauptfenster die Verantwortung darüber, wann die Nachrichtenschleife geschlossen wird – nämlich genau dann, wenn es selbst geschlossen wird. Die Folge ist, dass jedes Fenster der Anwendung geschlossen wird, das zu dieser Nachrichtenschleife gehört.
Dieses Verhalten ist nicht immer wünschenswert. Wenn Sie auf den Automatismus der Nachrichtenschleife verzichten wollen, müssen Sie deren Steuerung selbst in die Hand nehmen. Dabei genügt es nicht, zuerst das Startformular zu instanziieren und danach mit der Methode Run eine Nachrichtenschleife einzurichten:
Dim frm As New StartFormular() frm.Show() Application.Run()
Bei dieser Lösung sind die Nachrichtenschleife und das Startfenster nicht aneinander gekoppelt, es fehlt definitiv eine Anweisung, um die Nachrichtenschleife zu beenden. Die Anwendung läuft auch nach dem Schließen des letzten Fensters unbemerkt im Hintergrund weiter. Das können Sie sehr schön nach dem Start der Anwendung aus der Entwicklungsumgebung heraus erkennen: Das Visual Studio schaltet nicht in den Entwicklungsmodus zurück.
Die Lösung des Problems ist an sich trivial, denn die Nachrichtenschleife muss genau dann mit Application.Exit bzw. Application.ExitThread beendet werden, wenn das letzte Fenster geschlossen wird. Es stellt sich damit die Frage: Wie können wir feststellen, wann das letzte Fenster geschlossen wird?
Verwaltung der Formulare
Jede Anwendung speichert automatisch alle geöffneten Formulare in der mit Shared an die Klasse Application gebundenen Eigenschaft OpenForms vom Typ FormCollection. Alle Methoden zum Verändern der Liste, wie zum Beispiel Add und Remove, sind nicht öffentlich. Die Liste wird also vollständig vom .NET Framework verwaltet. Ihre Programme können nur lesend darauf zugreifen.
Die Auflistung stellt Ihnen nur drei Funktionalitäten öffentlich zur Verfügung: einen Iterator zum Durchlaufen der Auflistung zum Beispiel in einer For Each-Schleife, die schreibgeschützte Anzahl der Elemente in der Eigenschaft Count und einen schreibgeschützten Indexer. Mehr brauchen Sie nicht, um festzustellen, wann das letzte Fenster geschlossen wird und die Anwendung beendet werden soll. Zur Erinnerung: Wir hatten die feste Bindung zwischen dem Startfenster und der Nachrichtenschleife durch einen Aufruf der parameterlosen Überladung von Application.Run() aufgehoben und mussten nur noch einen Mechanismus finden, um nach dem Schließen der letzten Form auch die Nachrichtenschleife zu beenden.
Ich möchte Sie noch auf eine kleine Stolperfalle aufmerksam machen. Dem Indexer der Auflistung FormCollection können Sie entweder einen Integer oder einen String übergeben. Der Integer beschreibt die Position in der nullbasierten Auflistung. Meistens werden Sie damit jedoch nicht viel Erfolg haben, weil Sie in der Regel die Position des Fensters in der Liste nicht kennen. Besser hingegen ist die Übergabe einer Zeichenfolge. Diese beschreibt den Namen der gesuchten Form.
Hinweis |
Wenn Sie ein Formular in OpenForms über den Namen ansprechen, sollten Sie unbedingt für eindeutige Namen sorgen. Eindeutige Titelleistentexte helfen hier nicht weiter. |
Mehrfensteranwendung
Schauen wir uns zuerst einmal das Aussehen der Anwendung an, bevor wir Details der Implementierung besprechen. Abbildung 11.9 zeigt den Zustand nach zweimaligem Öffnen eines Fensters mit dem Öffnen-Button.
Abbildung 11.9 Mehrfensteranwendung
Mit der Liste OpenForms ist die Implementation nun sehr einfach. Sie wird im folgenden Listing gezeigt. Im Ereignishandler Zu() von FormClosed prüfen wir, ob mit der aktuellen Form auch die letzte der laufenden Anwendung geschlossen wird. Eine Prüfung im Ereignishandler von FormClosing ist nicht möglich, da zu diesem Zeitpunkt die Auflistung OpenForms noch unverändert ist (das Schließen kann ja noch verweigert werden). Liefert der Zähler Count der Liste den Wert null, sind alle Formulare geschlossen, und die Methode Exit oder ExitThread von Application wird aufgerufen, um (auch) die Nachrichtenschleife zu beenden.
Die Hilfsfunktion Listen() schreibt in alle Textboxen aller geöffneten Fenster, indem sie in einer äußeren For Each-Schleife die Liste OpenForms durchläuft, um die Textboxen aller offenen Formulare anzusprechen. In einer inneren Schleife werden alle in OpenForms gelisteten Formulare nochmals durchlaufen, um den Text der Textbox der Form in der äußeren Schleife zu füllen. Zur Kennzeichnung des aktuellen Formulars in der Liste wird ein Stern verwendet. Zu Demonstrationszwecken wird das Formular über seinen Namen in der Auflistung OpenForms herausgesucht. Der Name ist bewusst anders gewählt als der Titelleistentext.
Sowohl im Ereignishandler Auf() von Shown als auch im Ereignishandler Zu() von FormClosed wird Listen() aufgerufen, um die Auflistungen geöffneter Fenster immer aktuell zu halten. Im Ereignishandler Neu() des Buttons wird ein neues Fenster erzeugt und angezeigt. Der Zähler im Titelleistentext dient nur der leichteren Orientierung.
'...\WinForm\MehrFenster\Fenster.vb |
Public Class Fenster <STAThread()> Public Shared Sub Main() Dim start As New Fenster() start.Show() start.Name = "Name: " & start.Text Application.Run() End Sub Private Shared Sub Listen() For Each fm As Fenster In Application.OpenForms fm.Fensterliste.Text = "" Dim akt As Form = Application.OpenForms(fm.Name) For Each f As Form In Application.OpenForms fm.Fensterliste.Text += f.Text If f.Name.Equals(akt.Name) Then fm.Fensterliste.Text += "*" fm.Fensterliste.Text += Environment.NewLine Next Next End Sub Private Shared Zähler As Integer Private Sub Neu(sender As Object, e As EventArgs) Handles Öffnen.Click Dim frm As New Fenster() Zähler += 1 frm.Text = "Fenster " & Zähler frm.Name = "Name: " & frm.Text frm.Show() End Sub Private Sub Auf(sender As Object, ByVal e As EventArgs) _ Handles MyBase.Shown Listen() End Sub Private Sub Zu(sender As Object, e As FormClosedEventArgs) _ Handles MyBase.FormClosed If Application.OpenForms.Count = 0 Then Application.Exit() Listen() End Sub End Class
Hinweis |
Sie sollten in den Projekteinstellungen das Anwendungsframework deaktivieren und Sub Main als Startobjekt festlegen. |
Das Programm wird erst beendet, wenn alle Fenster geschlossen wurden. Die Reihenfolge ist beliebig.
11.8.4 Begrüßungsfenster (Splash)
Manchmal nimmt die Initialisierung einer Anwendung eine längere Zeitspanne in Anspruch. Um den Anwendern zu zeigen, dass das Programm tatsächlich startet, wird während dieser Phase ein sogenanntes Splash-Fenster angezeigt . Splash-Fenster haben keine Titelleiste und reagieren im Allgemeinen nicht auf Ereignisse. Daher ist es nicht notwendig, das Fenster einer Nachrichtenschleife zuzuordnen, und wir können uns in Main eine Instanz der Splash-Form besorgen und anzeigen lassen, bevor die Nachrichtenschleife ins Leben gerufen wird.
Um ein Fenster ohne Titelleiste zu erzeugen, wird FormBorderStyle auf False eingestellt. Die Anzeigeposition ist üblicherweise mittig auf dem Bildschirm, StartPosition ist also FormStartPosition.CenterScreen. Die Gestaltung des Fensters ist beliebig, hier wird ein ganz einfaches verwendet (siehe Abbildung 11.10).
Abbildung 11.10 Einfaches Splash-Fenster
Damit die Initialisierungsarbeiten, die im Beispiel durch eine For-Schleife simuliert werden, nicht beginnen, bevor das Fenster vollständig gezeichnet ist, rufen wir vor der Schleife Application.DoEvents() auf. (Hinweis: Application.DoEvents() nehmen wir weiter unten in diesem Kapitel genauer unter die Lupe.)
In der Praxis können während der Initialisierung Fehler auftreten – beispielsweise wenn mit der Initialisierung ein Datenbank- oder Dateizugriff verbunden ist. Deshalb werden die Initialisierungsarbeiten in einem Try-Block ausgeführt. Tritt eine Ausnahme auf, wird der Anwender benachrichtigt und die Anwendung geschlossen. Um die Fehlerbehandlung zu testen, enthält die Schleife des Beispiels eine Throw-Anweisung, deren Auskommentierung nur aufgehoben werden muss.
Startet die Anwendung fehlerfrei, muss das Splash-Fenster mit Close geschlossen werden. Dann ist auch der Zeitpunkt gekommen, das Hauptfenster der Anwendung mit Application.Run() zu öffnen.
'...\WinForm\MehrFenster\Initialisierung.vb |
Public Class Initialisierung <STAThread()> Public Shared Sub Main() Dim sp As New Splash() sp.Show() Application.DoEvents() Try For p As Integer = 0 To 100 sp.Fortschritt.Text = "Initialisierung " & p & "%" sp.Fortschritt.Refresh() Threading.Thread.Sleep(50) ' Simulation einer Ausnahme ' Throw New Exception("Abbruch") Next sp.Close() Application.Run(New Initialisierung()) Catch MessageBox.Show("Es ist ein Fehler aufgetreten.", _ Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error) Application.Exit() End Try End Sub End Class
Hinweis |
Ein Aufruf von Application.Exit() vor dem Start der Nachrichtenschleife verpufft wirkungslos. Daher darf Application.Run() nicht nach dem Try-Block aufgerufen werden. |
Alternativ können Sie das Anwendungsframework in den Projekteinstellungen aktivieren und einen Begrüßungsbildschirm auswählen. Er wird in einem eigenen Thread gestartet und muss über den Aufruf eines Dispose-Delegates geschlossen werden. Aufgrund dieser Problematik habe ich mich hier für eine manuelle Codierung der Main-Methode entschieden.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.