How to use threading for Loading Bar WinForm? - vb.net

I am wanting to have a an infinite loop progress bar, just to give the user something to stare at during long load times. Currently the user will select a SQL query to execute, and results get displayed onto another winform that contains a gridview for the query results. My goal is to have another winform(loading form) that just has a progress bar on it that just infinitely fills to end and resets over and over until the gridview on the results form finishes rendering. I tried a background worker because i believe this will need multi threading for performance but the load form never shows up. Basically the execution plan should be this:
User clicks execute button, show progress bar on load form(infinite loop), Executes query and loads result form
Closes progress bar on load form
The above execution is called from my Main form
Private Sub LoadingForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
pbSqlCall.Minimum = 0
pbSqlCall.Maximum = 100
pbSqlCall.Step = 10
Dim Counter As Integer = 0
While Counter < 100
pbSqlCall.Increment(10)
Counter += 10
If Counter = 100 Then
Counter = 0
pbSqlCall.Value = 0
End If
End While
End Sub
BackgroundWorker1.RunWorkerAsync()
ExecuteQuery(Parameters, Queries.Item(ddlQueries.SelectedIndex))
'Not sure how to close the form using the thread
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
lf.ShowDialog() 'Load form
End Sub

You can do the following given a form called 'Loader' and a form called 'Main'
Loader:
Public Class Loader
Private Sub Loader_Load(sender As Object, e As EventArgs) Handles MyBase.Load
pbSqlCall.Style = ProgressBarStyle.Marquee
pbSqlCall.MarqueeAnimationSpeed = 30
End Sub
End Class
Main:
Imports System.Threading.Tasks
Public Class Main
Private Sub DoWork_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lf As New Loader
lf.Show(Me)
Task.Factory.StartNew(Sub()
'go get the data for the grid
System.Threading.Thread.Sleep(6000)
End Sub) _
.ContinueWith(Sub(result As task)
'check the aggregate exception
Dim aex As System.AggregateException = result.Exception
If aex IsNot Nothing Then
MessageBox.Show(String.Format("something bad happened: ", aex.Flatten.Message))
End If
'bind the data to the grid
lf.Close()
lf.Dispose()
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
End Sub
Private Sub Main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
End Class

Related

Controlling program-order when using a TabControl

Here is a simple VB.Net Forms program. The form contains a TabControl, which has 2 pages. On TabPage1 there is a Button, and a PictureBox, which contains a small 'Waiting' image. Initially the PictureBox is hidden. The TabControl starts by showing TabPage1.
What I would like to happen is that when the Button is pressed, the PictureBox is made visible, then, my SlowRoutine() is called, then the PictureBox is hidden, then I swap to TabPage2.
What actually happens is that when the Button is pressed, we wait 2 seconds, then we swap to TabPage2. I never see the PictureBox.
If I uncomment the MessageBox line, just to add a halt to the program-flow, then press the Button, the following occurs: 2 seconds passes, and then the PictureBox and the MessageBox appear. Clicking on the MessageBox closes it, and we go to TabPage2. Flipping back to TabPage1 shows that the PictureBox has been hidden.
The order of events is not happening in a logical way. How can I fix this, please?
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
PictureBox1.Visible = False
PictureBox1.Hide()
PictureBox1.SendToBack()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PictureBox1.Visible = True
PictureBox1.Show()
PictureBox1.BringToFront()
SlowRoutine()
' MessageBox.Show("I am waiting")
PictureBox1.Visible = False
PictureBox1.Hide()
PictureBox1.SendToBack()
TabControl1.SelectTab("TabPage2")
End Sub
Private Sub SlowRoutine()
' My SLOW ROUTINE: wait 2 seconds
Threading.Thread.Sleep(2000)
End Sub
End Class
Thanks to all. Here is the working code based on those comments, in case anyone else needs to do a similar task:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
PictureBox1.Visible = False
End Sub
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PictureBox1.Visible = True
Await SlowRoutineAsync()
PictureBox1.Visible = False
TabControl1.SelectTab("TabPage2")
End Sub
Private Async Function SlowRoutineAsync() As Task
' My SLOW ROUTINE: wait 2 seconds
Await Task.Delay(2000)
End Function
End Class

vb.net form load and render speed too slow

I have a mdi parent form which, when clicking a specific button on the screen, opens a new child form. When this form loads, it needs to call the database to retrieve information, the problem is that the form takes a lot of time to load, like 15 seconds, there's not too much code in the load sub, also, I tried to remove the database code from the load event, and, although it did make it a little faster, it still takes some time to load, is there a way to fix this?
This is my form
This is my code
Public Class Deposito
Dim FormPadre As MDICajero = New MDICajero
Private monedas As ArrayList = New ArrayList
Private Sub Deposito_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.WindowState = FormWindowState.Maximized
FormPadre = Me.MdiParent
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Dim ProximoForm As MenuPrincipal = New MenuPrincipal
FormPadre.LimpiarForm()
ProximoForm.MdiParent = FormPadre
ProximoForm.WindowState = FormWindowState.Maximized
ProximoForm.Show()
ProximoForm.CargaMenu(1)
End Sub
Private Sub BDepositar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BDepositar.Click
Dim dato As Emovimientos = New Emovimientos
dato.monto = montodeposito.Text
dato.idcuenta = nrocuenta.Text
dato.fecha = fechacobro.Value
End Sub End class
I've worked with java before and never seen so much load time between forms, even when retrieving information off a database.
EDIT: added a few lines to the constructor to make the form load faster, however, the rendering process it's still buggy.
Sub New()
Me.SetStyle(ControlStyles.DoubleBuffer Or ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.UserPaint, True)
InitializeComponent()
End Sub
EDIT 2: on the MDI parent form i used the same code that is automatically written when adding a mdi form to your solution, the only thing i added was an event for when the buttons are pressed to open the child forms:
Private Sub BRetiro_Click(sender As System.Object, e As System.EventArgs) Handles BRetiro.Click
Dim ProximoForm As Retiro = New Retiro
Me.LimpiarForm()
ProximoForm.MdiParent = Me
ProximoForm.WindowState = FormWindowState.Maximized
ProximoForm.Show()
End Sub
This is the code of the "LimpiarForm" function:
Public Sub LimpiarForm()
'borro todos los form hijos para no ocupar memoria, debe ser lo primero que tengo que hacer
Dim num As Integer
For num = 0 To Me.MdiChildren.Count - 1
Dim a = Me.MdiChildren(num)
a.Dispose()
Next
End Sub

How to wait for BackgroundWorker to finish without killing ProgressBar?

The application is doing a lot more than this, but I have narrowed down the issue with the example below.
When bgwDone.WaitOne() is commented out, the progress bar works fine, cancel button is effective, but execution continues before the background process is complete.
When bgwDone.WaitOne() is applied, the ProgressForm is visible but not enabled, so processing cannot be cancelled and progress bar does not refresh, and the most confusing part, Msgbox("1") does not execute. I only see Msgbox("2") after the background worker finishes. I am utterly perplexed.
Imports System.ComponentModel
Public Class Form1
Private WithEvents bgw As BackgroundWorker
Private Event bgwCancelled()
Private bgwDone As New System.Threading.AutoResetEvent(False)
'Allows ProgressForm to cancel execution
Public Sub bgwCancelAsync()
RaiseEvent bgwCancelled()
End Sub
Private Sub bgw_Cancelled_by_ProgressForm() Handles Me.bgwCancelled
bgw.CancelAsync()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Cursor = Cursors.WaitCursor
bgw = New BackgroundWorker
bgw.WorkerReportsProgress = True
bgw.WorkerSupportsCancellation = True
If bgw.IsBusy = False Then
ProgressForm.Show()
bgw.RunWorkerAsync(10)
End If
'********THIS LINE: bgwDone.WaitOne() MAKES A BIG DIFFERENCE*******
bgwDone.WaitOne()
MsgBox("1")
MsgBox("2")
Cursor = Cursors.Default
End Sub
'BackgroundWorker.RunWorkerAsync raises the DoWork event
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
Dim numToDo As Integer = CInt(e.Argument)
For n As Integer = 1 To numToDo
If bgw.CancellationPending Then
Exit For
End If
System.Threading.Thread.Sleep(200)
bgw.ReportProgress(n * 10)
Next
bgwDone.Set()
End Sub
'ReportProgress raises the ProgressChanged event
Private Sub bgw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
ProgressForm.UpdateProgress(e.ProgressPercentage)
End Sub
Private Sub bgw_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
ProgressForm.Close()
End Sub
And my form with the ProgressBar:
Public Class ProgressForm
Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
Form1.bgwCancelAsync()
End Sub
Public Sub UpdateProgress(pct As Integer)
ProgressBar1.Value = pct
ProgressBar1.Refresh()
End Sub
End Class
I am not sure what you are trying to accomplish. But it almost seems like some of your code is trying to defeat the purpose of a BackGroundWorker:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Cursor = Cursors.WaitCursor
bgw = New BackgroundWorker
...
If bgw.IsBusy = False Then
ProgressForm.Show()
bgw.RunWorkerAsync(10)
End If
bgwDone.WaitOne()
MsgBox("1")
MsgBox("2")
Cursor = Cursors.Default
End Sub
The purpose of a BackgroundWorker is to do some long running task on another thread and leave the UI responsive. I am not sure that a task that only "takes several seconds" qualifies as a long running task.
Given that, why use the WaitCursor while the BGW runs? The point to leaving the UI resposive is to allow the user to do other things in the meantime.
The test for bgw.IsBusy can never, ever be true - you just created it 3 lines earlier. Click the button again and you will create another BGW.
The rest of the code in the click looks like you want or expect the code to continue on the next line after the BGW completes. That's not how it works.
If the app cannot continue without those tasks being completed, disable anything that lets the user go elsewhere until the worker completes or:
Forego the worker and put the form in wait mode (Me.UseWaitCursor) until the stuff is loaded. This doesn't rule out a ProgressBar.
A dedicated Progress Form can make sense in cases where the app will use various workers at various times. A StatusBar can contain a ProgressBar and is much more subtle (and perhaps appropriate since it is a status element).
So, revised and using a form instance for the progress reporter:
MainForm
Private WithEvents bgw As BackgroundWorker
Private frmProg As ProgressForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
bgw = New BackgroundWorker
End Sub
Private Sub btnLoadAll_Click(sender As Object, e As EventArgs) Handles btnLoadAll.Click
bgw.WorkerReportsProgress = True
bgw.WorkerSupportsCancellation = True
If bgw.IsBusy = False Then
' create ProgressForm instance if needed
If frmProg Is Nothing Then frmProg = New ProgressForm
frmProg.Show()
bgw.RunWorkerAsync(78)
End If
btnLoadAll.Enabled = False
End Sub
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
' multiple workers can use the same event
Dim thisWorker = DirectCast(sender, BackgroundWorker)
Dim count = Convert.ToInt32(e.Argument)
For n As Integer = 1 To count
If thisWorker.CancellationPending Then
Exit For
End If
' Fake work:
System.Threading.Thread.Sleep(50)
' dont assume the size of the job if
' there are multiple BGW or tasks
thisWorker.ReportProgress(Convert.ToInt32((n / count) * 100))
Next
End Sub
Private Sub bgw_ProgressChanged(sender As Object,
e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
frmProg.UpdateProgress(e.ProgressPercentage)
End Sub
Private Sub bgw_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
If e.Error IsNot Nothing Then
'... ToDo
ElseIf e.Cancelled Then
'... ToDo
Else
frmProg.Close()
' avoid 'cannot access disposed object':
frmProg = Nothing
Me.btnNextStep.Enabled = True
btnLoadAll.Enabled = True
End If
End Sub
Rather than enabling a "Next" button, the app could automatically proceed. It depends on the app.

VB.NET: Passing variable between forms, error

I want to pass the value of an exact variable to another form...
To be more precise I'm building a game in which a player answer a few questions so I want to pass the variable that contains the correct answer to another form which works as the end game window and displays the score... I'm using this kind of code in the end game window:
Public Class Form2
Public Property CCount As Integer
Private Sub Form5_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.Text = CCcount
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CCount = 0
Form1.Show()
Me.Close()
End Sub
End class
And this in the main form:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Lines of code
Form2.CCount +=1
'Lines of code
'Me.Hide()
'Form2.Show()
EndSub
The first time that I'm "playing the game" everything works fine, but if try to replay it the displaying scores never changes... It remains the same as the first run..
Any ideas how I can fix this out?
You assign value of CCount to the Label only ones when form was loaded.
Load event raised only one time.
Put label updating code inside CCount Property Setter
Private _CCount As Integer
Public Property CCount As Integer
Get
Return _CCount
End Get
Set (value As Integer)
If _CCount = value Then Exit Property
_CCount = value
Me.Label1.Text = _CCount.ToString()
End Set
End Property

textbox on form within form not updating

I'm trying to understand more about VB.NET and Multiple forms so I can make my code better.
I have a SQL database table that holds the live data for all 14 processes, the monitor program updates a form showing the progress of all the processes. Years ago in MS Access I would have simply used a rolling subform to show the contents of the table.
However, my first attempt in VB.NET is to have "many" textboxes, basically 14 lines of textboxes and my code has 14 very similar parts updating all the textboxes. There has to be a better way :(
For Example:
txtProcessID1.Text TxtStatus1.Text ProgressBar1 ......
txtProcessID2.Text TxtStatus2.Text ProgressBar2 ......
txtProcessID3.Text TxtStatus3.Text ProgressBar3 ......
So, I'm trying to come up with a code where I create a SubForm that looks like one controller line, then create 14 instances of this subform on my mainform.
My test code seems to work but the textboxes on the subforms are not updating the contents on screen!! Even though when I call back the contents of .text it is what I expect.
Why does this example code not work, and is my solution the best way to complete this?
Public Class MainForm
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SubForm.SetText = Me.TextBox1.Text ' Try to change contents of a TextBox on the SubForm
Me.TextBox2.Text = SubForm.SetText ' Data comes back as expected, but the subform textbox remains unchanged.
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim objSubForm As New SubForm()
objSubForm.TopLevel = False
Me.Panel1.Controls.Add(objSubForm)
objSubForm.Show()
End Sub
End Class
Public Class SubForm
Public Property SetText() As String
Get
SetText = TextBox1.Text
End Get
Set(ByVal Value As String)
Me.TextBox1.Text = Value ' Control is not updated of the SubForm.
Debug.Print("Value = " & Value) ' The Value is Passed Correctly.
Debug.Print("Text = " & TextBox1.Text) ' And the property of the control has been updated.
End Set
End Property
End Class
Many Thanks
Kev
In your button click you are referencing the class name SubForm, this in VB.NET is called as the default automatic instance of a form, but this is not the same instance that you display in your Form_Load. Here you create a different instance called objSubForm and this is the instance that you display.
To fix you need to keep the objSubForm as a global instance and refer to this global when you click
Public Class MainForm
Dim objSubForm As SubForm
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
objSubForm.SetText = Me.TextBox1.Text ' Try to change contents of a TextBox on the SubForm
Me.TextBox2.Text = objSubForm.SetText ' Data comes back as expected, but the subform textbox remains unchanged.
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
objSubForm = New SubForm()
objSubForm.TopLevel = False
Me.Panel1.Controls.Add(objSubForm)
objSubForm.Show()
End Sub
End Class
Keep in mind that after this change you are responsible to effectively close and dispose the global instance.
Private Sub Form1_FormClosed(sender as Object, e as FormClosedEventArgs) _
Handles Form1.FormClosed
if objSubForm IsNot Nothing
objSubForm.Close
End If
objSubForm = Nothing
End Sub
Thanks Steve, I applied the new code in just half an hour based on your fix.
Here's a basic example of my code that gives me 14 subforms I can update.
I've moved away from creating a property in the subform in favour of directly updating the TextBox control.
The solution has given me "MUCH" less code and now I can add another detail to the one subform and show that for all processes.
Thanks Again!
Kev
Public Class MainForm
Dim objSubForm(14) As SubForm
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Update the 14 forms
For n = 0 To 13
objSubForm(n).TextBox1.Text = Me.TextBox1.Text & " " & n
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' create 14 subforms
For n = 0 To 13
objSubForm(n) = New SubForm()
objSubForm(n).TopLevel = False
Me.Panel1.Controls.Add(objSubForm(n))
objSubForm(n).Location = New Point(0, 20 * n)
objSubForm(n).Show()
Next
End Sub
Private Sub MainForm_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
' CLear up
If objSubForm IsNot Nothing Then
For n = 0 To 13
objSubForm(n).Close()
Next
End If
For n = 0 To 13
objSubForm(n) = Nothing
Next
End Sub
End Class