Using Events to update UI from a backgroundworker with another class - vb.net

I’m trying to use events to update a textbox from a background worker from a different class.
It is the same problem as mentioned in this SO post, except I'm using VB.NET. I'm trying to implement the 2nd suggested solution by #sa_ddam213.
I’m getting an error: “Cross-thread operation not valid: Control 'txtResult' accessed from a thread other than the thread it was created on.”
Here is my code:
Public Class DataProcessor
Public Delegate Sub ProgressUpdate(ByVal value As String)
Public Event OnProgressUpdate As ProgressUpdate
Private Sub PushStatusToUser(ByVal status As String)
RaiseEvent OnProgressUpdate(status)
End Sub
Public Sub ProcessAllFiles(ByVal userData As UserData)
'Do the work
End Sub
End Class
Public Class MainForm
Private bw As New BackgroundWorker
Private dp As New DataProcessor
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
If bw.CancellationPending = True Then
e.Cancel = True
Else
dp.ProcessAllFiles(CType(e.Argument, UserData))
End If
End Sub
Private Sub dp_OnProgressUpdate(ByVal status As String)
txtResult.Text = status
End Sub
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
AddHandler dp.OnProgressUpdate, AddressOf dp_OnProgressUpdate
End Sub
End Class
Thanks all!

The event still comes from a different thread than the UI. You need to delegate the woke back to the UI. I don't check InvokeRequired because I know it is from the worker thread.
Me is the form, Invoke asks for the delegate that will handle the work of bring the data to the UI thread. Here my Delegate Sub is a lambda Sub instead of using a normal Sub routine - simpler design.
Private Sub dp_OnProgressUpdate(ByVal status As String)
'invoke the UI thread to access the control
'this is a lambda sub
Me.Invoke(Sub
'safe to access the form or controls in here
txtResult.Text = status
End Sub)
End Sub

Maybe you could try doing something like this?
Private Delegate Sub del_Update(ByVal status As String)
Private Sub dp_OnProgressUpdate(ByVal status As String)
If txtResult.InvokeRequired Then
txtResult.Invoke(New del_Update(AddressOf dp_OnProgressUpdate), New Object() {status})
Else
target_textbox.text = status
End If
End Sub

Related

VB.net Cross-form Cross-thread form call?

I am working on a project which requires a separate form which is running a server thread to access and change another forms control location. I know how to call a control from another thread running on the same form but I'm not sure how to do it on a separate form and thread.
Here's a simple example...
Define the event in your server form, and raise it when appropriate:
Public Class frmServer
Public Event NewPosition(ByVal pt As Point)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' ... in response to something (probably not a button click!) ...
Dim x As Integer = 250
Dim y As Integer = 100
RaiseEvent NewPosition(New Point(x, y))
End Sub
End Class
In your main form, subscribe to that event when you create an instance of the server form. This can be done with the AddHandler statement and the AddressOf keyword. Then do the normal Invoke pattern to make sure it runs in the correct UI thread:
Public Class frmMain
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim server As New frmServer
AddHandler server.NewPosition, AddressOf server_NewPosition
server.Show()
End Sub
Private Delegate Sub dlgNewPosition(ByVal pt As Point)
Private Sub server_NewPosition(pt As Point)
If Me.InvokeRequired Then
Me.Invoke(New dlgNewPosition(AddressOf server_NewPosition), New Object() {pt})
Else
Me.Location = pt
End If
End Sub
End Class

VB.net AddHandler with index

I have a multithread program that download info from the internet off different proxies. I have it working fine but I have to add functions for each thread so that I know which thread is being processed. so if I want 10 thread I need 10 functions named processItems0 processItems1 processItems2 and so on. All my processItems0 function does is pass the data to another function with a index. I wish I could do something thing like processItems(0) so that I can have 1 function and didn't need a stack of if statements to track which webclient the data is coming from. I want it to support 100 thread if i wanted it to. what im doing works but it cant be the best way. Thanks in advance
Dim wc As New WebClient
''' if statements that i want to get rid of
If wcn = 0 Then
AddHandler wc.UploadStringCompleted, AddressOf processItems0
ElseIf wcn = 1 Then
AddHandler wc.UploadStringCompleted, AddressOf processItems1
end if
wc.Proxy = wp(wcn)
Dim u As New Uri(laurl)
wc.UploadStringAsync(u, data)
''' individual functions for each webclient i want to run.. t
Private Sub processItems0(ByVal sender As Object, ByVal e As UploadStringCompletedEventArgs)
If e.Cancelled = False AndAlso e.Error Is Nothing Then
processData(CStr(e.Result), 0)
End If
End Sub
Private Sub processItems1(ByVal sender As Object, ByVal e As UploadStringCompletedEventArgs)
If e.Cancelled = False AndAlso e.Error Is Nothing Then
processData(CStr(e.Result), 1)
End If
End Sub
Private Sub processData(data As String, wcn As Integer)
'process data
end Sub
Please remember to remove your event handlers to prevent memory leaks.
Public Class ProxyWrapper
Inherits WebClient
Private _index As Integer
Public Sub New(ByVal index As Integer)
_index = index
End Sub
Public ReadOnly Property Index As Integer
Get
Return _index
End Get
End Property
Public Sub RegisterEvent()
AddHandler Me.UploadStringCompleted, AddressOf processItems
End Sub
Public Sub UnregisterEvent()
RemoveHandler Me.UploadStringCompleted, AddressOf processItems
End Sub
Private Sub ProcessItems(ByVal sender As Object, ByVal e As UploadStringCompletedEventArgs)
If e.Cancelled = False AndAlso e.Error Is Nothing Then
ProcessData(CStr(e.Result), _index)
End If
End Sub
Private Sub ProcessData(ByVal res As String, ByVal index As Integer)
' Handle data
End Sub
End Class

Backgroundworker in VB not working?

Im writing an app that takes and copy's folders (selected by the user in a listbox) to a specified location, it has a progress bar that should run the whole time but it seems that the backgroundworker isnt catching the process.. it just freezes the UI until the copy is complete.. when I put in a message between copy's the message works so I know it is capable of working.. im missing something..
Private Sub Button4_Click_1(sender As Object, e As EventArgs) Handles Btn_SaveApps.Click
bw.WorkerSupportsCancellation = True
bw.WorkerReportsProgress = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
If Not bw.IsBusy = True Then
bw.RunWorkerAsync(Module1.SaveApp)
End If
End Sub
Module Module1
Function SaveApp() As Process
Dim WinStrApps As String = ("C:\TransferFrom")
Form1.tbProgress.Style = ProgressBarStyle.Marquee
For Each Item In Form1.Selected_Apps.SelectedItems
On Error Resume Next
Form1.tbProgress.Visible = True
Dim FileLoc = ("C:\TransferTo\")
If System.IO.Directory.Exists(WinStrApps + Item) = True Then
Directory.CreateDirectory(FileLoc + Item)
My.Computer.FileSystem.CopyDirectory(WinStrApps + Item, FileLoc + Item)
Else
MsgBox(Item + " does not exist, or is a system App. Please choose another application")
End If
Next
MsgBox("Completed saving the applications")
End Function
I think you may have called the thread wrong, the thread has to begin in the sub bw_DoWork
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Module1.SaveApp
end sub
and any updates passed back to the UI thread need to be passed to
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
Form1.tbProgress.value = e.ProgressPercentage
End Sub
however you wont be able to access the update routine from your sub as the routine is private of Form1... you may have to create a delegate in your module.

Why is my event not firing?

I am doing some transition coding, using the .net transitions library found # http://code.google.com/p/dot-net-transitions/. I am trying to add an event to fire on transitions completed. In my sub, I have the following statements:
Private Sub btnLogin_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLogin.Click
If md5Password = rtnPassHash Then
AddHandler Me.TransitionCompletedEvent, AddressOf theHandlerFunction
Dim tr_empID = New Transition(New TransitionType_Linear(500))
tr_empid.add(txtEmployeeID, "BackColor", Color.LightGreen)
Dim tr_passw = New Transition(New TransitionType_Linear(500))
tr_passw.add(txtPassword, "BackColor", Color.LightGreen)
tr_empID.run()
tr_passw.run()
AddHandler Me.TransitionCompletedEvent, AddressOf theHandlerFunction
Dim tr_empID = New Transition(New TransitionType_Linear(500))
tr_empid.add(txtEmployeeID, "BackColor", Color.LightGreen)
Dim tr_passw = New Transition(New TransitionType_Linear(500))
tr_passw.add(txtPassword, "BackColor", Color.LightGreen)
tr_empID.run()
tr_passw.run()
end if
end sub
Outside of that sub I have:
Public Event TransitionCompletedEvent As EventHandler(Of Transition.Args)
Private Sub theHandlerFunction(ByVal sender As Object, ByVal args As Transition.Args) Handles Me.TransitionCompletedEvent
MsgBox("Event Fired")
End Sub
However, the event is not firing after the transition has finished. Why would this be?
Basic design:
Public Class Transition
Public Event TransitionCompleted(args As Transition.Args)
Public Sub SomeSub()
RaiseEvent TransitionCompleted(New Transition.Args With {set some properties})
End Sub
...
End Class
Public Class Form1
Private transition1 As New Transition
Private Sub Login_Click(...) ...
...
Addhandler transition1.TransitionCompleted, AddressOf TransitionCompleted
End Sub
Private Sub TransitionCompleted(args As Transition.Args) ' no handles clause
MessageBox.Show("event fired")
End Sub
End Class

BackgroundWorker freezes GUI

I have read other posts about this but I still can't seem to get it to work right.
Whenever my BackgroundWorker begins to do work, my function API.CheckForUpdate causes the GUI to hang. I can't click on anything. It only freezes for half a second, but is enough to notice.
How can I fix this? Should I dive deeper into API.CheckForUpdate and run individual threads on particular statements, or can I just have an all-inclusive thread that handles this? API.CheckForUpdate does not reference anything in Form1.
Also, I presume Form1_Load is not the best place to put the RunWorkerAsync call. Where is a better spot?
'Declarations
Dim ApplicationUpdate As BackgroundWorker = New BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ApplicationUpdate.WorkerSupportsCancellation = True
ApplicationUpdate.WorkerReportsProgress = True
AddHandler ApplicationUpdate.DoWork, AddressOf ApplicationUpdate_DoWork
AddHandler ApplicationUpdate.ProgressChanged, AddressOf ApplicationUpdate_ProgressChanged
AddHandler ApplicationUpdate.RunWorkerCompleted, AddressOf ApplicationUpdate_RunWorkerCompleted
ApplicationUpdate.RunWorkerAsync()
End Sub
Private Sub ApplicationUpdate_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
'Check for an update (get the latest version)
Dim LatestVersion = API.CheckForUpdate
End Sub
Private Sub ApplicationUpdate_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
'Nothing here
End Sub
Private Sub ApplicationUpdate_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
'Work completed
MsgBox("Done")
End Sub
Its not a background worker Fix but if you don't mind walking around and not finding the answer, you can code like so:
Keep in mind when you first Start a Thread and you are coding in a Model you MUST pass (me) into the initial thread because of VB having a concept of "Default Form Instances". For every Form in the application's namespace, there will be a default instance created in the My namespace under the Forms property.
and that is just adding an additional parameter like so
----------------------/ Starting Main Thread /-----------------------------------
Private Sub FindCustomerLocation()
Dim Findcontractor_Thread As New Thread(AddressOf **FindContractor_ThreadExecute**)
Findcontractor_Thread.Priority = ThreadPriority.AboveNormal
Findcontractor_Thread.Start(me)
End Sub
------------------/ Running Thread /---------------
Private Sub **FindContractor_ThreadExecute**(beginform as *NameOfFormComingFrom*)
Dim threadControls(1) As Object
threadControls(0) = Me.XamDataGrid1
threadControls(1) = Me.WebBrowserMap
**FindContractor_WorkingThread**(threadControls,beginform) ' ANY UI Calls back to the Main UI Thread MUST be delegated and Invoked
End Sub
------------------/ How to Set UI Calls from a Thread / ---------------------
Delegate Sub **FindContractor_WorkingThread**(s As Integer,beginform as *NameOfFormComingFrom*)
Sub **FindContractor_WorkingThreadInvoke**(ByVal s As Integer,beginform as *NameOfFormComingFrom*)
If beginform.mouse.InvokeRequired Then
Dim d As New FindContractor_WorkingThread(AddressOf FindContractor_WorkingThreadInvoke)
beginform.Invoke(d, New Object() {s,beginform})
Else
beginform.Mouse.OverrideCursor = Cursors.Wait
'Do something...
beginform.Mouse.OverrideCursor = Nothing
End If
End Sub
Sources From Pakks Answer Tested!
Try starting the process outside the Load event. Create a Timer and start it on the Load event, and then handle the event for the tick:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
ApplicationUpdate.RunWorkerAsync()
End Sub