Progressbar aktualisiert nicht

  • VB.NET

Es gibt 5 Antworten in diesem Thema. Der letzte Beitrag () ist von markus.obi.

    Progressbar aktualisiert nicht

    Hi,

    ich habe ein kleines Programm geschrieben, dass bei allen Dateien mit Extension ".png" die Endung weghaut.
    Soweit sogut. Da es sich aber öfters um 10.000 - 20.000 Dateien handelt, wollte ich eine Progressbar einbauen, die den Fortschritt anzeigt.
    Zuerst zähle ich in einer extra Schleife alle Dateien (GetTotalCount). Die Sub SearchAndRename durchsucht rekursiv alle Ordner und bennent Dateien um.
    Ich "raise" nach jedem Umbenennen ein Event, welches den Wert der Progressbar und eine Textbox ändert.
    Wenn die Sub SearchAndRename fertig ist, dann springt die Progressbar auf 100%. Das Problem besteht auch, wenn ich statt dem Event die Sub direkt ausführe.
    Wie bekomm ich den Übergang hin? Brauche ich dafür Multithreading?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.IO
    2. Imports System.Text.RegularExpressions
    3. Imports System.Threading
    4. Public Class Form1
    5. Private Event RefreshUI()
    6. Private Property index As Int64 = 0
    7. Private Property total As Int64 = 0
    8. Private Sub GetTotalCount(path As String, lastdir As Boolean)
    9. Dim dirinfo As New DirectoryInfo(path)
    10. total += dirinfo.GetFiles("*.png").Length
    11. Dim dirfound As Boolean = False
    12. Dim Dirs() As DirectoryInfo = dirinfo.GetDirectories.ToArray
    13. dirfound = Dirs.Length > 0
    14. For i = 0 To Dirs.Length - 1
    15. GetTotalCount(Dirs(i).FullName, i = Dirs.Length - 1 And lastdir)
    16. Next
    17. If lastdir And Not dirfound Then
    18. tbTotal.Text = total.ToString
    19. pbRenamed.Minimum = 0
    20. pbRenamed.Maximum = CInt(total)
    21. pbRenamed.Value = 0
    22. End If
    23. End Sub
    24. Private Sub SearchAndRename(path As String, lastdir As Boolean)
    25. Dim dirinfo As New DirectoryInfo(path)
    26. For Each File As FileInfo In dirinfo.GetFiles("*.png")
    27. Dim NewFullPath As String = Regex.Replace(File.FullName, "\.png$", "")
    28. File.MoveTo(NewFullPath) 'Benennt eine Datei um
    29. RaiseEvent RefreshUI()
    30. Next
    31. Dim dirfound As Boolean = False
    32. Dim Dirs() As DirectoryInfo = dirinfo.GetDirectories.ToArray
    33. For i = 0 To Dirs.Length - 1
    34. dirfound = True
    35. SearchAndRename(Dirs(i).FullName, i = Dirs.Length - 1 And lastdir)
    36. Next
    37. If lastdir And Not dirfound Then
    38. System.Media.SystemSounds.Asterisk.Play()
    39. total = 0
    40. index = 0
    41. End If
    42. End Sub
    43. Private Sub RefreshMe() Handles Me.RefreshUI
    44. index += 1
    45. tbIndex.Text = index.ToString
    46. pbRenamed.Value = CInt(total)
    47. End Sub
    48. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles btnRename.Click
    49. index = 0
    50. total = 0
    51. tbIndex.Text = index.ToString
    52. tbTotal.Text = "Unbekannt"
    53. DirectCast(sender, Button).Enabled = False
    54. Dim sw As New Stopwatch
    55. sw.Start()
    56. GetTotalCount(Application.StartupPath, True) 'Zählt alle Dateien
    57. SearchAndRename(Application.StartupPath, True) 'Bennent alle Dateien um
    58. MsgBox(sw.ElapsedMilliseconds / 1000)
    59. DirectCast(sender, Button).Enabled = True
    60. End Sub
    61. End Class


    mfg Markus
    Dafür eignet sich ein BackGroundWorker sehr gut.
    Einen Thread mit "New Thread(AddressOf Bla)" zu erstellen funktioniert auch, aber der BackGroundWorker bietet komfortable Methoden die Werte wieder in den GUI-Thread zu bringen (Besonders hilfreich, wenn man mit Invoke nicht vertraut ist).

    Multithreading ist nötig, wenn Du nicht willst, dass sich die Form aufhängt.

    Ein Tipp noch zum Programm:
    Wie wäre es, wenn Du nicht zuerst zählst und dann nochmal die Dateien suchst, sondern zuerst die Dateien suchst, die gefundenen Pfade in eine Liste packst und diese im BGW abarbeitest? Dann musst Du die Rekursion nur einmal laufen lassen (Und Liste.Count gibt die Anzahl der Dateien zurück, also das Maximum für die ProgressBar).
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Der Tipp mit dem Backgroundworker ist gut. Das Tutorial von dir ist leicht verständlich.
    Mit Invoke hatte ich schon einmal bei asynchronen HttpWebRequests zu tun. (ThreadPool.RegisterWaitForSingleObject)

    Habe noch 2 Frage:

    1. In der Sub BGW_DoWork hat die Variable ".IsBusy" immer den Wert True.
    Die If Abfrage habe ich deswegen entfernt:

    VB.NET-Quellcode

    1. If Not BGW.IsBusy Then
    2. Label_Result.Text = "Bitte warten..."
    3. ProgressBar_Percentage.Maximum = CInt(NumericUpDown_Count.Value)
    4. ProgressBar_Percentage.Value = 0
    5. BGW.RunWorkerAsync(CInt(NumericUpDown_Count.Value))
    6. End If


    Wieso ist .IsBusy immer True?

    2. In der Sub BGW_RunWorkerCompleted sind BGW.CancellationPending und e.Cancelled gleich False.
    Muss ich also schon in Der BGW_Work Sub auf das Cancel reagieren?
    Das betrifft diesen Code:

    VB.NET-Quellcode

    1. If e.Cancelled Then
    2. Label_Result.Text = "Vorgang abgebrochen"
    3. Else
    4. Label_Result.Text = "Vorgang abgeschlossen")
    5. End If


    Oder gibts da nen Fix?

    markus.obi schrieb:

    Wieso ist .IsBusy immer True?

    Weil du es benutzt, um von "innen" nach "außen" zu schauen.

    BGW.IsBusy = Code im BGW läuft noch (namentlich DoWork). Da du IN DoWork bist, ist es logisch, dass der BGW busy ist. Schaust du von "außen" (also einem anderen Thread) auf den BGW, ist er dann nicht "busy", wenn sein Completed Event durch ist.
    @picoflop: Danke :)
    Dieser Code:

    VB.NET-Quellcode

    1. If Not BGW.IsBusy Then
    2. Label_Result.Text = "Bitte warten..."
    3. ProgressBar_Percentage.Maximum = CInt(NumericUpDown_Count.Value)
    4. ProgressBar_Percentage.Value = 0
    5. BGW.RunWorkerAsync(CInt(NumericUpDown_Count.Value))
    6. End If

    Kommt nicht in die Sub DoWork(), sondern in die Sub Start().

    Zu zweitens:
    Diesen Bug hat bereits jemand gefunden.
    Im zweiten Post beim Abbrechen (da, wo was durchgestrichen ist) erwähne ich das.
    e.Cancelled wird nicht auf True gesetzt, obwohl der Vorgang durch BGW.CancelAsync() abgebrochen wurde.
    Auf MSDN steht, dass das aufgrund von Race-Conditions auftreten kann.
    Bereits in der Sub DoWork() auf BGW.CancellationPending zu reagieren (zusätzlich zum Verlassen der Schleife) ist eine gute Idee. Man müsste da noch überlegen, wie man das dem GUI-Thread mitteilt (hier müsste man dann eventuell doch auf Invoke() zurückgreifen).
    Darf ich die Idee (natürlich mit Namensnennung) ins Tutorial aufnehmen?
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils