Progressbar won't change by ProgressChanged from a BackgroundWorker

Arokh

Centurion
Joined
Apr 11, 2006
Messages
124
Hi
As described in the Topic I'm having some problems with changing the Progressbar (used in a statusstrip).

The DoWork sub calls a Downloadfunction, where while it downloads, the progress percentage is calculated and "reported".
Then the ProgressChanged Sub is called and the percentage is assigned to the progressbar value,
but nothing happens, not even an error.

I've copy-pasted an example from die MSDN and it worked perfectly,
but I can't find the differences which would mine not work.

Relevent Code:
PHP:
    Public Sub GetAddons()
        LogEvent("Getting available ace addons...")

        THArgument.DownloadType = 0
        THArgument.Arg = "http://wowace.com/files"
        If Not main.bgwrk_Download.IsBusy Then
            main.bgwrk_Download.RunWorkerAsync(THArgument)
        End If
    End Sub
PHP:
    Private Sub Downloader(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwrk_Download.DoWork

        Dim Result As DLTStrc = Nothing
        Select Case e.Argument.DownloadType
            Case 0
                Result.DownloadType = 0
                Result.Arg = GetIndex(e.Argument.Arg)
        End Select
        e.Result = Result
    End Sub

    Private Sub Download_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwrk_Download.ProgressChanged
        ProgressDisplay.Value = e.ProgressPercentage
    End Sub

    Private Sub Download_Completed(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwrk_Download.RunWorkerCompleted
        ProcessDownload(e.Result)
        btn_ReloadIndex.Enabled = True

        ProgressDisplay.Visible = False
        ProgressLabel.Visible = False
        LogEvent("Got Addons")
        AddonListReady = True
    End Sub
PHP:
    Public Function GetIndex(ByVal URL As String) As String
        Dim httpReq As Net.HttpWebRequest
        Dim httpRes As Net.HttpWebResponse
        Dim buffer(0) As Byte
        Dim temp As Short = 0
        Debug.Print("Getindex start: " & Date.Now.ToLongTimeString)
        httpReq = System.Net.WebRequest.Create(URL & "/descript.ion")
        httpRes = DirectCast(httpReq.GetResponse(), System.Net.WebResponse)

        temp = httpRes.GetResponseStream.ReadByte()
        While temp <> -1
            If UBound(buffer) Mod 2000 = 0 Then
                main.bgwrk_Download.ReportProgress(UBound(buffer) / httpRes.ContentLength * 100)
                Debug.Print(Int(UBound(buffer) / httpRes.ContentLength * 100))
            End If

            buffer(UBound(buffer)) = temp
            ReDim Preserve buffer(UBound(buffer) + 1)
            temp = httpRes.GetResponseStream.ReadByte()
        End While


        Debug.Print("Getindex done: " & Date.Now.ToLongTimeString)
        Return System.Text.Encoding.UTF8.GetString(buffer)
    End Function

Did I miss something?
 
You have to report progress using BackgroundWorker.ReportProgress(int) for there to be progress handled by the ProgressChanged event.

Take a look at the sample code here for some more info. Take a look here if you need some help translating the sample code from C# to VB.Net.
 
I'm reporting the progress in the GetIndex Sub:
main.bgwrk_Download.ReportProgress(UBound(buffer) / httpRes.ContentLength * 100)

And it sends the value correctly to the Download_ProgressChanged Sub (Debug.Print shows the progress correctly).
But as soon as I want to change controls on the main form within it, it doesn't work. (As in nothing happens)

I tried your example and it works as it should, but as with the example from MSDN,
I can't spot any differences which would cause my problem in my project.

[Update]

I've tried the other way in your example as well (the delegate thing to add lines to the listbox).
But I'm having the exact problem again, nothing happens.

PHP:
    Delegate Sub SetTextCallback(ByVal Message As String)
    Private Sub THLogEvent(ByVal Message As String)
        If main.lst_Log.InvokeRequired Then
            main.Invoke(New SetTextCallback(AddressOf LogEvent), Message)
        Else
            LogEvent(Message)
        End If
    End Sub

    Public Sub LogEvent(ByVal Message As String)
        main.lst_Log.Items.Add("[" & Date.Now.ToLongTimeString & "]: " & Message)
    End Sub

The sub THLogEvent is called when it is going through the GetIndex sub.
 
Last edited:
I'm sorry, but what do you mean when you say "change controls on the main form within it?"

Also, what happens if you use Debug.Print in the ProgressChanged event handler? (Or, in other words, does program control ever reach the event handler?)
 
When the Sub Download_ProgressChanged is called and I try to do anything to the Controls,
adding text to the Listbox or changing the ProgressBar.Value, nothing happens.
This line has no effect:
ProgressDisplay.Value = e.ProgressPercentage
When I use Debug.Print(e.ProgressPercentage) instead the Value is printed as it should.

The example mskeel has given me does the same thing (as far I can see),
but there it succeeds and the Progressbar Control changes accordingly with the progressvalue it has been given.


I hope this clears things up.
 
And you are calling Debug.Print inside the event handler? Do you have WorkerReportsProgress set to true? I copied and pasted your code, stripped out all the web functionality, changed a couple of things because I don't have your form with your controls, and I ended up with this:
Code:
[Color=Blue] Private Sub[/Color] Downloader([Color=Blue]ByVal [/Color]sender [Color=Blue]As [/Color]System.Object, [Color=Blue]ByVal [/Color]e [Color=Blue]As [/Color]System.ComponentModel.DoWorkEventArgs) [Color=Blue]Handles [/Color]BackgroundWorker1.DoWork
        GetIndex()
    [Color=Blue]End Sub[/Color]

    [Color=Blue]Private Sub [/Color]Download_ProgressChanged([Color=Blue]ByVal [/Color]sender [Color=Blue]As [/Color]Object, [Color=Blue]ByVal [/Color]e [Color=Blue]As [/Color]System.ComponentModel.ProgressChangedEventArgs) [Color=Blue]Handles [/Color]BackgroundWorker1.ProgressChanged
        ProgressBar1.Value = e.ProgressPercentage
    [Color=Blue]End Sub[/Color]

    [Color=Blue]Private Sub [/Color]Download_Completed([Color=Blue]ByVal [/Color]sender [Color=Blue]As [/Color]Object, [Color=Blue]ByVal [/Color]e [Color=Blue]As [/Color]System.ComponentModel.RunWorkerCompletedEventArgs) [Color=Blue]Handles [/Color]BackgroundWorker1.RunWorkerCompleted
        Text = "COMPLETE"
    [Color=Blue]End Sub[/Color]

    [Color=Blue]Public Sub[/Color] GetIndex()
        [Color=Blue]For [/Color]i [Color=Blue]As[/Color] Integer = 0 [Color=Blue]To [/Color]99
            BackgroundWorker1.ReportProgress(i)
            System.Threading.Thread.Sleep(100)
        [Color=Blue]Next
    End Sub[/Color]
It looks pretty different, but the multi-threading aspect is identical, and it works just fine. Perhaps you have a property set wrong or an event handler that you forgot to attach, or something along those lines.
 
*confused*
I just found the reason its not working:
I have the Sub GetIndex in a seperate module,
as soon as I copy the sub into the windowsform (with the subs from the Backgroundworker) it works.

What I don't understand is why.
 
UI thread marshalling?

As a guess I would say this strange behaviour might be caused by the event not being raised on the GUI thread. I'd be interested to know if your original code works with the following event handler:

Code:
Private Sub Download_ProgressChanged( _
      ByVal sender As Object, _
      ByVal e As System.ComponentModel.ProgressChangedEventArgs _
    ) Handles bgwrk_Download.ProgressChanged

    If Me.InvokeRequired Then
        Dim evHandler As ProgressChangedEventHandler = AddressOf Download_ProgressChanged
        Me.Invoke(evHandler, sender, e)
    Else
        ProgressDisplay.Value = e.ProgressPercentage 
    End If
End Sub

Try that out.
 
Last edited:
'AddressOf' expression cannot be converted to 'System.Delegate' because type 'System.Delegate' is declared 'MustInherit' and cannot be created.

[Update]
As long as Backgroundworker.reportprogress is called from the WindowsForm code it works,
when it is called in the module it doesn't
 
Last edited:
I had to change "ProgressChangedEventHandler" to "System.ComponentModel.ProgressChangedEventHandler" (I guess there is no difference)

But even after that the result is the same as with just having
PHP:
    Private Sub Download_ProgressChanged( _
          ByVal sender As Object, _
          ByVal e As System.ComponentModel.ProgressChangedEventArgs _
        ) Handles bgwrk_Download.ProgressChanged

        ProgressDisplay.Value = e.ProgressPercentage
    End Sub

Me.InvokeRequired is always false
 
If it isn't working when called from a module it could be down to how you refer to the BackGroundWorker.

In the line
Visual Basic:
main.bgwrk_Download.ReportProgress(UBound(buffer) / httpRes.ContentLength * 100)
where / how is main defined?
 
main is my Windows Form Class (it is defined as Public if it matters) which contains all the Controls and
its event handlers. (Was created by the standard "Windows Form" template.)

I've stripped my code to the necessary code and attached it here.
When GetIndex is in the module the progressbar doesn't change after hitting the button,
it works when it is in the Windos Form Class (main).
 

Attachments

This is interesting...

If you make these changes...
Visual Basic:
Private Sub Downloader(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwrk_Download.DoWork
        GetIndex([B]Me[/B])
    End Sub


and in the module...
Visual Basic:
    Public Function GetIndex([B]ByVal parent As main[/B]) As String
        Dim I As Int16
        THLogEvent("Bla")

        Debug.Print("Getindex start: " & Date.Now.ToLongTimeString)
        For I = 0 To 10
            Threading.Thread.Sleep(200)
            [B]parent[/B].bgwrk_Download.ReportProgress(I * 10)
        Next
        Debug.Print("Getindex done: " & Date.Now.ToLongTimeString)
        Return ""
    End Function
It works.

The event seems to be raised and captured so I'm not really sure why it wouldn't update the progress. Instance versus object maybe?
 
when using a BackgroundWorker, you may want to look at creating a Delegate Sub to handle the progress etc... rather than using the DoWork function.
eg:
Visual Basic:
    '/// the backgroundworker...
    Private WithEvents bgworker As BackgroundWorker
    '/// the Delegate to handle progress...
    Private Delegate Sub download(ByVal wRequest As HttpWebRequest, ByVal wResponse As WebResponse, ByVal bar As ProgressBar)
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        bgworker = New BackgroundWorker
        bgworker.RunWorkerAsync(Me)
    End Sub
    Private Sub bgworker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgworker.DoWork
        Dim imgPath As String = "[URL]http://www.google.com/images/logo.gif[/URL]"
        Dim wRequest As HttpWebRequest = DirectCast(HttpWebRequest.Create(imgPath), HttpWebRequest)
        Dim wResponse As WebResponse = DirectCast(wRequest.GetResponse(), WebResponse)
        Dim dl As New download(AddressOf downloadimage)
        '/// make an array of the arguments required.
        Dim objArgs As Object() = {wRequest, wResponse, ProgressBar1}
        '/// invoke the delegate sub to handle the download / progress.
        Me.Invoke(dl, objArgs)
    End Sub
    Private Sub downloadimage(ByVal wRequest As HttpWebRequest, ByVal wResponse As WebResponse, ByVal pBar As ProgressBar)
        Try
            Dim sWriter As FileStream = New FileStream("new.gif", FileMode.OpenOrCreate)
            Dim ContentLength As Integer = wResponse.ContentLength
            Dim sizeInKB As String = (ContentLength / 1024).ToString()
            Me.Text = "The Size of the Image is: " & sizeInKB.Substring(0, sizeInKB.IndexOf(".") + 3) & "KB"
            Dim buffer(ContentLength) As Byte
            Dim length As Integer = ContentLength
            Dim position As Integer = 0
            Dim complete As Integer = 1
            Dim returned As Integer = 0
            pBar.Value = 0 '/// clear the progressbar.
            pBar.Maximum = ContentLength '/// set it's length.
            While Not complete = 0
                position = wResponse.GetResponseStream().Read(buffer, returned, length)
                sWriter.Write(buffer, returned, position)
                complete = position
                returned += position
                length -= position
                pBar.Step = returned
                pBar.PerformStep()
            End While
            Me.Text = "Completed download"
            sWriter.Close()
            wRequest = Nothing
            MessageBox.Show("Complete!")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub
hope it helps.
 
Last edited:
Back
Top