VB.Net Asynchronous UI Thread Issue - vb.net

I have a Windows Form, that has a Telerik RadGridView with a bunch of records to be imported into our SQL database. The RadGridView just shows the users the records that are going to be imported, in case they see an easy to fix mistake that they could address, before importing the records.
I have the following controls on the Winform form:
rgImport - Telerik RadGridView control
btnSubmit - button control
ProgressBar1 - Progress Bar for when importing the records
Label1 - some text telling the user to review the records presented in rgImport
I am looking to make some of these functions Async/Await, so the UI doesn't get locked, and the progress bar shows what percentage of records have been imported:
The two calls to these functions:
DataAPI.HR_Payroll_TimeCards_Insert(dr)
DataAPI.HR_Payroll_TimeCards_Insert_Double(dr)
do nothing more than inject the records into our database server tables and return a boolean on whether is was successful or failed
Private Sub Import_Payroll_Review_Load(sender As Object, e As EventArgs) Handles Me.Load
rgImport.DataSource = DataAPI.HR_Payroll_Import_GetData()
ProgressBar1.Minimum = 0
ProgressBar1.Maximum = 100
ProgressBar1.Visible = False
End Sub
DataAPI.HR_Payroll_Import_GetData() - just returns a DataTable that is used to load up the RadGridView with records
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Select Case MsgBox("Are you sure you are ready to import?", MsgBoxStyle.YesNo, "Import Records")
Case MsgBoxResult.Yes
'Import the records from the import table
Dim dt As DataTable = DataAPI.HR_Payroll_Import_GetRecordsToImport()
Dim iRows As Long = 1
Dim iTotalRows As Long = dt.Rows.Count
'CallTheInsertMethodHere
For Each dr As DataRow In dt.Rows
If dr("AfterHours") = 0 Then
'This is standard time, so import it into the [TimeCards.Current] table
If DataAPI.HR_Payroll_TimeCards_Insert(dr) Then
'Success
Debug.Print("Inserted Row into TimeCard.Current")
Else
'Failed
Debug.Print("Failed to insert row into TimeCard.Current")
End If
Else
'This is double time, so import it into the [TimeCards.Current.Double] table
If DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) Then
'Success
Debug.Print("Inserted Row into TimeCard.Current.Double")
Else
'Failed
Debug.Print("Failed to insert row into TimeCard.Current.Double")
End If
End If
iRows += 1
Next
MsgBox("Inserted " & iRows - 1 & " rows", vbInformation, "Success")
Case Else
'User clicked on no, so exit
Exit Sub
End Select
End Sub
I am just looking for a little help/direction on how to break this up and where to add Async/Await to allow the UI to be free'd up to show what percentage of the records have been inserted using the ProgressBar (The total records is in the variable iTotalRows)
Thank you for your time reviewing this and any input you may provide to me to get this working.

This is a basic implementation of Async/Await for you to see how the UI is still responsive while Awaiting DoWorkAsync, but not while running DoWork. I included progress from this Stephen Cleary answer
Private Function DoWorkAsync(progress As IProgress(Of Integer)) As Task(Of Boolean)
Return Task.Run(Function() DoWork(progress))
End Function
Private Function DoWork(progress As IProgress(Of Integer)) As Boolean
Dim i As Integer
While i < 100
i += (New Random).Next(5)
Threading.Thread.Sleep(50)
progress.Report(i)
End While
Return True
End Function
Private Async Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim progress = New Progress(Of Integer)(
Sub(value)
ProgressBar1.Value = Math.Max(Math.Min(value, ProgressBar1.Maximum), ProgressBar1.Minimum)
Console.WriteLine(value)
End Sub)
If MessageBox.Show("Run Async?", "Confirm", MessageBoxButtons.YesNo) = DialogResult.Yes Then
Await DoWorkAsync(progress)
Else
DoWork(progress)
End If
MessageBox.Show("Done")
End Sub
You can basically put all your work into DoWork, with success logic (that's why I returned Boolean) and it won't block the UI. I wouldn't worry about making separate functions for DataAPI.HR_Payroll_Import_GetData(), DataAPI.HR_Payroll_Import_GetRecordsToImport(), DataAPI.HR_Payroll_TimeCards_Insert() or DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) unless you need that granularity for other parts of your code.
You can add a CancellationToken, and with your own code doing the work, it might look like this
Private Function DoWorkAsync(progress As IProgress(Of Integer), cancellationToken As Threading.CancellationToken) As Task(Of Boolean)
Return Task.Run(Function() DoWork(progress, cancellationToken))
End Function
Private Function DoWork(progress As IProgress(Of Integer), cancellationToken As Threading.CancellationToken) As Boolean
Try
'Import the records from the import table
Dim dt As DataTable = DataAPI.HR_Payroll_Import_GetRecordsToImport()
Dim iRows As Long = 1
Dim iTotalRows As Long = dt.Rows.Count
'CallTheInsertMethodHere
For Each dr As DataRow In dt.Rows
If dr("AfterHours") = 0 Then
'This is standard time, so import it into the [TimeCards.Current] table
If DataAPI.HR_Payroll_TimeCards_Insert(dr) Then
'Success
Debug.Print("Inserted Row into TimeCard.Current")
Else
'Failed
Debug.Print("Failed to insert row into TimeCard.Current")
End If
Else
'This is double time, so import it into the [TimeCards.Current.Double] table
If DataAPI.HR_Payroll_TimeCards_Insert_Double(dr) Then
'Success
Debug.Print("Inserted Row into TimeCard.Current.Double")
Else
'Failed
Debug.Print("Failed to insert row into TimeCard.Current.Double")
End If
End If
iRows += 1
progress.Report(CInt(100 * iRows / iTotalRows))
cancellationToken.ThrowIfCancellationRequested()
Next
Return True
Catch ex As Exception
Debug.Print(ex.Message)
Return False
End Try
End Function
Private Async Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim progress As IProgress(Of Integer) = New Progress(Of Integer)(
Sub(value)
ProgressBar1.Value = Math.Max(Math.Min(value, ProgressBar1.Maximum), ProgressBar1.Minimum)
Console.WriteLine(value)
End Sub)
cancellationSource = New Threading.CancellationTokenSource()
Dim cancellationToken = cancellationSource.Token
cancellationToken.Register(Sub() progress.Report(0))
If MessageBox.Show("Run Async?", "Confirm", MessageBoxButtons.YesNo) = DialogResult.Yes Then
Await DoWorkAsync(progress, cancellationToken)
Else
DoWork(progress, cancellationToken)
End If
progress.Report(0)
If cancellationToken.IsCancellationRequested Then
MessageBox.Show("Cancelled")
Else
MessageBox.Show("Done")
End If
End Sub
Private cancellationSource As New Threading.CancellationTokenSource()
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
cancellationSource.Cancel()
End Sub
Credit to Stephen Cleary for the CancellationToken, again

Related

Read and write below a precise cell in a CSV

I'm stuck on a basic problem. What I want is to parse through a CSV in order to compare some string and write below if I find it.
Precisely, I have a programe where I can drag and drop some button, when I drop this button I want to save it's new location on the first empty cells below the corresponding column.
Here's a sample of my CSV :
So I substring the .x/.y from my CSV and compare the name from the drop button with each cell with the help of textFieldParser. It seems to work my loop stopped when it find an equal expression.
But here's the problem I don't know how to say to my program to write below it. The first reason I can figured it out is because my parser go until the endOfData and I want it to go until the endOfDat + one row.
The second one is because I don't know if I can use a fieldwriter into textFieldParser, I mean I tried to create a variable with row+1 and write below but nothing happen when I use fileWriter.
now a sample of my code :
Private Sub manageCsv(ByVal sender As Button)
Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("..\..\Pic\csvPic.csv")
MyReader.TextFieldType = FileIO.FieldType.Delimited
MyReader.SetDelimiters(",")
Dim currentRow As String()
Dim rowPlusOne As String()
While Not MyReader.EndOfData
Try
currentRow = MyReader.ReadFields()
rowPlusUn = MyReader.ReadFields()
Dim currentField As String
Dim str As String = btnSender.Name.Substring(3)
Dim nameDelimited As String
Dim x As Integer
For Each currentField In currentRow
''Search the corresponding field''
x = InStr(currentField, ".")
If Not (currentField.Equals("imagefile")) Then ''imagefile is the first index of my csv''
nameDelimited = currentField.Substring(0, x) ''substr the extension''
If nameDelimited.Equals(str) Then
writeCsv("..\..\Image\csvPic.csv", nameDelimited, ",")
''Ofc the "+1" does not work but that was the idea''
currentRow(+1) = lblImgName.Text
currentRow(+1) = btnSender.Location.ToString
Exit For
End If
End If
Next
Catch ex As _
Microsoft.VisualBasic.FileIO.MalformedLineException
MsgBox("Line " & ex.Message & "is not valid and will be skipped.")
End Try
End While
End Using
End Sub
I hope it's clear enough, if not i'll try to elaborate more. Thanks for your help
Show your teacher that there are better ways to do this with a simple text file. The file will only exist if buttons have been moved before in the application. See in line comments.
Private ButtonLocation As New Dictionary(Of Button, Point)
Private MouseIsDown As Boolean
Private ptX, ptY As Integer 'Starting point of mouse relative to the button
Private btn As Button 'The button being moved
Private ButtonPath As String = "C:\Users\maryo\Desktop\Code\DroppedButtons.txt"
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Reposition the buttons to where they were dropped in the previous session.
If File.Exists(ButtonPath) Then
Dim lines = File.ReadAllLines(ButtonPath) 'returns an array of strings (each line)
For Each line In lines 'loop though each line in the file
Dim fields = line.Split(","c) 'The three values on the line are separated by a comma
Dim b = DirectCast(Controls(fields(0)), Button) 'Change the string Button.Name
'to an actual Button object by finding it in the controls collection
'Set the location with the next 2 values on the line
b.Location = New Point(CInt(fields(1)), CInt(fields(2)))
'Add the Button and Location to the list
ButtonLocation.Add(b, b.Location)
Next
End If
End Sub
'These three Event procedures are the normal code to Drag and Drop a control
Private Sub Button_MouseDown(sender As Object, e As MouseEventArgs) Handles Button1.MouseDown, Button2.MouseDown
btn = DirectCast(sender, Button)
ptX = e.Location.X
ptY = e.Location.Y
MouseIsDown = True
End Sub
Private Sub Button_MouseMove(sender As Object, e As MouseEventArgs) Handles Button1.MouseMove, Button2.MouseMove
If MouseIsDown Then
'e.X and e.Y are the coordinates of the Mouse relative to the control (the Button)
'not the Form or the Screen.
btn.Location = New Point(btn.Location.X + e.X - ptX, btn.Location.Y + e.Y - ptY)
End If
End Sub
Private Sub Button_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp, Button2.MouseUp
MouseIsDown = False
'When we drop the button with the MouseUp event we record the new location in the list
RecordButtonLocation(btn, btn.Location)
btn = Nothing
End Sub
Private Sub RecordButtonLocation(Sender As Button, Location As Point)
'Check if the Button is already in the list
If ButtonLocation.ContainsKey(Sender) Then
'Record its new location
ButtonLocation.Item(Sender) = Location
Else
'If it is not in the list add it.
ButtonLocation.Add(Sender, Location)
End If
End Sub
Private Sub Form1_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
SaveDictionary()
End Sub
Private Sub SaveDictionary()
If ButtonLocation.Count > 0 Then
'If there is anything in the list we will create or overwrite the file
Dim sb As New StringBuilder
For Each kv As KeyValuePair(Of Button, Point) In ButtonLocation
sb.AppendLine($"{kv.Key.Name},{kv.Value.X},{kv.Value.Y}")
Next
File.WriteAllText(ButtonPath, sb.ToString)
End If
End Sub

vb.NET You are not allowed to perform an operation across different threads

I am running code to read text from pdf files and than create a WordCloud. To inform the user the process is on going I add a BackgroundWorker to my form that shows an image saying Loading. I get an error for operating across different threads.
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
If ListBox1.SelectedIndex > -1 Then
For Each Item As Object In ListBox1.SelectedItems
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
Dim unused = MsgBox("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
Else
Dim unused = MsgBox("You have to choose an Archive to create the Word Cloud",, Title)
End If
Size = New Drawing.Size(1400, 800)
End Sub
I put above code in a Private Sub and called it from my BackgroundWorker. The error occured at this line: If ListBox1.SelectedIndex > -1 Then
After trying the suggested code I get the same error again:
Public Sub CreateMyCloud(ByVal sourcePDF As String)
Dim WordsFreqList As New List(Of WordsFrequencies)
For Each line As String In File.ReadLines(sourcePDF)
Dim splitText As String() = line.Split(","c)
If splitText IsNot Nothing AndAlso splitText.Length = 2 Then
Dim wordFrq As New WordsFrequencies
Dim freq As Integer
wordFrq.Word = splitText(0)
wordFrq.Frequency = If(Integer.TryParse(splitText(1), freq), freq, 0)
WordsFreqList.Add(wordFrq)
End If
Next
If WordsFreqList.Count > 0 Then
' Order the list based on the Frequency
WordsFreqList = WordsFreqList.OrderByDescending(Function(w) w.Frequency).ToList
' Add the sorted items to the listview
WordsFreqList.ForEach(Sub(wf)
error -> ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End If
Dim wc As WordCloudGen = New WordCloudGen(600, 400)
Dim i As Image = wc.Draw(WordsFreqList.Select(Function(wf) wf.Word).ToList, WordsFreqList.Select(Function(wf) wf.Frequency).ToList)
ResultPictureBox.Image = i
End Sub
Should look something more like:
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
If ListBox1.SelectedItems.Count > 0 Then
Dim data As New List(Of Object)
For Each Item As Object In ListBox1.SelectedItems
data.Add(Item)
Next
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
BackgroundWorker1.RunWorkerAsync(data)
Else
MessageBox.Show("You have to choose an Archive to create the Word Cloud",, Title)
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim data As List(Of Object) = DirectCast(e.Argument, List(Of Object))
For Each Item As Object In data
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
MessageBox.Show("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Size = New Drawing.Size(1400, 800)
End Sub
To fix the error in your second, edited post:
WordsFreqList.ForEach(Sub(wf)
ListView1.Invoke(Sub()
ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End Sub)
You cannot simply access controls outside of the current thread. You first need to call the Invoke method on the target control and then pass a delegate containing the instructions intended to modify the control outside of the current thread. See this article on MSDN on how to do this: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

VB Simple Threading using Delegates

I understand the concept of threading. I understand the concept of delegates but I am having trouble combining the two concepts. I followed a tutorial and I was able to make two counters start at the same time using multiple threads on my form. I was getting the cross threading error and I used the Me.CheckForIllegalCrossThreadCalls = False work around. I know my current method isnt ideal and I was wondering how I would use delegates to produce the same results. I have been at it all day and still cant seem to grasp the idea. How would I add delegates to the code below to allow two counters to work simultaneously on my form?
Public Class Form1
Dim i As Integer = 0
Dim i2 As Integer = 0
'declare two threads
'thread 1
Dim thread As System.Threading.Thread
'thread 2
Dim thread2 As System.Threading.Thread
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'replace countup() with, this will assign the countup method to thread 1
thread = New System.Threading.Thread(AddressOf countup)
thread.Start()
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
' countup2()
thread2 = New System.Threading.Thread(AddressOf countup2)
thread2.Start()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Label1.Text = i
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Label2.Text = i2
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
End Class
I would love to see the code using delegates but what I would really like is to have the understanding of whats going on.
Thanks guys
Not sure if this is exactly what you're looking for, but here's my best shot at it:
Module Module1
Dim i As Integer = 0
Dim i2 As Integer = 0
Public Delegate Sub counting()
Sub Main()
Dim del2 As counting = AddressOf countup2
Dim callback2 As IAsyncResult = del2.BeginInvoke(Nothing, Nothing)
Dim del1 As counting = AddressOf countup
Dim callback1 As IAsyncResult = del1.BeginInvoke(Nothing, Nothing)
del2.EndInvoke(callback2)
del1.EndInvoke(callback1)
Console.ReadLine()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Loop
Console.WriteLine("i = " & i)
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Loop
Console.WriteLine("i2 = " & i2)
End Sub
End Module
Sorry I have the first and second parts reversed and it's a console app instead of a form, but I figured the important part was to demonstrate delegates...
As a note, I'm not sure how familiar you are with delegates, but I included the EndInvoke to make sure the program wouldn't terminate prior to the delegates finishing their operations. They are used to return any values or exceptions from the method call as well as making the program wait. (In this case, since it's a sub there is no return value, so I didn't bother worrying about it)
One should use Control.Invoke to execute a specified delegate on the thread that owns the control's underlying window handle. Also, replace Me.Refresh() with Thread.Sleep(1) to ensure that other threads get some execution time.
Private Sub countup()
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.Label1.Text = i.ToString())
Thread.Sleep(1)
Next
End Sub
Here's an example.
' n=0 n=1
Private threads As Thread() = New Thread(2 - 1) {Nothing, Nothing}
Private Sub ButtonsClick(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click
Dim n As Integer = -1
If (sender Is Me.Button1) Then
n = 0
ElseIf (sender Is Me.Button2) Then
n = 1
End If
If (n <> -1) Then
If (Me.threads(n) Is Nothing) Then
'Start new thread.
Me.threads(n) = New System.Threading.Thread(Sub() Me.CountUp(n))
Me.threads(n).Start()
Else
'Abort thread.
Me.threads(n).Abort()
Me.threads(n) = Nothing
End If
End If
End Sub
Public Sub DisplayCount(n As Integer, text As String)
'Inside UI thread.
If (n = 0) Then
Me.Label1.Text = text
ElseIf (n = 1) Then
Me.Label2.Text = text
End If
End Sub
Private Sub CountUp(n As Integer)
'Inside worker thread.
Try
If ((n < 0) OrElse (n > 1)) Then
Throw New IndexOutOfRangeException()
End If
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.DisplayCount(n, i.ToString()))
Thread.Sleep(1)
Next
Catch ex As ThreadAbortException
Me.Invoke(Sub() Me.DisplayCount(n, "Cancelled"))
Thread.Sleep(1)
Catch ex As Exception
'TODO: Handle other exceptions.
End Try
End Sub
using Me.CheckForIllegalCrossThreadCalls = False is not the right approach.
Basically, Cross-thread operation not valid exception is raised when a control is being updated from a thread other than the thread it was created on.
Each control exposes a InvokeRequired property that allows it to be updated in a thread-safe manner.
Therefore the right way to update the label is to use code like -
Private Delegate Sub UpdateLabelDelegate(i As Integer)
Private Sub UpdateLabel(i As Integer)
If Label1.InvokeRequired Then
Dim del As New UpdateLabelDelegate(AddressOf UpdateLbl)
Label1.Invoke(del, New Object() {i})
'Me.Refresh()
Else
' this is UI thread
End If
End Sub
Private Sub UpdateLbl(i As Integer)
Label1.Text = i.ToString()
End Sub
Delegate.BeginInvoke will execute the method on a thread pool thread. Once the method returns, the thread is returned to the pool.
So basically instead of starting a new thread, you will asynchronously execute the method using Delegate.BeginInvoke

New Multi-threading not working

I'm trying to create a thread so when I click a button it creates a new PictureBox from a class, this is how far I've got but nothing comes up on the screen at all.
Form1 code:
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
pgClass.thread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private trd As Thread
Public Sub thread()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = False
trd.Start()
End Sub
Private Sub NewUIThread()
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
Application.DoEvents()
Threading.Thread.Sleep(100)
Next
counter += 1
End Sub
End Class
I have posted something similar before on here but it was different, the pictureBoxes were showing on the screen but I was trying to get them to move at the same time but they wouldn't move, they only moved one at a time. The question that I asked before was this Multi threading classes not working correctly
I made a few assumptions for this answer so it may not work for you out of the box but I think it will put you on the right track without using any Thread.Sleep calls because I personally don't like building intentional slows to my apps but that's a personal preference really.
So For my example I just used a bunch of textboxes because I didn't have any pictures handy to fiddle with. But basically to get it so that the user can still interact with the program while the moving is happening I used a background worker thread that is started by the user and once its started it moves the textboxes down the form until the user tells it to stop or it hits an arbitrary boundary that I made up. So in theory the start would be the space bar in your app and my stop would be adding another control to the collection. For your stuff you will want to lock the collection before you add anything and while you are updating the positions but that is up to your discretion.
So the meat and potatoes:
in the designer of the form I had three buttons, btnGo, btnStop and btnReset. The code below handles the click event on those buttons so you will need to create those before this will work.
Public Class Move_Test
'Flag to tell the program whether to continue or to stop the textboxes where they are at that moment.
Private blnStop As Boolean = False
'Worker to do all the calculations in the background
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker
'Controls to be moved.
Private lstTextBoxes As List(Of TextBox)
'Dictionary to hold the y positions of the textboxes.
Private dtnPositions As Dictionary(Of Integer, Integer)
Public Sub New()
' Default code. Must be present for VB.NET forms when overwriting the default constructor.
InitializeComponent()
' Here I instantiate all the pieces. The background worker to do the adjustments to the position collection, the list of textboxes to be placed and moved around the form
' and the dictionary of positions to be used by the background worker thread and UI thread to move the textboxes(because in VB.NET you can not adjust controls created on the UI thread from a background thread.
bgWorker = New System.ComponentModel.BackgroundWorker()
Me.lstTextBoxes = New List(Of TextBox)
Me.dtnPositions = New Dictionary(Of Integer, Integer)
For i As Integer = 0 To 10
Dim t As New TextBox()
t.Name = "txt" & i
t.Text = "Textbox " & i
'I used the tag to hold the ID of the textbox that coorelated to the correct position in the dictionary,
' technically you could use the same position for all of them for this example but if you want to make the things move at different speeds
' you will need to keep track of each individually and this would allow you to do it.
t.Tag = i
dtnPositions.Add(i, 10)
'Dynamically position the controls on the form, I used 9 textboxes so i spaced them evenly across the form(divide by 10 to account for the width of the 9th text box).
t.Location = New System.Drawing.Point(((Me.Size.Width / 10) * i) + 10, dtnPositions(i))
Me.lstTextBoxes.Add(t)
Next
'This just adds the controls to the form dynamically
For Each r In Me.lstTextBoxes
Me.Controls.Add(r)
Next
End Sub
Private Sub Move_Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'Don't need to do anything here. Placeholder
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Try
If Not bgWorker.IsBusy Then
'User starts the movement.
bgWorker.RunWorkerAsync()
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
Try
'Reset the positions and everything else on the form for the next time through
' I don't set the blnStop value to true in here because it looked cooler to keep reseting the textboxes
' and have them jump to the top of the form and keep scrolling on their own...
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, 10)
Next
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) = 10
Next
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub bgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'This is where we do all the work.
' For this test app all its doing is scrolling through each value in the dictionary and incrementing the value
' You could make the dictionary hold a custom class and have them throttle themselves using variables on the class(or maybe travel at an angle?)
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) += 1
Next
Catch ex As Exception
blnStop = True
End Try
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Try
'Once the background worker is done updating the positions this function scrolls through the textboxes and assigns them their new positions.
' We have to do it in this event because we don't have access to the textboxes on the backgroun thread.
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, dtnPositions(CInt(r.Tag)))
Next
'use linq to find any textboxes whose position is beyond the threshhold that signifies they are down far enough.
' I chose the number 100 arbitrarily but it could really be anything.
Dim temp = From r In Me.lstTextBoxes Where r.Location.Y > (Me.Size.Height - 100)
'If we found any textboxes beyond our threshold then we set the top boolean
If temp IsNot Nothing AndAlso temp.Count > 0 Then
Me.blnStop = True
End If
'If we don't want to stop yet we fire off the background worker again and let the code go otherwise we set the stop boolean to false without firing the background worker
' so we will be all set to reset and go again if the user clicks those buttons.
If Not Me.blnStop Then
bgWorker.RunWorkerAsync()
Else
Me.blnStop = False
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Try
'The user clicked the stop button so we set the boolean and let the bgWorker_RunWorkerCompleted handle the rest.
Me.blnStop = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
End Class
Theres a lot of code there but a lot of it is comments and I tried to be as clear as possible so they are probably a little long winded. But you should be able to plop that code on a new form and it would work without any changes. I had the form size quite large (1166 x 633). So I think that's when it works best but any size should work(smaller forms will just be more cluttered).
Let me know if this doesn't work for your application.
This is a problem that is well suited to async/await. Await allows you to pause your code to handle other events for a specific period of time..
Private Async Function Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) As Task Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
await pgClass.NewUIThread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private Async Function NewUIThread() As Task
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
await Task.Delay(100) 'The await state machine pauses your code here in a similar way to application.doevents() until the sleep has completed.
Next
counter += 1
End Sub
End Class

after using background worker also my application get stuck,,how i can resolve

I am working on windows form application :
in my form am filling my datagrid view in some interval.so some time my application getting stuck..so i used back ground worker and timer
in back ground worker i am calling my function to fill the my data grid view.i set Timer Interval as 10000. in background worker i given code like this:
Private Sub BackgroundWorker1_DoWork
Call Fetch_Info()
End Sub
in Timer click event i given code like thise:
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
my Fetch_Info() function like this:
Dim cnt As Integer
Dim tbarcodedgv As String
Dim totaltbarcode As String
cnt = DGVall.RowCount
Dim tbar As String
Dim locTable As New DataTable
locTable.Columns.Add("carid", GetType(String))
If cnt > 0 Then
For i = 0 To cnt - 2
tbarcodedgv = DGVall.Rows(i).Cells(0).Value
locTable.Rows.Add(tbarcodedgv)
Next
End If
Dim flag As Boolean = False
Dim dcnt As Integer = DGVall.RowCount
Dim trid As Integer
Dim tbarcode As String
Dim keyloc As String
Dim cmd23 As New SqlCommand("IBS_fetchrequested", con.connect)
cmd23.CommandType = CommandType.StoredProcedure
cmd23.Parameters.Add("#tid", SqlDbType.Int).Value = tid
If cnt > 1 Then
Dim tvp1 As SqlParameter = cmd23.Parameters.AddWithValue("#Tbaroced", locTable)
tvp1.SqlDbType = SqlDbType.Structured
tvp1.TypeName = "dbo.TBarcode"
End If
dr = cmd23.ExecuteReader
While dr.Read
flag = False
tbarcode = dr("TBarcode")
If flag = False Then
If dr("keyloc") Is DBNull.Value Then
keyloc = ""
Else
keyloc = dr("keyloc")
End If
Dim row0 As String() = {tbarcode, keyloc, "", "Release"}
DGVall.Rows.Add(row0)
AxWindowsMediaPlayer1.URL = "C:\Beep.mp3"
End If
End While
dr.Close()
con.disconnect()
While your background worker runs in another thread than your GUI you are manipulating the Datagridview that's running in the GUI's thread. This should usually not work at all but it is probably the reason, why your GUI hangs while the BGW is running.
Try splitting the work: The time consuming fetching of data from the database is carried out in the Backgroundworker's DoWork event handler and you set the results as the e.Result value of the EventArgs variable in the DoWork function.
Then you handle the Backgroundworker's RunWorkerCompleted event and there you quickly update your datagridview with the results you set in the DoWork method. That way your GUI has nothing to do with the actual time consuming task and will only be affected by the quick update of your datagridview.
The code example for this is:
Public Class Form1
Private WithEvents LazyBGW As New System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'This code runs in the UI-Thread
LazyBGW.RunWorkerAsync()
End Sub
Private Sub LazyBGW_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles LazyBGW.DoWork
'This code runs in the BGW-Thread
Dim a As Integer = 0
For i = 1 To 5
a += 1
'I'm a lazy worker, so after this hard work I need to...
Threading.Thread.Sleep(1000) 'This locks up the BGW-Thread, not the UI-thread
Next
'Work is done, put results in the eventargs-variable for further processing
e.Result = a
End Sub
Private Sub LazyBGW_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LazyBGW.RunWorkerCompleted
'This code runs in the UI-Thread
Dim results As Integer = CInt(e.Result) 'e.Result contains whatever you put into it in the DoWork() method
MessageBox.Show("Finally the worker is done and our result is: " & results.ToString)
End Sub
End Class