How to report background worker progress with only percentage on a label without progress bar - vb.net

Using the BackgroundWorker Class I need to run a process which extracts data from a database and report progress as percentage completed on a label without progress bar.
Most of the examples I have seen for this approach uses a loop in the ProgressChanged Event which makes the process to repeat:
For example:
Calling the code as shown below causes multiple calls to the DB, thus repeating the query.
Private Sub backgroundWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim i As Integer
For i = 1 To 10
If (worker.CancellationPending = True) Then
e.Cancel = True
Exit For
Else
' This method is rightfully called multiple times.
GetDbDataHere()
worker.ReportProgress(i * 10)
End If
Next
End Sub
Report update here:
Private Sub backgroundWorker1_ProgressChanged(ByVal sender As System.Object, _
ByVal e As ProgressChangedEventArgs) Handles
backgroundWorker1.ProgressChanged
resultLabel.Text = (e.ProgressPercentage.ToString() + "%")
End Sub
Without a progress bar, how can run the process and display the percentage completed but without calling the method being process by the BackgroundWorker multiple times?

Related

VB - Issues With Progress Bar

I'm making a simple progress bar to be used as a splash screen within my application but when the code is executed, the loading bar does not reach the end of the progress bar.
I've used the code:
splashprogressbar.Increment(1)
If splashprogressbar.Value = 100 Then
Main_Menu.Show()
Me.Hide()
End If
to open a form when the progress bar reaches 100, which has been set as the maximum value.
The issue is more so related to appearance rather than functionality but i would still like to understand why this occurs and hopefully get a fix.
To clarify, the form Main_Menu, opens when the bar is about 3/4 of the way completed and i can't get my head around why this occurs. Any ideas?
I couldn't imagine that VB has such a bug. Probably you are running an old or incompatible ".Net Framework" that causes that.
You can try to set your Progressbars maximum programatically while running the program.
Use this code in the Form1_Load event:
ProgressBar1.Maximum = 100
It should work. But if it doesn't, maybe it's just code related. Try to fill your progressbar this way (It must be in a timer):
If splashprogressbar.Value = splashprogressbar.Maximum Then
Main_Menu.Show()
Me.Hide()
Else
splashprogressbar.Value += 100
End If
Just a friendly advice: It seems you're simulating a fake loading bar which fills and then, only then, open the app (Potentially wasting the users time). Don't do that.
You could also try taking a look at the following link. This may help with your approach;
Trigger Background Worker
The below was copied from the link above...
Dim WithEvents bgWorker As New BackgroundWorker With { _
.WorkerReportsProgress = True, _
.WorkerSupportsCancellation = True}
Private Sub bgWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
For i As Integer = 0 To 100
'Threw in the thread.sleep to illustrate what's going on. Otherwise, it happens too fast.
Threading.Thread.Sleep(250)
bgWorker.ReportProgress(i)
Next
End Sub
Private Sub bgWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
If e.ProgressPercentage Mod 10 = 0 Then
MsgBox(e.ProgressPercentage.ToString)
End If
End Sub
Private Sub bgWorker_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
MsgBox("Done")
End Sub
you could try something like this;
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Me.Show()
Dim i As Integer
splashprogressbar.Minimum = 0
splashprogressbar.Maximum = 100
If splashprogressbar.Value < splashprogressbar.Maximum Then
For i = 0 To 100
splashprogressbar.Value = i
Application.DoEvents()
System.Threading.Thread.Sleep(100)
Next
End If
Me.Hide()
MsgBox("Here I am") 'Use your "Main_Menu.Show" here
End Sub

How does the ProgressBar.PerformStep() function work?

I'm quite confused about progress bar in VB.net, here is my code
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
ProgressBar_my.Minimum = 0
ProgressBar_my.Maximum = 10
ProgressBar_my.Step = 1
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ProgressBar_my.PerformStep()
Threading.Thread.Sleep(5000)
If 1 = 1 Then
ProgressBar_my.PerformStep()
ProgressBar_my.PerformStep()
End If
'Threading.Thread.Sleep(2000)
End Sub
For the above code, what I expected is that after I click Button1, the progress bar will increase the progress status by 1, then it will pause for 5 sec, then it will increase the progress status by 2 at once.
However, after I ran the above code, what I saw was that after I click Button1, the progress bar will increase by 3 continually after 5 sec.
Can someone tell me why it behaves like this and How should I program my code so that I can increase by 1, then pause 5 sec and then increase by 2?
Thanks in advance!
I think what you are seeing (or not seeing) is the fact that the progress bar takes a finite amount of time to advance each step.
When you call Threading.Thread.Sleep on the UI thread this stops the progress bar from being redrawn until after the Sleep
What you should do is update the progress bar on a background worker instead, then I think you will see the effect you desire.
Add a BackgroundWorker to your form
Change your button click code to start the worker:
Private Sub frmSO_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ProgressBar_my.Minimum = 0
ProgressBar_my.Maximum = 10
ProgressBar_my.Step = 1
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync
End Sub
Then perform the update in the DoWork event:
'define a delegate to handle updates to the UI thread
Private Delegate Sub PerformStepCallback()
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim cb As PerformStepCallback = AddressOf ProgressBar_my.PerformStep
Me.BeginInvoke(cb)
Threading.Thread.Sleep(5000)
Me.BeginInvoke(cb)
Me.BeginInvoke(cb)
End Sub
The problem is that you call Threading.Thread.Sleep(5000), which will, well, pause the current thread. But that also means that the window won't be redrawn, so you see the effect of the first call to PerformStep only after the thread is no longer paused.
You can use another thread for wait and the second update; the easiest way in this case is using the Task Parallel Library:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ProgressBar1.PerformStep()
Task.Delay(5000).ContinueWith(Sub()
ProgressBar1.PerformStep()
ProgressBar1.PerformStep()
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
End Sub
Thanks Matt Wilko for giving me the clue. Here is my code.
From the Components tab of the Toolbox, add a BackgroundWorker component named backgroundWorker2. Create DoWork and ProgressChanged event handlers for the BackgroundWorker.
The following code will initiate the Component.
Public Sub New()
InitializeComponent()
BackgroundWorker2.WorkerReportsProgress = True
BackgroundWorker2.WorkerSupportsCancellation = True
End Sub
Call RunWorkerAsync when the button is clicked and the background worker is not running.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If BackgroundWorker2.IsBusy <> True And ProgressBar_my.Value < 6 Then
BackgroundWorker2.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker2_DoWork_1(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles BackgroundWorker2.DoWork
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
worker.ReportProgress(1)
System.Threading.Thread.Sleep(2000)
worker.ReportProgress(1)
worker.ReportProgress(1)
End Sub
The following event is raised when every time the ReportProgress method is called.
Private Sub backgroundWorker2_ProgressChanged(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
Handles BackgroundWorker2.ProgressChanged
ProgressBar_my.PerformStep()
End Sub
More information about the BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

BackgroundWorker.ReportProgress exception if run in another module

My BackgroundWorker works perfectly in my main form frmMain. But when I run the ReportProgress method in another module, I get exception "This BackgroundWorker states that it doesn't report progress. Modify WorkerReportsProgress to state that it does report progress." This IS set to report progress; this works fine when run the same way in the main module.
Basically, from a module called by my BackgroundWorker, I want to show progress on my main form.
How can I fix this? The only idea I have is to move the code from the module into my main form, but this seems a backward step, which would involve extra work. Am hoping there are easier ways!
Calling code in class frmMain:
Friend WithEvents BackgroundWorker As New System.ComponentModel.BackgroundWorker
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTest.Click
' Specify that we do NOT want the background operation to allow cancellation
BackgroundWorker.WorkerSupportsCancellation = False
' Specify that we want the background operation to report progress.
BackgroundWorker.WorkerReportsProgress = True
' Start running the background operation by calling the RunWorkerAsync method.
BackgroundWorker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
Dim result As Boolean
result = MyTest()
End Sub
Private Sub BackgroundWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker.ProgressChanged
Me.Text = e.ProgressPercentage.ToString() & "%"
sspStatus.Text = e.UserState.ToString
End Sub
Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles BackgroundWorker.RunWorkerCompleted
If e.Cancelled = True Then
' The background operation was cancelled
Me.Text = "Cancelled!"
ElseIf e.Error IsNot Nothing Then
' The background operation encountered an error
Me.Text = "Error: " & e.Error.Message
Else
' The background operation completed successfully
Me.text = "Done!"
End If
End Sub
Code which generates the exception in separate module Invoices:
Public Function MyTest() As Boolean
frmMain.BackgroundWorker.ReportProgress(0)
End Function
Am using VB.NET in VS 2010, with .NET 3.5.
Try to set it up as
Public Function MyTest(worker as BackgroundWorker) As Boolean
worker.ReportProgress(0)
End Function
to make sure you are talking to the right worker instance.
(And aside: avoid using classnames for instance fields).

Iteration Not Working As Intended

I'm using a DO interation to loop a function I'm using to test for internet connectivity. The code is working fine, except that when one of the tests is satisfied the loop stops. I want this to continue in the background while the program is running. How can I get this to work?
Private Sub checkInternet()
Dim InetChecker As Boolean
InetChecker = CheckForInternetConnection()
Do While LabelCount.Text <> ""
Thread.Sleep(10)
If InetChecker = True Then
Dim image = My.Resources.greenbar
PictureBox4.Image = image
Else
Thread.Sleep(10)
Dim image = My.Resources.redbar
PictureBox4.Image = image
'NoInetConnError.Show()
End If
Loop
End Sub
Your assistance would be greatly appreciated, thanks.
Put a BackgroundWorker on your form (you will find it in the Components section of the Toolbox).
In the Properties window set WorkerReportsProgress to True for your BackgroundWorker.
Insert the following code to your form
Private connected As Boolean
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
While True
Dim online = CheckForInternetConnection()
If online <> connected Then
connected = online
BackgroundWorker1.ReportProgress(CInt(online))
End If
Thread.Sleep(500)
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles BackgroundWorker1.ProgressChanged
Dim online As Boolean = CBool(e.ProgressPercentage)
If online Then
PictureBox4.Image = My.Resources.greenbar
Else
PictureBox4.Image = My.Resources.redbar
End If
End Sub
Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
' Start the background worker
BackgroundWorker1.RunWorkerAsync()
End Sub
Note that Sub BackgroundWorker1_DoWork runs on a separate thread and does not freeze your form while it is running.
It would be best to do something like this in a Timer and not in a loop.
Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer1.Tick
If CheckForInternetConnection Then
PictureBox4.Image = My.Resources.greenbar
Else
PictureBox4.Image = My.Resources.redbar
End If
End Sub
If you have access to .Net framework 3+ then you could use the DispatcherTimer class which essentially creates an interval (set at whatever length you require) which you can handle the tick event for. When the tick event is raised, you can do your internet connection check.
Modifying the MSDN example for your situation, you could do something like this:
' DispatcherTimer setup
dispatcherTimer = New Threading.DispatcherTimer()
AddHandler dispatcherTimer.Tick, AddressOf dispatcherTimer_Tick
dispatcherTimer.Interval = New TimeSpan(0,0,1) ' Or however long you want
dispatcherTimer.Start()
Private Sub dispatcherTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
' Checks to see whether an internet connection is still available etc
checkInternet()
' Forcing the CommandManager to raise the RequerySuggested event
CommandManager.InvalidateRequerySuggested()
End Sub

VB.Net Asynchronous background worker inside a loop

Im using Vb.net visual studio 2008. i have got a process(system.diagnostics.process) to be run in background and it updates the Ui thread progressbar and a label and i have worked it using backgroundworker.runworkerAsync. Now the problem is i have to work the same process for a number of time with different inputs.
The code block is :
Private Sub fnStartEvent(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.click
Dim inputFolder = Directory.GetFiles(textboxSourceFolder.text)
Dim currentNumberOfFile As Integer=1
For each Files in inputFolder
Dim arguments As Object = Files
updateProgressStatus(0, currentNumberOfFile)
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync(New Object() {arguments})
currentNumberOfFile += 1
Next
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'uses the arguments
Dim Bg_process As System.Diagnostics.Process = New System.Diagnostics.Process
With Bg_process.StartInfo
.Arguments = str_arguments
.FileName = ffmpegPath
.CreateNoWindow = True
.UseShellExecute = False
.RedirectStandardOutput = True
.RedirectStandardError = True
End With
Bg_process.Start()
Dim outputReader As StreamReader = Bg_process.StandardError
Dim output As String
While Not Bg_process.HasExited
output = outputReader.ReadLine()
BackgroundWorker1.ReportProgress(0, output)
Threading.Thread.Sleep(500)
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
' process args
updateProgressStatus(args(0), args(1) )
End Sub
Private Function BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Messagebox.Show(e.error.ToString)
End Try
Sub updateProgressStatus(ByVal progressValue As Integer, ByVal progressStatus As String)
progressBar.value = progressValue
lblprogressStatus.Text = progressStatus
End Sub
The problem in here is the fnStartEvent method once gets started, it calls BackgroundWorker1.runWorkerAsync process and that runs in a seperate thread and does not wait for the thread to complete and it moves to the next line of code i.e loops to the next item and it returns to same BackgroundWorker1.runWorkerAsync line and throws exception that it is already running.
Tried
1.
Private Sub fnStartEvent(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.click
Dim inputFolder = Directory.GetFiles(textboxSourceFolder.text)
Dim currentNumberOfFile As Integer=1
For each Files in inputFolder
Dim arguments As Object = Files
updateProgressStatus(0, currentNumberOfFile)
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync(New Object() {arguments})
''
Do Until BackgroundWorker1.IsBusy
Thread.Sleep(500)
Loop
currentNumberOfFile += 1
Next
End Sub
But this does not updates the Ui thread used to denote progress.
2.Put the whole process inside the DoWork thread and loop for each file in there itself, but that to returns the cross thread error in progress bar updation.
what is the standard procedure in executing a loop for the backgroundworker to wait for finishing as well as update the UI without blocking it.
Whenever you use Thread.Sleep, you are actually sleeping the current thread that is hooked to your startup class (ie. Form). So, if you sleep your main thread, no UI updates will happen.
Per the MSDN article regarding BackgroundWorker.IsBusy, you need to throw an Application.DoEvents. Code below is copied from the article linked above.
Private Sub downloadButton_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) _
Handles downloadButton.Click
' Start the download operation in the background.
Me.backgroundWorker1.RunWorkerAsync()
' Disable the button for the duration of the download.
Me.downloadButton.Enabled = False
' Once you have started the background thread you
' can exit the handler and the application will
' wait until the RunWorkerCompleted event is raised.
' If you want to do something else in the main thread,
' such as update a progress bar, you can do so in a loop
' while checking IsBusy to see if the background task is
' still running.
While Me.backgroundWorker1.IsBusy
progressBar1.Increment(1)
' Keep UI messages moving, so the form remains
' responsive during the asynchronous operation.
Application.DoEvents()
End While
End Sub