fuechse-online  
Ingenieurbüro für Softwareentwicklung
Dr. Joachim Fuchs
  Börnhausener Berg 16
51674 Wiehl
Tel.: 02262 / 701 310
Home Beispiele Kontakt Impressum
 
 

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.

Download des Projektes

 

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.