I am writing a VB form application that redirects the standard output stream of a process and uses it in a UI.
I am having trouble calling methods with parameters that update controls on the form from the OutputHandler sub.
I can call a method without parameters like so
Me.Invoke(New MyDelSub(AddressOf ServerStarted))
Which works fine.
And a bit of googling told me that to call a method with parameters I should do this:
Dim del As JoinDelegate = AddressOf PlayerJoins
del.Invoke(username)
With this delegate and method pair:
Private Delegate Sub JoinDelegate(ByVal username As String)
Private Sub PlayerJoins(ByVal username As String)
PlayersBox.Items.Add(username)
'Do other stuff
End Sub
But this produces an IllegalOperationException the first time the method tries to access a control.
1) Supposing you have a method like this:
Public Sub DoSomething(value1 As String, value2 As String)
MessageBox.Show(String.Format("{0} {1}", value1, value2))
End Sub
You can call it using invoke this way:
Me.Invoke(Sub() DoSomething("Hello", "World!"))
2) If you want to make thread safe call to a control you can write the method this way:
Public Sub AddItemToListBox1(item As String)
If (ListBox1.InvokeRequired) Then
ListBox1.Invoke(Sub() AddItemToListBox1(item))
Else
ListBox1.Items.Add(item)
End If
End Sub
Then it's enough to call it in a the UI thread or in another thread the same way simply:
AddItemToListBox1("some item")
The call would be thread safe.
E.g.
Private Sub SetControlText(control As Control, text As String)
If control.InvokeRequired Then
control.Invoke(New Func(Of Control, String)(AddressOf SetControlText), control, text)
Else
control.Text = text
End If
End Sub
Call that method from any thread.
Related
Since I can't put in parameters, how can I respect the following signature?
Private Sub SetFocusToRow(ByRef ultraGridRow As Infragistics.Win.UltraWinGrid.UltraGridRow)
grdSoldeOuverture.ActiveCell = ultraGridRow.Cells(0)
grdSoldeOuverture.PerformAction(Infragistics.Win.UltraWinGrid.UltraGridAction.EnterEditMode)
End Sub
When I call it like this
Me.BeginInvoke(New MethodInvoker(AddressOf Me.SetFocusToTemplateAddRow))
I'm on .NET 2.0 in Visual Studio 2005 with Microsoft Visual Basic 2005 so a lambda expression is not an option.
You can use a lambda to capture the requirements and pass them in:
Foo arg = GetTheFoo()
BeginInvoke(New MethodInvoker(Sub() SetFoo(arg)))
Edit:
First, change your method to not pass ByRef - this is unnecessary:
Private Sub SetFocusToRow(ByVal ultraGridRow As Infragistics.Win.UltraWinGrid.UltraGridRow)
grdSoldeOuverture.ActiveCell = ultraGridRow.Cells(0)
grdSoldeOuverture.PerformAction(Infragistics.Win.UltraWinGrid.UltraGridAction.EnterEditMode)
End Sub
Next, define a delegate:
' Define your delegate:
Delegate Sub SetFocusToRowDelegate(ByVal ultraGridRow As Infragistics.Win.UltraWinGrid.UltraGridRow)
Then you can call via:
BeginInvoke(new SetFocusToRowDelegate(AddressOf SetFocusToRow), arg)
Since lambdas cause a problem you could try implementing them manually using an object:
Class FooCurry
Private bar as Foo
Private Sub new (foo as Foo)
bar = foo
End Sub
Public sub DoFoo()
bar.SetFoo()
EndSub
End Class
dim foocurry as new FooCurry(foo)
BeginInvoke(New MethodInvoker(AdressOf foocurry.DoFoo))
This is how lambdas are implemented under the hood, so this should work. You could generalise the object to take a delegate and use it in more places.
I'm doing this:
Delegate Sub SetTextBoxText_Delegate(ByVal [Label] As TextBox, ByVal [text] As String)
' The delegates subroutine.
Public Sub SetTextBoxText_ThreadSafe(ByVal [Label] As TextBox, ByVal [text] As String)
' InvokeRequired required compares the thread ID of the calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If [Label].InvokeRequired Then
MsgBox("invoke")
Dim MyDelegate As New SetTextBoxText_Delegate(AddressOf SetTextBoxText_ThreadSafe)
Me.Invoke(MyDelegate, New Object() {[Label], [text]})
Else
MsgBox("noinvoke")
[Label].Text = [text]
End If
End Sub
However it always uses noinvoke. If I try setting it normaly it gives me a thread-safe warning and doesn't work. If I force invoke then it says the control isn't created?
Could someone help?
It's most likely because the control has not yet been created when you try to access it. Wait until the control has loaded, or check it using Label.Created. Like so:
Public Sub SetTextBoxText_ThreadSafe(ByVal Label As TextBox, ByVal text As String)
If Label.Created Then
If Label.InvokeRequired Then
MsgBox("invoke")
Dim MyDelegate As New SetTextBoxText_Delegate(AddressOf SetTextBoxText_ThreadSafe)
Me.Invoke(MyDelegate, New Object() {Label, text})
Else
MsgBox("noinvoke")
Label.Text = text
End If
End If
End Sub
P.S. You don't need a custom delegate type, just use Action(Of TextBox, String). You also don't need square brackets around Label or text.
I'm having some trouble getting cross-thread communication/field-updating working properly in my VB.NET 2010 program. I'm trying to update a field on my main form whenever a thread that I've started throws an event. Here's a simplified version of my code:
My main form:
Public Class Main
' stuff
' Eventually, startProcessing gets called:
Private Sub startProcessing()
Dim processingClass = New MyProcessingClass("whatever")
AddHandler processingClass.processStatusUpdate, AddressOf handleProcessStatusUpdate
Dim processingThread = New Thread(AddressOf processingClass.process)
processingThread.Start()
End Sub
Private Sub handleProcessStatusUpdate(statusUpdate As String)
txtMainFormTextBox.Text = statusUpdate ' InvalidOperationException
' "Cross-threaded operation not valid: Control 'txtMainFormTextBox' accessed from a thread other than the thread it was created on"
End Sub
End Class
The class which raises the event:
Public Class MyProcessingClass
Private whatever As String
Public Event processStatusUpdate(status As String)
Public Sub New(inWhatever As String)
whatever = inWhatever
End Sub
Public Sub process()
' do some stuff
RaiseEvent processStatusUpdate(whatever)
End Sub
End Class
As you can see, the handler in my main class doesn't have access to the TextBox I need since it was triggered by a different thread (I think). I've tried a number of other approaches to get this working, including:
Moving the event handler to MyProcessingClass, and passing txtMainFormTextBox by reference (ByRef) to the class.
Having the actual thread start inside of MyProcessingClass instead of Main.
None of these have worked. Clearly there's a concept that I'm missing here. What's the best way to get this done? Thanks!
You need to update the textbox on the UI thread by calling BeginInvoke.
You should use the BackgroundWorker component, which does all of this for you.
Simply handle the DoWork and ProgressChanged events.
In my application, I have a MainWindow with a ToolStripProgressBar and a ToolStripStatusLabel.
This properties:
Property ProgressBarPercantage() As Integer Implements BCSXPSearchTool.Presenter.IMainView.ProgressPercentage
Get
Return Me._progressbarpercentage
End Get
Set(ByVal value As Integer)
Me._progressbarpercentage = value
Me.StatusStripCurrentProgressBar.Value = Me._progressbarpercentage
End Set
End Property
Private _progressbarpercentage As Integer = 0
Property ProgressStatusText() As String Implements BCSXPSearchTool.Presenter.IMainView.ProgressStatusText
Get
Return Me._progressstatustext
End Get
Set(ByVal value As String)
Me._progressstatustext = value
Me.StatusStripCurrentState.Text = Me._progressstatustext
End Set
End Property
Private _progressstatustext As String = "Ready"
In the MainWindowPresenter I start a new BackgroundWorker which should read from a database.
Public Sub Search()
Dim bw As New BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf runproc
If bw.IsBusy = False Then
bw.RunWorkerAsync()
End If
End Sub
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
statusToSub.Invoke("Test")
'percToSub.Invoke(50)
End Sub
Public Sub percTo(ByVal value As Integer)
_view.ProgressPercentage = value
End Sub
Public Sub statusTo(ByVal value As String)
_view.ProgressStatusText = value
End Sub
Delegate Sub delegateStatusTo(ByVal value As String)
Delegate Sub delegatePercTo(ByVal value As Integer)
The code above is working. But if I change the sub runproc() to:
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
' statusToSub.Invoke("Test")
percToSub.Invoke(50)
End Sub
It doesn't work. I get an exception:
InvalidOperationException
I got the text in english and can't translate it to english very well but I think something like:
The access to the control, created by another thread from another thread is not allowed.
I'm using Visual Studio 2008 Express + VB 2.0.
Thank you!
This is due to cross-thread UI access which is disallowed (but for every UI access, so your other code shouldn’t work either!). The easiest solution is to use BeginInvoke when required:
Public Sub statusTo(ByVal value As String)
If InvokeRequired Then
BeginInvoke(New Action(Of String)(AddressOf statusTo))
Return
End If
_view.ProgressStatusText = value
End Sub
Furthermore, #vulkanino’s comment is spot-on: your calls should be direct method calls, not delegate invocations.
Dim statusToSub As **new** delegateStatusTo(AddressOf WriteToDebug)
statusToSub.Invoke("Test")
Dim percToSub As **new** delegatePercTo (AddressOf percTo)
percToSub.Invoke(50)
It looks like you are attempting to access UI controls from the DoWork event handler. Remember, that event handler is running on a worker thread. You are not allowed to touch any UI control from a thread other than the one that created it. There is a ProgressChanged event that will be marshaled onto the UI thread automatically upon calling ReportProgress. You safely update the UI from this event.
I need to create multiple threads when a button is clicked and i've done that with this:
Dim myThread As New Threading.Thread(AddressOf getFile)
myThread.IsBackground = True
myThread.Start()
but i need to update a picture box with the downloaded file, buy if i set an event in the function getFile and raise it to notify that the files was downloaded and then update the picturebox.
Use an AsyncResult, and either check it periodically for completion, or provide a delegate to be called when the thread has completed its work.
A complete example in VB can be found here.
You need to make use of MethodInvoker deligate.
Public Sub GetFile()
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(GetFile))
End If
End Sub
Now you can handle any event in your specified class.
You can achive that using the Asyncallback, ...
Dim sinctotal As New Del_sinc(AddressOf sincronizar)
Dim ar As IAsyncResult = sinctotal.BeginInvoke(_funcion, type, New AsyncCallback(AddressOf SincEnd), cookieobj)
The cookieobj is this
Class Cookie
Public id As String
Public AsyncDelegate As [Delegate]
Sub New(ByVal id As String, ByVal asyncDelegate As [Delegate])
Me.id = id
Me.AsyncDelegate = asyncDelegate
End Sub
End Class
When the delegate finish it will call the funcion Sincend (in this example), then you could use a event to update your picture.
Hope this helps!