Backgroundworker Report Progress wird nicht aufgerufen

  • VB.NET
  • .NET (FX) 4.0

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    Backgroundworker Report Progress wird nicht aufgerufen

    Hallo zusammen,

    habe leider noch ein kleines Problem.
    Wie in meinem anderen Thread bereits erwähnt schreibe ich ein Programm zum Umwandeln von Bildern.
    Wenn es viele Bilder sind, friert die GUI ein und es sieht aus wie abgestürzt.

    Gut also der Backgroundworker muss her.
    Ich bin kein Programmierer und habe mir alles durch lesen, try and error selber beigebracht bitte habt Nachsicht mit mir :)

    Also ich habe eine For Schleife für das Abarbeiten der Bilder aus einer Listbox.

    Wenn ich jetzt diese For Schleife in einen BGW einbaue, dann kann er nicht mehr auf das Listview Object zugreifen.

    VB.NET-Quellcode

    1. For i = 0 To ListView1.SelectedItems.Count - 1
    2. ordner_tpl = MetroLabel7.Text.Replace("aktiver TPL Ordner: ", "")
    3. Try
    4. If (ListView1.Items.Count = 0) Then Return 'ListView leer
    5. If (ListView1.SelectedItems.Count = 0) Then Return 'Kein Eintrag markiert
    6. If (ListView1.SelectedItems(0).SubItems.Count < 2) Then Return 'Keine oder zu wenige SubItems
    7. If manueller_Ordner_tpl = True Then
    8. Try
    9. PictureBox1.Image = Base64ToImage(System.IO.File.ReadAllText(ordner_tpl & "\" & ListView1.SelectedItems(i).SubItems(1).Text))
    10. Catch ex As Exception
    11. PictureBox1.Image = PictureBox1.ErrorImage
    12. End Try
    13. Else
    14. Try
    15. PictureBox1.Image = Base64ToImage(System.IO.File.ReadAllText(My.Settings.standardordner_tpl & "\" & ListView1.SelectedItems(i).SubItems(1).Text))
    16. Catch ex As Exception
    17. PictureBox1.Image = PictureBox1.ErrorImage
    18. End Try
    19. End If
    20. Catch ex As Exception
    21. MsgBox(ex.Message)
    22. End Try
    23. PictureBox1.Image.Save(ordner_für_png & "\" & ListView1.SelectedItems(i).SubItems(1).Text.Replace("tpl", "png"))
    24. BackgroundWorker1.ReportProgress(i)
    25. Next


    Das ist die For Schleife.

    Dann habe ich mir die Finger wund gesucht und versuchte es dann mit Invoke.
    Habe diese For Schleife in eine Sub gepackt und rufe nun diese Sub auf

    VB.NET-Quellcode

    1. Private Delegate Sub delegateumwandeln()
    2. Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    3. Me.Invoke(New delegateumwandeln(AddressOf Umwandeln_TPL_zu_Bild))
    4. End Sub


    Das funktioniert auch wunderbar.
    Leider wird die ProgressChanged Methode erst aufgerufen, wenn du komplette Sub fertig ist (alle Bilder abgearbeitet).

    Möchte aber ja, dass jeder Schleifendurchlauf reported wird.
    In der ProgressChanged Methode ist eine Progressbar.

    Was kann ich tun?
    oder
    Wie bekomme ich es hin, dass der BGW ohne invoke auf die Listview zugreifen kann?

    Vielen Dank schon mal im Voraus.

    PS. Das i von

    VB.NET-Quellcode

    1. ​BackgroundWorker1.ReportProgress(i)
    zählt auch hoch, wie es sein soll.

    LG

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „dj alex z“ ()

    Der BGW hat ne Property .WorkerReportsProgress, setz die (im Designer) auf True:

    VB.NET-Quellcode

    1. Me.BackgroundWorker1.WorkerReportsProgress = True

    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!
    Wie man den BGW verwenden sollte, hab ich hier mal erklärt:
    vb-paradise.de/allgemeines/tip…kgroundworker/?highlight=

    Deine DoWork-Methode macht nicht die eigentliche Arbeit, sondern ruft nur Me.Invoke auf. Dadurch findet der Aufruf an Umwandeln_TPL_zu_Bild (ungünstige Namenskonvention nebenbei) erst wieder im GUI-Thread statt.
    Die Methode muss ohne Invoke aufgerufen werden. Das ist ja Sinn des BGWs. Und natürlich darfst Du dann in der Methode nicht auf Controls wie z.B. ListView1 (ebenfalls ungünstige Namenskonvention) zugreifen, sondern die benötigten Daten musst Du dem BGW mitgeben. Wie das geht ist im oben verlinkten Tutorial beschrieben.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    dj alex z schrieb:

    nicht aufgerufen
    Setz da mal einen Haltepunkt rein.
    Was passiert?
    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!
    Vielen Dank für eure Antworten.

    @Niko Ortner
    das Tutorial habe ich mir schon durchgelesen.
    Ich habe aber ja in der Schleife stehen, dass er die markierten Einträge der Listview nacheinander abarbeiten soll.
    Ich verstehe nicht, wie ich dem BGW einen variablen Wert mitgeben kann, der sich verändert.
    Die Namenskonventionen werde ich noch ändern. Ist noch sehr früh das Projekt.

    @RodFromGermany
    Der Haltepunkt wird niemals erreicht.
    So könnte das aussehen:

    VB.NET-Quellcode

    1. 'In der Methode, die den BGW startet:
    2. BGW.RunWorkerAsync(ListView1.SelectedItems.Where(Function(i) i.IrgendwasTrifftZu).ToList)
    3. 'In der DoWork-Methode:
    4. Dim ListViewItems = DirectCast(e.Argument, List(Of ListViewItem)) 'Bin mir nicht sicher, ob das her wirklich ListViewItem ist. Musst Du nachgucken.
    5. For Each i In ListViewItems
    6. 'Irgendwas machen
    7. Next
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Bitte habt Nachsicht mit mir.
    Bin absoluter Anfänger.
    Also wenn ich das eins zu eins übernehme, dann bekomme ich das:

    VB.NET-Quellcode

    1. 'In der Methode, die den BGW startet:
    2. BGW.RunWorkerAsync(ListView1.SelectedItems.Where(Function(i) i.listview1.selecteditems.count).ToList)

    'Where' is not a member of 'ListView.SelectedListViewItemCollection'

    Also das "where" nimmt er garnicht, egal wie ich den Befehl schreibe.
    Ich muss auch zugeben, dass ich das mit where und dem (function(i) nicht verstehe.

    Wie gesagt, bitte habt Nachsicht mit mir.
    Ach, ok. ListViewItemsCollection implementiert nicht das generische IEnumerable-Interface.
    Dann halt von Hand:

    VB.NET-Quellcode

    1. Dim Items As New List(Of ListViewItem)
    2. For Index = 0 To ListView1.Items.Count - 1
    3. Dim Item = ListView1.Items(Index)
    4. If Item.IrgendwasTrifftZu Then
    5. Items.Add(Item)
    6. End If
    7. Next
    8. BGW.RunWorkerAsync(Items)

    Du hast da ja ein paar Abfragen, welche Items verarbeitet werden sollen. Die machst Du halt vorher. Alle Items, die verarbeitet werden sollen, gibst Du dem BGW mit.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    dj alex z schrieb:

    Der Haltepunkt wird niemals erreicht.
    Poste mal den kompletten Code.
    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!
    Vielen Dank Niko Ortner und RodFromGermany, aber ich möchte ja nicht das Item Adden sondern das markierte Item auslesen.
    Das funktioniert so:
    Prüfung ob im ListView ein Eintrag oder mehrere markiert sind.
    Wenn nur einer, dann öffne SaveFileDialog und speichere den eingegebenen Dateinamen.
    Wenn mehrere, dann öffne FolderBrowserDialog und speichere alle markierten Items nacheinander in diesem gewählten Ordner ab.

    hier der komplette Code wie er in einem Button Click Ereignis steht.
    Mein Ziel ist es aber, dass wenn mehrere markiert sind der BGW anspringt und mir eine ProgressBar füllt.

    VB.NET-Quellcode

    1. Private Sub Button10_Click_1(sender As Object, e As EventArgs) Handles Button10.Click
    2. If ListView1.SelectedItems.Count > 1 Then 'Prüfung ob mehr als ein Eintrag ausgewählt ist
    3. If Not PictureBox1.Image Is Nothing Then
    4. If fbd_mehrfach_bild.ShowDialog = System.Windows.Forms.DialogResult.OK Then
    5. ordner_für_png = fbd_mehrfach_bild.SelectedPath
    6. BackgroundWorker1.RunWorkerAsync()
    7. For i = 0 To ListView1.SelectedItems.Count - 1
    8. ordner_tpl = MetroLabel7.Text.Replace("aktiver TPL Ordner: ", "")
    9. Try
    10. If (ListView1.Items.Count = 0) Then Return 'ListView leer
    11. If (ListView1.SelectedItems.Count = 0) Then Return 'Kein Eintrag markiert
    12. If (ListView1.SelectedItems(0).SubItems.Count < 2) Then Return 'Keine oder zu wenige SubItems
    13. If manueller_Ordner_tpl = True Then
    14. Try
    15. PictureBox1.Image = Base64ToImage(System.IO.File.ReadAllText(ordner_tpl & "\" & ListView1.SelectedItems(i).SubItems(1).Text))
    16. Catch ex As Exception
    17. PictureBox1.Image = PictureBox1.ErrorImage
    18. End Try
    19. Else
    20. Try
    21. PictureBox1.Image = Base64ToImage(System.IO.File.ReadAllText(My.Settings.standardordner_tpl & "\" & ListView1.SelectedItems(i).SubItems(1).Text))
    22. Catch ex As Exception
    23. PictureBox1.Image = PictureBox1.ErrorImage
    24. End Try
    25. End If
    26. Catch ex As Exception
    27. MsgBox(ex.Message)
    28. End Try
    29. PictureBox1.Image.Save(ordner_für_png & "\" & ListView1.SelectedItems(i).SubItems(1).Text.Replace("tpl", "png"))
    30. Next
    31. Else
    32. MsgBox("ein Ordner zum Speichern muss ausgewählt werden")
    33. Exit Sub
    34. End If
    35. Else
    36. MsgBox("ein Ordner zum Speichern muss ausgewählt werden")
    37. Exit Sub
    38. End If
    39. Else 'nur ein Eintrag gewählt, SaveFileDialog öffnen und Datei speichern
    40. If Not PictureBox1.Image Is Nothing Then
    41. SaveFileDialog1.Filter = "PNG Dateien|*.png"
    42. If SaveFileDialog1.ShowDialog() = DialogResult.OK Then
    43. If Not PictureBox1.Image Is Nothing Then 'Sicherstellen, dass die Picturebox auch Inhalt hat
    44. PictureBox1.Image.Save(SaveFileDialog1.FileName)
    45. MsgBox("PNG wurde unter " & SaveFileDialog1.FileName & " gespeichert")
    46. If ListView1.Items.Count > 0 Then
    47. Dim eintrag As Integer = ListView1.SelectedItems(0).Index.ToString
    48. ListView1.Items.Item(eintrag).ImageIndex = 1
    49. End If
    50. End If
    51. Else
    52. MsgBox("keine Datei ausgewählt")
    53. End If
    54. Else
    55. MessageBox.Show("keine TPL Datei geladen, Speichern nicht möglich", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error)
    56. End If
    57. End If
    58. End Sub
    @dj alex z Das ist ja die Routine, die den BGW startet, ansonsten passiert da nix problemrelevantes.
    Interessant wäre die BackgroundWorker1.DoWork-Routine, wo Du so was aufrufen solltest, nachdem Du BackgroundWorker1.WorkerReportsProgress = True gesetzt hast:

    VB.NET-Quellcode

    1. BackgroundWorker1.ReportProgress(42)
    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!
    Ach das BackgroundWorker1.RunWorkerAsync() sollte da gar nicht mehr drinstehen.
    Das ist der Code, wie er per Knopfdruck funktioniert.

    Ich wollte es aber gerne in einen BGW auslagern.
    Habe den Code in DoWork eins zu eins übernommen.
    Das funktioniert ja leider nicht, aber eure Erklärungen sind mir irgendwie auch zu hoch :)

    Ich schnall es einfach nicht, wie ich dem BGW sagen kann was für ein Listview Item ausgewählt ist.

    Ps. Das DoWork Ereignis hatte ich im ersten Post geschrieben.
    @dj alex z Was ist das Problem?
    Ungültiger threadübergreifender Vorgang?
    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!

    dj alex z schrieb:

    nicht das Item Adden

    Genau lesen:
    Items As New List(Of ListViewItem)


    Du kannst im BGW-Thread nicht auf Controls zugreifen. Deshalb machst Du die Prüfung, welche Items verarbeitet werden sollen, vorher (also bevor der BGW gestartet wird). Diese Liste von zu verarbeitenden Items gibst Du dem BGW dann mit und der verarbeitet die dann.

    Wenn nur einer, dann öffne SaveFileDialog und speichere den eingegebenen Dateinamen.
    Wenn mehrere, dann öffne FolderBrowserDialog und speichere alle markierten Items nacheinander in diesem gewählten Ordner ab.

    Könnte dann so aussehen (mir ist übrigens gerade aufgefallen, dass man die ListViewItems auch nicht direkt an den BGW übergeben darf, sondern dass Du die benötigten Daten vorher rausklauben musst. So wie ich das sehe ist das nur ein String, aber was Du genau brauchst, musst Du selber wissen):

    VB.NET-Quellcode

    1. Private Structure BgwArguments
    2. Public IsSingleItem As Boolean
    3. Public Path As String
    4. Public Items As IEnumerable(Of String)
    5. End Structure
    6. 'In der Methode, die den BGW startet:
    7. Select Case ListView1.SelectedItems.Count
    8. Case 0
    9. Return
    10. Case 1
    11. Using Dlg As New SaveFileDialog
    12. If Not Dlg.ShowDialog = DialogResult.OK Then Return
    13. BGW.RunWorkerAsync(New BgwArguments With {.IsSingleItem = True, .Path = Dlg.FileName, .Items = {ListView1.SelectedItems(0)}})
    14. End Using
    15. Case Else
    16. Dim Items As New List(Of String)
    17. Using Dlg As New FolderBrowserDialog
    18. If Not Dlg.ShowDialog = DialogResult.OK Then Return
    19. For i = 0 To ListView1.SelectedItems.Count - 1
    20. Items.Add(ListView1.SelectedItems(i).SubItems(0).Text)
    21. Next
    22. BGW.RunWorkerAsync(New BgwArguments With {.IsSingleItem = False, .Path = Dlg.SelectedPath, .Items = Items})
    23. End Using
    24. End Select
    25. 'In der DoWork-Methode
    26. Dim Arguments = DirectCast(e.Argument, BgwArguments)
    27. If Arguments.IsSingleItem Then
    28. e.Items.Single als e.Path speichern
    29. Else
    30. Alle Items von e.Items unter e.Path speichern
    31. End If


    Ein Tipp noch:
    Wie so oft sollte man generell vermeiden, Daten in Controls anzuzeigen und die Daten dann wieder von den Controls rauszuklauben, um sie zu verarbeiten.
    Du solltest stattdessen eine Liste von Objekten haben, die Deine Daten repräsentieren. Das ListView zeigt nur an, was da drin ist. Dann muss man sich nicht umständlich aus irgendwelchen SubItems Strings rausklauben sondern kann die Daten bequem abgreifen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils