user1567394
user1567394

Reputation: 31

VB.net 2010 backgroundworker updating form progressbar

Trying to understand background workers :)

Imports System.Threading
Imports System
Imports System.IO
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Text.RegularExpressions
Imports System.Text
Imports System.Diagnostics
Imports System.Drawing


Public Class CLViewForm

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    BackgroundWorker1.RunWorkerAsync()
End Sub


Function SystemLoads()
    Dim PCV As Integer = Convert.ToDecimal(PerformanceCounter1.NextValue())
    Me.Label5.Text = PCV & " %"
    Me.ProgressBar1.Minimum = 0
    Me.ProgressBar1.Maximum = 100
    Me.ProgressBar1.Step = 1
    Me.ProgressBar1.Value = PCV
    Me.Label6.Text = Now.ToLongTimeString
    Return 0
End Function

Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Me.Invoke(SystemLoads())
    End Sub
End Class

The basic gist of what I am trying to do is just load a background worker that updates 3 things on the primary form a progressbar that shows current system load a text label that shows the load by percent and a clock

I know I can do all this with a simple timer, but when I go to add my other timers it tends to make the form all kinds of sluggish so I wish to learn how to shoot most of these things I want to do into a background thread.

I am new to all this so I do not know how to call these things and be thread safe about it and as such I get an error [of course, which is why I am here asking :)] that says "vb.net cross-thread operation not valid control 'progressbar1' accessed from a thread other than which is it called"

or something to that effect.

Using the code sample I supplied how can I make the background thread update the form's progress bar and % label?

TIA

Upvotes: 0

Views: 15973

Answers (6)

Mark Hurd
Mark Hurd

Reputation: 10931

You have been provided better solutions, but here are the changes I use to get your idea working:

  1. As you asked in a comment, the BackgroundWorker only executes the DoWork event once. So call RunWorkerAsync again in RunWorkerCompleted event.
  2. VB.NET normally provides the delegate when you just use AddressOf, but Control.Invoke is too general, so you have to specify one. However, the framework provides MethodInvoker. Thus, change SystemLoads to a Sub.
  3. Some of the "Percent" PerfomaceCounters don't seem to adhere to 0<=%<=100.

So the simple changes to get you past the current roadblocks give you:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    BackgroundWorker1.RunWorkerAsync()
End Sub

Sub SystemLoads()
    Dim PCV As Integer = Convert.ToInt32(PerformanceCounter1.NextValue())
    Me.Label5.Text = PCV & " %"
    Me.ProgressBar1.Minimum = 0
    Me.ProgressBar1.Maximum = 100
    Me.ProgressBar1.Step = 1
    Me.ProgressBar1.Value = Math.Min(Math.Max(0, PCV), 100)
    Me.Label6.Text = Now.ToLongTimeString
End Sub

Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Me.Invoke(New Methodinvoker(AddressOf SystemLoads))
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    BackgroundWorker1.RunWorkerAsync()
End Sub

This leads to a few other issues that I've addressed in this LinqPad query, specifically:

  1. Always getting an ObjectDisposedException on closing the window. I tried mitigating it, but, in the end, I just ignored it when the Form has been disposed.
  2. Otherwise catering for exceptions.
  3. Sleep a bit to reduce the updates, to reduce sluggishness when resizing the form. This is where it shows a Timer would be better.
  4. Use the proper If InvokeRequired pattern even if it's not needed here.

NB I had to fabricate my own control locations and they're barely OK as it stands. Also, you'll have to change the PerformanceCounter to at least an InstanceName that exists on your system.

Upvotes: 0

Steven Cinquegrana
Steven Cinquegrana

Reputation: 31

The trick I use is dreadful but it works:

            pgbMain.Maximum += 1
            pgbMain.Value += 2
            pgbMain.Value -= 1
            pgbMain.Maximum -= 1

Or you could use:

            i = pgbMain.Value + 1
            pgbMain.Value = pgbMain.Maximum
            pgbMain.Value = i

But the latter method means that the final step is as delayed as usual as there is no 'backward' step. (No, neither method is visually detectable.)

You can see on the WinForm when adjusting the progress bar Value property manually that it gradually moves to higher values but instantly moves to lower ones. So this seems to get rid of the lag. Sadly.

Upvotes: 0

Yosem
Yosem

Reputation: 4765

Progress bar is kinda goofy and confusing. I had to resort to keeping all my work in the same form (if you can). So in your case I might try doing your systemloads inside a procedure in the same form. That's what worked for me. I hope it helps. I have pasted some code that might explain it a bit (can't get it to format right in SO- sorry) but in a nutshell:

  1. I start with Me.BackgroundWorker.RunWorkerAsync() Which starts the Do-work event.
  2. that calls BackgroundWorker_DoWork (which calls the Proc you want to do the work and report back)
  3. Your Proc that does the work and will report back using BackgroundWorker.ReportProgress

Declaration

Public Event DoWork As DoWorkEventHandler
Public Event PrgRprt As ProgressChangedEventHandler
Public g_intProgress As Integer             

progress bar info

 Private Sub btnStepOne_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStepTwo.Click
    'Calls  BackgroundWorker_DoWork, which in turn calls step One
    Me.BackgroundWorker1.RunWorkerAsync()
End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker2.DoWork
    'Called from Me.BackgroundWorker1.RunWorkerAsync()
    'calls step One proc, where the work is done
    Call StepOne(strInput)
End Sub

Private Function StepOne(strInput as string)as string
   'this is where I do my work that the progress bar will monitor   
    Dim x as integer    
    For x = 0 to 100
     'Do your calculations as you loop
      'This is where you report back to the worker to change progress bar   
        Me.BackgroundWorker1.ReportProgress(x)
    Next x

End function


Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker2.ProgressChanged

    Me.pbConvertion.Value = e.ProgressPercentage
    Me.g_intProgress = e.ProgressPercentage
    Me.pbConvertion.Maximum = "100"
    Me.pbConvertion.Update()
    Me.lblProgbar.Text = "Percentage - " & Me.pbConvertion.Value & "% Completed"
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    Me.lblProgbar.Text = "Step One Completed"
End Sub

Upvotes: 0

Esselans
Esselans

Reputation: 1546

You need to set:

Backgroundworker can use progressbar

Backgroundworker can report progress

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
     BW1.RunAsync()
End Sub

Then:

Private Sub BW1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BW1.DoWork

    Dim i as integer = 1
    Do Until integer = 0

        IF BW1.CancellationPending = True then
             MsgBox("Cancelled by user")
             Exit Sub
        End If


         ....


        BW1.ReportProgress(i)

    Loop

End Sub

Show the progress:

Private Sub BW1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BW1.ProgressChanged
     Progressbar1.Value = e.ProgressPercentage  
End Sub

EDIT:

You can't use UI Controls directly on DoWork method. You need to delegate them first but for showing progress (progressbar, label, etc) Backgroundworker has it owns method (ProgressChanged()) that let you use the UI in a safe mode.

Upvotes: 0

Matt Wilko
Matt Wilko

Reputation: 27322

I have tried to create a simple example here. You are on the right lines by using a delegate but I think your inplementation is a little off.

A form that has a label, a progressbar and a backgroundworker control on it is what you need then drop the following code in:

Private Sub TestForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    'Start the worker
    BackgroundWorker1.RunWorkerAsync()
End Sub

Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    'simulate long running processes
    UpdateStatus(0, "Loading")
    System.Threading.Thread.Sleep(1000)
    UpdateStatus(33, "One third of the way through")
    System.Threading.Thread.Sleep(1000)
    UpdateStatus(66, "Two thirds of the way through")
    System.Threading.Thread.Sleep(1000)
    UpdateStatus(100, "Finished")
End Sub

'all calls to update the progress bar and label go through here
Private Sub UpdateStatus(ByVal progress As Integer, ByVal status As String)
    Try
        If Me.InvokeRequired Then
            Dim cb As New UpdateStatusCallback(AddressOf UpdateStatusDelegate)
            Me.Invoke(cb, New Object() {progress, status})
        Else
            UpdateStatusDelegate(progress, status)
        End If
    Catch ex As Exception
        MessageBox.Show("There was an error " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Try
End Sub

Private Delegate Sub UpdateStatusCallback(ByVal progress As Integer, ByVal status As String)

'This catually updates the control - modify the paramters and update to suit the control you are using
Private Sub UpdateStatusDelegate(ByVal progress As Integer, ByVal status As String)
    ProgressBar1.Value = progress
    Label1.Text = status
End Sub

Upvotes: 1

iSa
iSa

Reputation: 1153

Create an event for the background worker RunWorkerCompleted and ProgressChanged..
C#

this.backgroundWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(/* the event method */);
this.backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(/* the event method */);

Also, do not forget to set the WorkerReportsProgress to true..

Upvotes: 0

Related Questions