Vb.Net beglückt uns ja standardmäßig mit dem "AnwendungsFramework". Wenn aktiviert, kann man in den Projekteinstellungen das MainForm festlegen, oder dass die Settings nicht abspeichern, oder einen SplashScreen (auf dem man aber keine wechselnden Nachrichten anzeigen kann), oder man kann die Anwendung als Einzel-Anwendung festlegen - derlei Sachen.
Größter Nachteil des AFW ist, dass -wenn aktiv - der Debugger Exceptions nicht richtig debuggt, im Falle sie auftreten, bevor das Form anzeigt (also bis zum
Man kann das AFW aber auch deaktivieren:
. . .
Dann muss man aber ein Extra-Modul coden, mit einer
Und darin muss man alles coden, was bislang das AFW für uns getan hat - was recht betrachtet nicht sonderlich viel ist:
(Das reicht im Normalfall - nun folgen "Extras")
Login mit Auswahl des MainForms
Hat man auf diese Weise selbst die Kontrolle über den Anwendungs-Startup übernommen, tun sich einem weitere Möglichkeiten auf.
Etwa das Vorschalten eines AnmeldeDialogs ist nun viel intuitiver zu implementieren, und - was mit AFW überhaupt nicht ginge - die Anmeldung kann nun sogar darüber entscheiden, welches Form überhaupt MainForm der Anwendung wird!
Hier gilt die Messagebox als Anmelde-Dialog, und
(Statt der MessageBox könnte es auch eine Passwort-Abfrage sein, und ein
(Will man das AFW belassen, muss man - weniger intuitiv - den Dialog ins
Splash-Screen, mit wechselnden Nachrichten
Splash-Screen wird bischen komplizierter, dafür ist das mit den Nachrichten sogar bischen einfacher als wie unter AFW. Nachrichten sind wichtig, denn ein Splash, der länger als 5 Sekunden lädt, sollte berichten, was grade gemacht wird.
Bei Cancel wird natürlich sofort beendet (#11), ansonsten wird das Ergebnis des "AnmeldeDialogs" erstmal in der
Dann wird ein Thread erstellt mit einer anonymen Methode, die den Splash erzeugt, in dessen Shown-Event das ManualResetEvent signalisiert wird (zeile#15) - damit der MainThread fortfahren kann. Danach den Splash mit .ShowDialog
Dieses alles wird mit höchster Thread-Priorität gestartet, und dann geht der MainThread selbst erstmal ins Koma (#20-22), mittels ManualResetEvent, aus dem er erst erwacht, wenn der NebenThread den Splash angezeigt hat (zur Erinnerung: zeile#15).
Bei Erwachen die Daten laden (und Meldungen darüber abschicken) (#24 - 29) - so viele es auch sein mögen, und dann erst wird die Selection ausgewertet, welches Form anzuzueigen ist (#32 o. 33) - dann noch den Splash im NebenThread schließen (#35), und dann glücklich das MainForm starten, mit:Uff!
(Hier zum Vergleich noch die AFW-Alternative: Splash-Screen mit Status-Meldungen)
(Einen habich noch): general ExceptionHandling mit bedingter Kompilierung
Wir können jetzt auch sehr schön dafür sorgen, dass in der Release-Version beim Absturz nur noch unsere eigenen Meldungen kommen, und keine Meldungen der Runtime, die dem User ja ebenso unverständlich sind, wie dem Entwickler wertvoll (mehr noch: Angreifer provozieren oft Fehler, um aus unkontrollierten Fehlermeldungen auf Schwachstellen zu schließen (dann muss aber das Logging aber erst recht geschützt werden)).
Ausserdem ists für Support-Zwecke wichtig, dass Abstürze von Releases geloggt werden. Mit bedingter Kompilierung kann man diese Fehlerbehandlung zur Entwicklungszeit abstellen, denn da wollen wir ja kein Logging, sondern Fehler sollen durch den VisualStudio-Debugger debugt werden.
Also die whole Story des Program-Moduls der angehängten Anwendung sieht so aus:
(Ich habe auch in
Größter Nachteil des AFW ist, dass -wenn aktiv - der Debugger Exceptions nicht richtig debuggt, im Falle sie auftreten, bevor das Form anzeigt (also bis zum
Form_Shown
-Event) (Klingt nach exotischen SonderFällen, ist aber bei anspruchsvollen Oberflächen, und/oder bei Laden nennenswerter Datenbestände vergleichsweise unexotisch (knowWhatIMean? )).Man kann das AFW aber auch deaktivieren:
. . .
Dann muss man aber ein Extra-Modul coden, mit einer
Shared Sub Main
, und die als StartObjekt festlegen.Und darin muss man alles coden, was bislang das AFW für uns getan hat - was recht betrachtet nicht sonderlich viel ist:
Login mit Auswahl des MainForms
Hat man auf diese Weise selbst die Kontrolle über den Anwendungs-Startup übernommen, tun sich einem weitere Möglichkeiten auf.
Etwa das Vorschalten eines AnmeldeDialogs ist nun viel intuitiver zu implementieren, und - was mit AFW überhaupt nicht ginge - die Anmeldung kann nun sogar darüber entscheiden, welches Form überhaupt MainForm der Anwendung wird!
VB.NET-Quellcode
- Public Module Program
- <STAThread> _
- Public Sub Main()
- Application.EnableVisualStyles()
- Application.SetCompatibleTextRenderingDefault(False)
- Dim frm As Form = Nothing
- Dim selected = MessageBox.Show("Möchten Sie frmYes als MainForm, frmNo, oder wollen Sie gleich Canceln?", "Anmeldung", MessageBoxButtons.YesNoCancel)
- Select Case selected
- Case DialogResult.Yes : frm = New frmYes
- Case DialogResult.No : frm = New frmNo
- Case DialogResult.Cancel : Return
- Case Else : Throw New ArgumentException("unknown User-Selection")
- End Select
- Application.Run(frm)
- My.Settings.Save()
- End Sub
- End Module
frmYes
/ frmNo
sind die beiden Optionen, mit welchem Form die Anwendung startet. Beachte auch die 3.Option: Cancel
- garnicht starten.(Statt der MessageBox könnte es auch eine Passwort-Abfrage sein, und ein
frmUser
, frmAdmin
würde starten - oder garnichts.)(Will man das AFW belassen, muss man - weniger intuitiv - den Dialog ins
Form_Load
des MainForms verlegen - und da hat man nur die Optionen: starten / canceln - nicht aber, das MainForm überhaupt erst zu bestimmen. Egal - gezeigt ists hier: Dialoge implementieren)Splash-Screen, mit wechselnden Nachrichten
Splash-Screen wird bischen komplizierter, dafür ist das mit den Nachrichten sogar bischen einfacher als wie unter AFW. Nachrichten sind wichtig, denn ein Splash, der länger als 5 Sekunden lädt, sollte berichten, was grade gemacht wird.
VB.NET-Quellcode
- Public Module Program
- <STAThread> _
- Public Sub Main()
- Dim frmSplash As frmSplash = Nothing
- Dim blocker As New ManualResetEventSlim
- Application.EnableVisualStyles()
- Application.SetCompatibleTextRenderingDefault(False)
- Dim frm As Form = Nothing
- Dim selected = MessageBox.Show("Möchten Sie (nach dem Splash) frmYes, frmNo - oder wollen Sie gleich canceln?", "Anmeldung", MessageBoxButtons.YesNoCancel)
- If selected = DialogResult.Cancel Then Return
- Dim th As New Thread(
- Sub()
- frmSplash = New frmSplash
- AddHandler frmSplash.Shown, Sub(s, e) blocker.Set() 'Mainthread freigeben
- frmSplash.ShowDialog()
- frmSplash.Dispose()
- blocker.Dispose()
- End Sub)
- th.Priority = ThreadPriority.Highest
- th.Start()
- blocker.Wait() ' hier Füsse still halten, bis der Splash angezeigt ist
- Dim message As Action(Of String) = Sub(s) frmSplash.Label1.Text = s
- frmSplash.BeginInvoke(message, String.Concat("Lade Daten für frm", selected))
- Thread.Sleep(1000)
- frmSplash.BeginInvoke(message, "lade immer noch (tue wenigstens so)")
- Thread.Sleep(1000)
- frmSplash.BeginInvoke(message, "gleich gehts los - ehrlich!")
- Thread.Sleep(1000)
- Select Case selected
- Case DialogResult.Yes : frm = New frmYes
- Case DialogResult.No : frm = New frmNo
- Case Else : Throw New ArgumentException("unknown User-Selection")
- End Select
- frmSplash.BeginInvoke(DirectCast(AddressOf frmSplash.Close, Action))
- Application.Run(frm)
- My.Settings.Save()
- End Sub
- End Module
selected
-Variablen gemerkt (#10).Dann wird ein Thread erstellt mit einer anonymen Methode, die den Splash erzeugt, in dessen Shown-Event das ManualResetEvent signalisiert wird (zeile#15) - damit der MainThread fortfahren kann. Danach den Splash mit .ShowDialog
anzeigen, und danach den Kram disposen (#16-#18).Dieses alles wird mit höchster Thread-Priorität gestartet, und dann geht der MainThread selbst erstmal ins Koma (#20-22), mittels ManualResetEvent, aus dem er erst erwacht, wenn der NebenThread den Splash angezeigt hat (zur Erinnerung: zeile#15).
Bei Erwachen die Daten laden (und Meldungen darüber abschicken) (#24 - 29) - so viele es auch sein mögen, und dann erst wird die Selection ausgewertet, welches Form anzuzueigen ist (#32 o. 33) - dann noch den Splash im NebenThread schließen (#35), und dann glücklich das MainForm starten, mit:Uff!
(Hier zum Vergleich noch die AFW-Alternative: Splash-Screen mit Status-Meldungen)
(Einen habich noch): general ExceptionHandling mit bedingter Kompilierung
Wir können jetzt auch sehr schön dafür sorgen, dass in der Release-Version beim Absturz nur noch unsere eigenen Meldungen kommen, und keine Meldungen der Runtime, die dem User ja ebenso unverständlich sind, wie dem Entwickler wertvoll (mehr noch: Angreifer provozieren oft Fehler, um aus unkontrollierten Fehlermeldungen auf Schwachstellen zu schließen (dann muss aber das Logging aber erst recht geschützt werden)).
Ausserdem ists für Support-Zwecke wichtig, dass Abstürze von Releases geloggt werden. Mit bedingter Kompilierung kann man diese Fehlerbehandlung zur Entwicklungszeit abstellen, denn da wollen wir ja kein Logging, sondern Fehler sollen durch den VisualStudio-Debugger debugt werden.
Also die whole Story des Program-Moduls der angehängten Anwendung sieht so aus:
VB.NET-Quellcode
- Imports System.Threading
- Public Module Program
- <STAThread> _
- Public Sub Main()
- Dim frmSplash As frmSplash = Nothing
- Dim blocker As New ManualResetEventSlim
- Application.EnableVisualStyles()
- Application.SetCompatibleTextRenderingDefault(False)
- #If CONFIG = "Release" Then
- AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf AppDomain_Exception
- #End If
- Dim frm As Form = Nothing
- Dim selected = MessageBox.Show("Möchten Sie (nach dem Splash) frmYes, frmNo oder wollen Sie gleich Canceln?", "Anmeldung", MessageBoxButtons.YesNoCancel)
- If selected = DialogResult.Cancel Then Return
- Dim th As New Thread(
- Sub()
- frmSplash = New frmSplash
- AddHandler frmSplash.Shown, Sub(s, e) blocker.Set() 'Mainthread freigeben
- frmSplash.ShowDialog()
- frmSplash.Dispose()
- blocker.Dispose()
- End Sub)
- th.Priority = ThreadPriority.Highest
- th.Start()
- blocker.Wait() ' solange der Splashscreen in seim Thread mit höchster Priorität gestartet wird, hier die Füsse still halten
- Dim message As Action(Of String) = Sub(s) frmSplash.Label1.Text = s
- frmSplash.BeginInvoke(message, String.Concat("Lade Daten für frm", selected))
- Thread.Sleep(1000)
- frmSplash.BeginInvoke(message, "lade immer noch (tue wenigstens so)")
- Thread.Sleep(1000)
- frmSplash.BeginInvoke(message, "gleich gehts los - ehrlich!")
- Thread.Sleep(1000)
- Select Case selected
- Case DialogResult.Yes : frm = New frmYes
- Case DialogResult.No : frm = New frmNo
- Case Else : Throw New ArgumentException("unknown User-Selection")
- End Select
- frmSplash.BeginInvoke(DirectCast(AddressOf frmSplash.Close, Action))
- Application.Run(frm)
- My.Settings.Save()
- End Sub
- Private Sub AppDomain_Exception(sender As Object, e As UnhandledExceptionEventArgs)
- MessageBox.Show("Hier wäre die Exception zu loggen, und dem User eine Mitteilung zu machen, ohne Implementations-Details zu verraten", "Fehler aufgetreten")
- End
- End Sub
- End Module
frmYes
einen Fehler eingebaut, dass man sich überzeugen kann, dass er in Debug-Version debugt wird (im Startup!!), als Release aber abgefangen.) Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()