Splashscreen vorzeitig schließen / Async-Dialog anzeigen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 18 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Splashscreen vorzeitig schließen / Async-Dialog anzeigen

    Hallo zusammen.
    Je nach Leitungsgeschwindigkeit bei uns auf der Firma kann es gut 1-2 Minuten dauern, bis meine Anwendung gestartet ist (es werden jede Menge Daten aus der DB gezogen etc.)
    Jetzt hab' ich eben einen Splashscreen (einfache Form mit Progressbar auf Marquee) in den Projekteigenschaften als "Begrüßungsbildschirm" hinterlegt.

    Nun ist es so, dass vor dem Anzeigen meiner frmMain ein Login-Dialog aufgerufen wird, der Splashscreen beendet sich aber erst, wenn die frmMain angezeigt ist.
    Jetzt würde ich kurz vor Anzeige des Login-Dialogs den Splashscreen quittieren wollen, aber mit My.Application.SplashScreen.Close() geht's nicht, er meckert dann
    wegen thread-übergreifend etc.

    Kann ich mir den Thread des Splashscreens abgreifen und dann an richtiger Stelle schließen oder gibt's nochwas einfacheres?


    Edit: Hab's dann doch in der letzten Ecke von Google gefunden:

    VB.NET-Quellcode

    1. If My.Application.SplashScreen.InvokeRequired Then
    2. 'Invoke the Close method of the splash screen on the thread that owns it.
    3. My.Application.SplashScreen.Invoke(New MethodInvoker(AddressOf My.Application.SplashScreen.Close))
    4. Else
    5. 'Close the splash screen.
    6. My.Application.SplashScreen.Close()
    7. End If





    Ich hätte aber noch was anderes:
    In meinem Haupt-Thread läuft ja derzeit alles ab, ich würde dem User gerne einen Busy-Dialog präsentieren (mache ich aktuell so:

    VB.NET-Quellcode

    1. Using dlg As New dlgIsBusy
    2. dlg.Text = "Laden..."
    3. dlg.lblDesc.Text = "Urlaubsdaten werden geladen"
    4. dlg.Show()
    5. 'hier dann der Code, der ausgeführt werden soll
    6. End Using


    allerdings erscheint der Dialog nicht sauber, die Progressbar läuft auch nicht:


    Mittlerweile hab ich auf .NET-Framework 4.5 umgestellt, gibt's eine einfach Methode, den Dialog in einem anderen Thread laufen zu lassen, damit der
    auch sauber angezeigt wird? Ich hab in die Richtung bisher noch garnix gemacht von daher fehlt mir da jegliches Wissen. :(
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „tragl“ ()

    @tragl Dann musst Du dieses Close() in den Dialog-Thread invoken.
    Auch wenn Du eine separate Dialog-Task erstellst, läuft die in einem anderen Thread, so dass Du auch da invoken musst.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Moin. Jo, das hab ich letzten Endes auch so gemacht. Also auf meiner frmMain gibt's jetzt diese Zeile direkt vor dem Login-Dialog:
    My.Application.SplashScreen.Invoke(New MethodInvoker(AddressOf My.Application.SplashScreen.Close))

    Damit schließt sich der SplashScreen. Aber was ist mit den Busy-Dialogen - wie bekomme ich die Dialoge in den Nebenthread?
    So wie es mit Async/Await angedacht ist kann ich das ja schlecht lösen, weil's oft um Datenaufbereitung geht und das sollte ja über
    den Hauptthread laufen. Ich kann also nicht den Dialog öffnen und mit Await arbeiten, bis der Rest durchgeackert ist .
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    tragl schrieb:

    wie bekomme ich die Dialoge in den Nebenthread?
    Hat doch der @ErfinderDesRades zelebriert:
    Async/Await: modaler IsBusy-Dialog, bis Nebenläufigkeit abgeschlossen
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Hat doch der @ErfinderDesRades zelebriert:

    Das is aber genau das, was ich nicht machen kann. Bei mir soll der Dialog im Nebenthread laufen, nicht der restliche Code. Ich bräuchte den Dialog wegen mir in nem Using-Block, der im Nebenthread läuft
    also in etwa sowas:

    VB.NET-Quellcode

    1. Using dlg As New dlgIsBusy -> aber im Nebenthread
    2. dlg.Text = "Laden..."
    3. dlg.lblDesc.Text = "Urlaubsdaten werden geladen"
    4. dlg.Show()
    5. 'hier dann der Code, der im Hauptthread ausgeführt werden soll
    6. End Using


    bzw. wird wohl ohne Using laufen müssen, weil man ja invoken muss:

    VB.NET-Quellcode

    1. Dim dlg As New dlgIsBusy
    2. dlg.Text = "Laden..."
    3. dlg.lblDesc.Text = "Urlaubsdaten werden geladen"
    4. dlg.Show() -> im Nebenthread
    5. 'hier dann der Code, der ausgeführt werden soll
    6. dlg.invoke(Address Of dlg.Close())
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Gegenfrage: Warum dürfen die Daten nicht im Nebenthread geladen werden? Das ist doch eine der gedachten Hauptaufgaben von Nebenthreads.
    Berechnungen etc. sind ja problemlos möglich. Nur die Anzeige der Daten muss ja im Hauptthread erfolgen.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    VaporiZed schrieb:

    Nur die Anzeige der Daten muss ja im Hauptthread erfolgen.

    jo, darum geht's - ist ein MischMasch von Beidem.
    In dem Beispiel wird ein DGV manuell befüllt, heißt laden der Daten aus der DB / aus dem DataSet und darstellen im DGV. Im Nebenthread passiert da garnix,
    hatte ich bereits probiert. Deshalb: Dialog in den Nebenthread, bis der Rest abgeschlossen ist, dann über Invoke schließen wäre jetzt mein Weg. Nur wie
    bekomme ich den Dialog in den Nebenthread?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Wie impliziert: Das ist der falsche Weg. Die Daten zu laden ist eine Sache. Ich habe in den vergangenen Tagen an einem privaten Projekt gearbeitet, bei dem es auch um viele Daten ging. Das laden dauert (etwas), die Anzeige derselbigen geht schnell. Man muss nur erstmal das DGV von seiner BindingSource trennen (oder abhängig vom Formaufbau die BindingSource von ihrer Datenquelle), dann die Daten nebenläufig laden und danach im Hauptthread alles wieder ans GUI anschließen.
    Anders ausgedrückt: Ich werde mir keine Gesanken zu der von Dir gesuchten Problemlösung machen, weil ich diesen Weg für Pfeil-Rücken-Brust-Auge halte.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    tragl schrieb:

    Bei mir soll der Dialog im Nebenthread laufen
    Ja und?
    Den Dialog startest Du in einem Nebenthread und die Daten verarbeitest Du in dem Thread, der für die Datenverarbeitung zuständig ist.
    Der Dialog weiß doch nichts von Deinen Daten.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    hmh, ok - da muss ich mich dann mal mit auseinandersetzen.
    Ein anderes Scenario wäre auch ein Dialog mit einer Richtextbox, welche sich während bestimmten Operationen mit "Logs" füllt - das soll ja
    dann auch async zum Rest laufen...

    mal gucken, wie ich das veranstalte.

    RodFromGermany schrieb:

    Den Dialog startest Du in einem Nebenthread

    genau das möchte ich ja wissen, wie das funzt ;)
    Sowas hier?:

    VB.NET-Quellcode

    1. Dim dlg As New dlgIsBusy
    2. dlg.begininvoke(AddressOf dlg.show)
    3. 'Hauptsachen abarbeiten
    4. dlg.invoke(AdressOf dlg.close)

    ?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Sowas suchte ich, danke schonmal - werde ich heute abend bestimmt direkt testen ;)
    Edit: Funzt. Top Sache!
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „tragl“ ()

    Sooo, hab nu alle IsBusy-Dialoge umgebaut und läuft.
    Nun hängt's an den Dialogen mit der Richtextbox.

    Also: Einfacher Dialog, darauf ist ne Richtextbox.
    Hab ich immer so "befüllt":

    VB.NET-Quellcode

    1. Private Sub TestToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles TestToolStripMenuItem.Click
    2. Using dlg As New dlgRtxt
    3. Dim rtb = dlg.RichTextBox1
    4. dlg.show()
    5. For i = 1 To 10000000
    6. addRtxt(rtb, $"Test{i}")
    7. Next
    8. End Using
    9. End Sub


    Nu wollt ich's so probieren - geht nicht, es wäre noch kein Handle erstellt... selbst wenn ich z.b. bei Zeile ein Dim useless = Me.Handle eintrage, geht's nicht.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.CompilerServices
    2. Imports System.Diagnostics
    3. Imports System.ComponentModel
    4. Namespace Global.System.Threading
    5. Public Module ThreadingT
    6. Public Class WaitDlg : Implements IDisposable
    7. Private _waitSplash As Form
    8. Public Sub New(dlg As Form)
    9. _waitSplash = dlg
    10. Dim thr As New Thread(New ThreadStart(AddressOf workerThread))
    11. With thr
    12. .IsBackground = True
    13. .SetApartmentState(ApartmentState.STA)
    14. .Start()
    15. End With
    16. End Sub
    17. Public Sub Dispose() Implements IDisposable.Dispose
    18. _waitSplash.Invoke(New MethodInvoker(AddressOf stopThread))
    19. End Sub
    20. Private Sub stopThread()
    21. _waitSplash.Close()
    22. End Sub
    23. Private Sub workerThread()
    24. With _waitSplash
    25. .TopMost = True
    26. End With
    27. Application.Run(_waitSplash)
    28. End Sub
    29. End Class
    30. End Module
    31. End Namespace

    VB.NET-Quellcode

    1. Private Sub TestToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles TestToolStripMenuItem.Click
    2. Dim test1 As New dlgRtxt
    3. Using New Threading.WaitDlg(test1)
    4. For i = 1 To 10000000
    5. test1.Invoke(New MethodInvoker(Sub() addRtxt(test1.RichTextBox1, $"Test{i}")))
    6. Next
    7. End Using
    8. End Sub


    Irgendwelche Ideen, wie ich Controls verändern kann, die in nem Thread laufen?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Ne, der SplashScreen funzt nu so wie er soll. Mein IsBusyDialog auch.
    Jetzt geht's um eine einfachen Dialog mit RichTextBox drauf. Der Dialog soll
    im Nebenthread laufen (damit er sauber angezeigt wird), die RichTextBox aber mit Informationen aus dem Haupthread gefüllt werden, während der Laufzeit.
    Am Ende ein Ergebnis mit RichTextBox1.Lines = Array anzeigen ist ja kein Problem.

    Ich geh' mal davon aus, dass ich auch da invoken muss um die Strings rüberzureichen aber wie?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    wie gesagt: Der dort angegebene SplashScreen hat das Potential, diese Anforderungen zu erfüllen.
    Ja - da wird auch Invoked.

    Der dortige Splash ist glaub besonders flexibel. Also er kann in jeden langdauernden Vorgang eingefrickelt werden - im Sample ist er halt in einen Startup-Vorgang eingefrickelt.

    Diese bischen unübersichtliche Einfrickelei rührt auch daher, dass die Vorgänge eben gleichzeitig ablaufen.
    Da muss man den "Splash" (der dann ja strenggenommen keiner mehr ist) erstmal vorbereiten, dann einen Thread bauen, wo er anzeigt, dann den Thread starten, den Mainithread solange blockieren, bis der Splash anzeigt, dann MainThread entblocken, und im weiteren gelegentlich invokete Meldungen übermitteln.
    Zuletzt invoken, dass der Splash geschlossen wird.

    Also diese Anforderung wird erfüllt:
    im Nebenthread laufen (damit er sauber angezeigt wird), die RichTextBox aber mit Informationen aus dem Haupthread gefüllt werden, während der Laufzeit.

    Diese Anfroderung hingegen steht der vorherigen bischen entgegen.
    Oder wäre zumindest eine sehr originelle neue Erfindung (die du da mal soeben in einem Nebensatz fallen lässt):
    Am Ende ein Ergebnis mit RichTextBox1.Lines = Array anzeigen ist ja kein Problem.
    In meiner Welt hat ein Splash kein Ergebnis, was am Ende iwie abzuliefern wäre.
    Aber möglich ist das - müsste man halt einen dafür geeigneten Delegaten verwenden (Func(Of String()) oder sowas)

    Obwohl - es passt auch nicht auf deine Anforderung
    Am Ende ... RichTextBox1.Lines = Array
    Von welcher Richtextbox ist da die Rede? Auffm Splash kann sie nicht sein, weil der ist "am Ende" ja weg - oder soll der nun auf einmal auch noch stehen bleiben?

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    Von welcher Richtextbox ist da die Rede? Auffm Splash kann sie nicht sein, weil der ist "am Ende" ja weg - oder soll der nun auf einmal auch noch stehen bleiben?


    Jo, das war vielleicht ein bisschen unverständlich. Auf dem "Splash" soll während der Operation was angezeigt werden, damit der User sieht dass da was passiert und ggf. auch was da gerade passiert.
    Da sich der Splash (und auch vorher in meinem normalen Using-Block) nach Beendigung der Operationen schließt, hab ich die Ausgaben der Richtextbox zeitgleich in einer List(Of String) gespeichert und lasse
    den Dialog nach Abschluss der Operationen mit RichTextBox1.Lines = List(Of String) über ShowDialog() anstatt Show() anzeigen - als Ergebnisansicht sozusagen.

    Ich probier' die Tage mal rum.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    So, hab's wie folgt schonmal ans Laufen gebracht:

    VB.NET-Quellcode

    1. Private Sub TestToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles TestToolStripMenuItem.Click
    2. Dim blocker As New Threading.ManualResetEventSlim
    3. Dim dlgLog As New dlgRtxt
    4. Dim lines As New List(Of String)
    5. Dim th As New Threading.Thread(Sub()
    6. AddHandler dlgLog.Shown, Sub(s, ea) blocker.Set()
    7. dlgLog.ShowDialog()
    8. dlgLog.Dispose()
    9. blocker.Dispose()
    10. End Sub)
    11. th.Priority = Threading.ThreadPriority.Highest
    12. th.Start()
    13. blocker.Wait()
    14. dlgLog.Invoke(Sub() dlgLog.Text = "Test-Threading")
    15. For i = 1 To 1000
    16. Dim x = i
    17. dlgLog.Invoke(Sub() addRtxt(dlgLog.RichTextBox1, x.ToString))
    18. lines.Add(x.ToString)
    19. Next
    20. dlgLog.Invoke(DirectCast(AddressOf dlgLog.Close, Action))
    21. Using dlg As New dlgRtxt
    22. dlg.RichTextBox1.Lines = lines.ToArray
    23. dlg.ShowDialog()
    24. End Using
    25. End Sub


    Jetzt am besten noch 'ne Klasse draus bauen, wär ja einfacher denk ich. Nur wie? Ich bräuchte ja das entspr. Form als Rückgabe, damit da korrekt invoken kann.
    Könnte man zumindest den obigen Code teilweise auslagern und somit verkürzen?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „tragl“ ()

    Ich muss grad 5000 Wohnwagen erzeugen:

    VB.NET-Quellcode

    1. Private Sub btTest_Click(sender As Object, e As EventArgs) Handles btTest.Click
    2. Dim dlg = CreateAsyncForm(Of frmNotify)()
    3. Dim notify As Action(Of String) = Sub(s) dlg.ListBox1.Items.Add(s)
    4. dlg.BeginInvoke(notify, "Noch 5000 Wohnwagens")
    5. For i = 0 To 9
    6. LadeWohnwagens()
    7. Dim wait = Rnd.Next(1, 3)
    8. Threading.Thread.Sleep(wait * 500)
    9. dlg.BeginInvoke(notify, $"Noch {5000 - _Wohnwagens.Count} Wohnwagens")
    10. Next
    11. dlg.BeginInvoke(DirectCast(AddressOf dlg.Close, Action))
    12. End Sub
    13. Private _Wohnwagens As New List(Of Wohnwagen)
    14. Private Sub LadeWohnwagens()
    15. For i = 0 To 499
    16. Dim ww = New Wohnwagen With {.Hersteller = "Hersteller" & i, .Jahr = "Jahr" & i, .Model = "Model" & i,
    17. .Typ = "Typ" & i, .Wert1 = "Wert1" & i, .Wert2 = "Wert2" & i, .Wert3 = "Wert3" & i, .Wert4 = "Wert4" & i,
    18. .Wert5 = "Wert5" & i, .Wert6 = "Wert6" & i, .Wert7 = "Wert7" & i}
    19. _Wohnwagens.Add(ww)
    20. Next
    21. End Sub
    22. Public Shared Function CreateAsyncForm(Of T As {Form, New})() As T
    23. ' returnt ein Form des angegebenen Typs T, im Nebenthread erzeugt und geöffnet
    24. Dim blocker As New Threading.ManualResetEventSlim
    25. Dim dlgLog As New T
    26. Dim th As New Threading.Thread(Sub()
    27. AddHandler dlgLog.Shown, Sub(s, ea) blocker.Set()
    28. dlgLog.ShowDialog()
    29. dlgLog.Dispose()
    30. blocker.Dispose()
    31. End Sub)
    32. th.Priority = Threading.ThreadPriority.Highest
    33. th.Start()
    34. blocker.Wait()
    35. Return dlgLog
    36. End Function
    frmNotify hat eine Listbox, wie du erahnen kannst


    tragl schrieb:

    VB.NET-Quellcode

    1. For i = 1 To 1000
    2. Dim x = i
    3. dlgLog.Invoke(Sub() addRtxt(dlgLog.RichTextBox1, x.ToString))
    4. lines.Add(x.ToString)
    5. Next
    keine gute Idee, jeden einzelnen Datensatz per Invoke iwo hinzuschicken. Invoking ist teuer und langsam.
    Sieh zu, dass das Notify-Form nicht öfter als alle halbe Sekunde beansprucht wird - der Intervall darf gerne auch bis 5s gehen.
    Und Invoke ist eh Mist, weil da wartet der MainThread auf den NebenThread - also Ausbremse im Quadrat. Nimm .BeginInvoke (hat ja einen Grund, dassich das so vorgemacht hab - ich weiss nicht, warum die Leuts immer Invoken wollen statt BeginInvoken)

    Ups - vergessen:

    VB.NET-Quellcode

    1. Public Class Wohnwagen
    2. Public Property Hersteller As String
    3. Public Property Typ As String
    4. Public Property Model As String
    5. Public Property Jahr As String
    6. Public Property Wert1 As String
    7. Public Property Wert2 As String
    8. Public Property Wert3 As String
    9. Public Property Wert4 As String
    10. Public Property Wert5 As String
    11. Public Property Wert6 As String
    12. Public Property Wert7 As String
    13. End Class

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „ErfinderDesRades“ ()