Startbildschirm für Windows-Anwendung anzeigen
Dieses Beispiel zeigt eine alternative Vorgehensweise, um während
der Initialisierungsphase eines Programms ein Startfenster
anzuzeigen, das Auskunft über den aktuellen Fortschritt gibt. Die
Initialisierungsmethode wird als zusätzlicher Thread gestartet, so
dass das Startfenster weiterhin bedienbar bleibt und nicht durch
überlange Initialisierungsvorgänge blockiert wird.
Entstanden ist das Beispiel aufgrund von Anregungen einiger Leser
des VB.NET-Codebooks, die teilweise von Problemen mit der Umsetzung
des Rezeptes 98 unter Windows XP berichteten. Ich habe die
unterschiedlichen Schritte in entsprechenden Klassen gekapselt, so
dass der Einsatz nun deutlich einfacher wird. Die Anzeige des
Startfensters erfolgt auch nicht mehr via ShowDialog. Statt dessen
wird die Fensteranzeige über ein ApplicationContext-Objekt
umgeschaltet.
Anwenden des Beispiels
Nur wenige Schritte sind notwendig, um das Beispiel für das
eigene Programm nutzen zu können. Zunächst muss in den
Projekteinstellungen als Startobjekt SubMain eingestellt
werden. Die Methode wird dann z.B. im Hauptfenster eingerichtet. Sie muss
je eine Instanz der Hauptfenster- und der Startfensterklasse anlegen
und einer neuen Instanz einer eigenen Initialisierungsklasse
übergeben:
Public Class MainWindow
Inherits System.Windows.Forms.Form
...
' Programmstart über Sub Main
Public Shared Sub Main()
' Initialisierung starten, Instanzen von Haupt-
' und Startfenster bereitstellen
Dim init As New MyPrograminitialization(New SplashScreen, New MainWindow)
End Sub
...
End Class
Mehr ist im Hauptfenster nicht zu tun. Die eigentliche
Initialisierung erfolgt in einer Methode einer selbst definierten
Klasse. Die Klasse wird von ProgramInitializationBase abgeleitet und
muss die Methode DoProgramInitialization sowie einen Konstruktor wie
folgt implementieren:
' Benutzerdefinierte Initialisierungsklasse wird von
' ProgramInitializationBase abgeleitet
Public Class MyPrograminitialization
Inherits ProgramInitializationBase
' Dieser Konstruktor muss implementiert werden
Public Sub New(ByVal splashscreen As Form, ByVal mainwindow As Form)
MyBase.New(splashscreen, mainwindow)
End Sub
' Benutzerdefinierte Initialisierung
Protected Overrides Sub DoProgramInitialization()
' Hier kann beliebiger Code für die Initialisierung stehen
' ACHTUNG!!!
' In dieser Methode darf nicht auf Fenster oder deren Controls
' zugegriffen werden, ohne Control.Invoke zu verwenden!
Dim i As Integer
For i = 0 To 100
' Startfenster über aktuellen Fortschritt informieren
SetProgress(i)
Debug.WriteLine("Initialisierung: " & i.ToString() & "%")
' Initialisierungsphase
' Thread.Sleep ist nur ein Dummy!
System.Threading.Thread.Sleep(50)
Next
End Sub
End Class
Der Konstruktor nimmt die Referenzen der beiden Fenster-Objekte
entgegen und delegiert alles weitere an den Konstruktor der
Basisklasse. Die zu überschreibende Methode DoProgramInitialization
wird in einem eigenen Thread gestartet. Sie darf alles tun, außer
auf die Steuerelemente zuzugreifen. Um den Fortschritt zu melden,
stellt die Basisklasse die Methode SetProgress zur Verfügung. Diese
kann genutzt werden, um threadsicher das Startfenster über den
Fortschritt zu informieren.
Das Startfenster muss seinerseits das Interface IProgressCallback
implementieren, um den Fortschritt anzuzeigen. Die Implementierung
kann ganz simpel mithilfe eines Progressbar-Controls erfolgen:
Public Class SplashScreen
Inherits System.Windows.Forms.Form
' Die Startfensterklasse muss IProgressCallback implementieren
Implements IProgressCallback
...
' Implementierung von IProgressCallback
Public Sub ProgressCallback(ByVal progress As Integer) _
Implements IProgressCallback.ProgressCallback
' Anzeigen des Fortschritts
ProgressBar1.Value = progress
End Sub
End Class
Funktionsweise hinter den Kulissen
Die eigentliche Arbeit verrichtet die Klasse
ProgramInitializationBase, die für die eigene Anwendung nicht weiter
angepasst werden muss. Sie ist die abstrakte Basisklasse für die
oben beschriebene Klasse, in der die Initialisierungsmethode
implementiert werden soll. Einige Referenzvariablen erlauben den
Zugriff auf die beteiligten Objekte:
Public MustInherit Class ProgramInitializationBase
' Referenzen der beiden Form-Objekte
Private Splashscreen As Form
Private Mainwindow As Form
' Der ApplicationContext zum Umschalten der Fenster
Public Shared AppContext As ApplicationContext
' Rahmen für die Initialisierungsmethode
Protected MustOverride Sub DoProgramInitialization()
...
End Class
Der Konstruktor nimmt die Referenzen der beiden Fensterobjekte
entgegen und speichert diese in den entsprechenden Feldern. Um zu
verhindern, dass der Aufruf von ProgressCallback im Startfenster
erfolgt, bevor dieses vollständig angezeigt wird, erfolgt der Start
der Initialisierungsmethode erst nach Erstellen des Fenster-Handles.
Hierzu wird ein Handler für das Ereignis HandleCreated angelegt und
angebunden.
Application.Run wird nun nicht eine Referenz eines
Fensterobjektes, sondern eines ApplicationContext-Objektes
übergeben. Über dieses kann später das Hauptfenster der Anwendung
umgeschaltet werden. Als Initialwert wird die Referenz des
Startfensters übergeben.
' Der Konstruktor startet mit dem Splashscreen
Public Sub New(ByVal splashscreen As Form, ByVal mainwindow As Form)
' Referenzen speichern
Me.Splashscreen = splashscreen
Me.Mainwindow = mainwindow
' Initialisierungsroutine wird erst gestartet,
' wenn das Splashscreen-Fenster aufgebaut worden ist
AddHandler splashscreen.HandleCreated, AddressOf SplashScreen_HandleCreated
' Start mit Splashscreen-Fenster, Kontext merken
AppContext = New ApplicationContext(splashscreen)
Application.Run(AppContext)
End Sub
Das Programm startet und zeigt das Startfenster an. Dann wird der
Handler für HandleCreated ausgeführt:
' Das Splashscreen-Fenster ist jetzt aufgebaut und kann benutzt werden
Private Sub SplashScreen_HandleCreated(ByVal sender As Object, _
ByVal e As System.EventArgs)
' Hintergrundthread für die Initialisierung starten
Dim t As Thread = New Thread(AddressOf DoProgramInitializationStart)
t.Name = "Initialisierungsthread"
t.Start()
End Sub
Hier wird ein neuer Thread angelegt und gestartet. Dieser Thread
ruft zunächst die benutzerdefinierte Initialisierungsmethode auf und
initiiert anschließend das Umschalten zum Hauptfenster:
Private Sub DoProgramInitializationStart()
' Benutzerdefinierte Initialisierungsmethode aufrufen
DoProgramInitialization()
' Fensterumschaltun initiieren
Splashscreen.Invoke(New ThreadStart(AddressOf LoadMainform))
End Sub
Das Umschalten der Fenster geschieht über das AppContext-Objekt.
Das Hauptfenster wird über Show angezeigt und das Startfenster
wieder geschlossen. Die Initialisierung ist hiermit beendet und das
Hauptfenster wird angezeigt.
Public Sub LoadMainform()
' ApplicationContext umschalten, damit die Messageloop für das Hauptfenster
' weitergeführt wird
AppContext.MainForm = Mainwindow
' Hauptfenster anzeigen
Mainwindow.Show()
' Startfenster schließen
Splashscreen.Close()
End Sub
Bleibt noch die Realisierung der Methode SetProgress. Sie
prüft zunächst, ob die Startfensterklasse auch wirklich die
Schnittstelle IProgressCallback implementiert und ruft dann
die Interface-Methode ProgressCallback auf. Der Aufruf muss
threadsicher für den GUI-Thread erfolgen, also über
Splashscreen.Invoke. Hierfür wird ein Delegate benötigt (ProgressCallBackDelegate).
Die Ausführung der Methode ProgressCallback im Startfenster
erfolgt dann vom GUI-Thread, der auch das Fenster angelegt hat und
die Messageloop abarbeitet.
Das Interface, das vom Startfenster implementiert werden muss,
sieht so aus:
Public Interface IProgressCallback
Sub ProgressCallback(ByVal progress As Integer)
End Interface
Bei Bedarf kann ProgressCallback durch weitere Parameter ergänzt werden, um
zusätzliche Informationen weiterzugeben.
|