I have two forms, Form1 and Newform. Form1 has two buttons and a textbox and Newform has its own textbox. I am using a settext sub to invoke a delegate sub in the backgroundworker to update the textbox in both forms.
The textbox in Form1 seems to be updating but the textbox in Newform isn't updating.
Is there something that I'm missing if I want to update the textbox on a different form?
Thanks in advance.
Imports System.Threading
Public Class Form1
Dim stopbit As Boolean
Dim TestingComplete As Boolean
Dim ReadValue As Double
Dim FinalValue As Double
Delegate Sub SetTextCallback(ByRef Txtbox As TextBox, ByVal Txt As String)
'Thread Safe textbox update routine
Private Sub SetText(ByRef Txtbox As TextBox, ByVal Txt 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.
Console.WriteLine(Txtbox.InvokeRequired & " textbox invokerequired")
If Txtbox.InvokeRequired Then
Try
'MsgBox("inside settext")
Txtbox.Invoke(New SetTextCallback(AddressOf SetText), Txtbox, Txt)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Else
Txtbox.Text = Txt
Txtbox.Update()
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
newform.Show()
End Sub
Function ReadTemp() As Double
ReadValue = ReadValue / 2
Return ReadValue
End Function
Sub Test()
Dim starttime As Integer
Dim EllapsedTime As Integer
Dim OldValue As Double = 0
Dim NewValue As Double = 0
Dim Difference As Double = 1
Dim Margin As Double = 0.1
stopbit = False
starttime = My.Computer.Clock.TickCount
Do
Thread.Sleep(200)
OldValue = NewValue
NewValue = ReadTemp()
Difference = Math.Abs(NewValue - OldValue)
SetText(Me.TextBox1, Difference.ToString)
SetText(newform.TextBox1, Difference.ToString)
newform.Refresh()
EllapsedTime = My.Computer.Clock.TickCount - starttime
Loop Until EllapsedTime > 5000 Or stopbit = True ' Or Difference < Margin
FinalValue = NewValue
TestingComplete = True
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
stopbit = True
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 10
ReadValue = 100000
TestingComplete = False
ThreadPool.QueueUserWorkItem(AddressOf Test)
Do
Thread.Sleep(200)
Loop Until TestingComplete = True
MsgBox("Final Value " & FinalValue)
Next
End Sub
End Class
Your issue is due to that you're using the default instance of newform
. In VB.NET default form instances is a feature that allows you to access a form via its type name without having to manually create an instance of it.
In other words it lets you do this:
newform.Show()
newform.TextBox1.Text = "Something"
...instead of doing it the correct way, which is this:
Dim myNewForm As New newform
myNewForm.Show()
myNewForm.TextBox1.Text = "Something"
Above we create a new instance of newform
called myNewForm
. This is required to be able to use most objects in the framework (including forms). However, VB.NET simplifies this behaviour by offering to create the instance for you, which is what is going on in my first example.
The problem with these default instances is that they are thread-specific, meaning a new instance is created for every thread that you use this behaviour in.
Thus the form you refer to when you do:
newform.Show()
...is not the same form that you refer to in your thread, because a new instance has been created for it in that thread:
'This is not the same "newform" as above!
SetText(newform.TextBox1, Difference.ToString)
The solution to this is of course to create the instance yourself, allowing you to have full control over what's going on:
Dim newFrm As New newform
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
newFrm.Show()
End Sub
...your code...
Sub Test()
...your code...
SetText(newFrm.TextBox1, Difference.ToString)
...even more of your code...
End Sub
As a side note you can remove your calls to newform.Refresh()
and Txtbox.Update()
. These just cause unnecessary overhead by forcing the form and text boxes to redraw themselves, which is already done when you change any of their properties that affect their contents/design (so you are essentially making them redraw themselves twice).
Also, if you want to make invoking to the UI thread simpler and you are using Visual Studio/Visual Basic 2010 or newer, you could switch to using lambda expressions instead of regular delegates. They're much easier to use and allows you to create whole methods in-line that can be invoked on the UI thread.
For this purpose I've written an extension method called InvokeIfRequired()
which lets you invoke any method/function on the UI thread, checking InvokeRequired
for you. It's similar to what you have now, only it works for any control (not just text boxes) and with lambda expressions, allows you to run any code you want on the UI.
You can use it by adding a module to your project (Add New Item... > Module
) and naming it Extensions
. Then put this code inside it:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
This allows you to invoke either one line of code by doing:
Me.InvokeIfRequired(Sub() Me.TextBox1.Text = Difference.ToString())
Or to invoke a whole block of code by doing:
Me.InvokeIfRequired(Sub()
Me.TextBox1.Text = Difference.ToString()
newFrm.TextBox1.Text = Difference.ToString()
Me.BackColor = Color.Red 'Just an example of what you can do.
End Sub)