Avatar of jmsjms
jmsjms
Flag for United Kingdom of Great Britain and Northern Ireland asked on

Update Textbox from a thread

Hi Experts,

I'm trying to update a text box with a variable from a thread.  I've tried to get my head around delegates but the code below still doesnt work.

When I step through it, it seems that it fails the  Me.txtEvent.InvokeRequired() check when I would have thought it would be true.

Any idea where I'm going wrong?  BTW, this is a test project for a thread that will eventually report back at at various places in the code so I dont want to use a backgroundWorker thread.

Thanks

' Function pointer to method to update GUI
	Private Delegate Sub UpdateGUIDelegate(ByVal Bval As Integer)

	' Method to update the GUI
	Public Sub UpdateGUI(ByVal Bval As Integer)
		' Check to see who is calling this method. If other then GUI thread
		' create delegate to make the call.
		If Me.txtEvent.InvokeRequired() Then
			Dim myD1 As New UpdateGUIDelegate(AddressOf UpdateGUI)
			Me.txtEvent.Invoke(myD1, Bval)
		Else
			Me.txtEvent.Text = "A=" & Bval
		End If
	End Sub
#End Region


The code that the thread runs (in a seperate class)
	Public Sub WorkCount()
		While intA < 10000
			While intB < 100
				intB += 1
				System.Threading.Thread.Sleep(50)
			End While
			Form1.UpdateGUI(intA)
			intB = 0
			intA += 1
		End While
		intA = 0
	End Sub

Open in new window

Visual Basic.NET.NET Programming

Avatar of undefined
Last Comment
jmsjms

8/22/2022 - Mon
Daniel Junges

on you thread you can call directly the invoke
 Dim myD1 As New UpdateGUIDelegate(AddressOf Form1.UpdateGUI)
 Me.txtEvent.Invoke(myD1, Bval)

and change the content from UpdateGUI to:
Public Sub UpdateGUI(ByVal Bval As Integer)

   Me.txtEvent.Text = "A=" & Bval

End Sub
jmsjms

ASKER
Thanks Junges.

Do I need the delegate in the class holding the threads code or in the form code?
jmsjms

ASKER
(The thread runs code held in a seperate class to the form)
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
jmsjms

ASKER
I put the delegate in the seperate class and ran the app.  I get a

"Invoke or BeginInvoke cannot be called on a control until the window handle has been created."

error...

Daniel Junges

yes, must be sure that the Form1 is already initialized, otherwise it get this error
jmsjms

ASKER
Form1 is the apps main form.  when it's loaded, I then click on a button to start the thread that runs code in a seperate class.  Therefore the form is up and running well before the thread is started.

J
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
SOLUTION
Daniel Junges

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
jmsjms

ASKER
give a reference from main form to the class where run the thread like "myForm"
then make this change:

sorry, how do I do that?
jmsjms

ASKER
Well I did

      Dim myform As Form1

I changed the relevant code to

                  Dim myD1 As New UpdateGUIDelegate(AddressOf myform.UpdateGUI)
                  myform.txtEvent.Invoke(myD1, intA)

(note the link to myform on the invoke line)
But it still errors

Delegate to an instance method cannot have null 'this'.
Daniel Junges

Add a reference from you mainForm to the class where you have the thread,
when you click the button and run the thread then assing the reference from mainForm to the class like:
MyThreadClass.myForm = this;
the variable myForm you can make as static because it is the mainForm.

now you Thread can get the correct adress fromyou function:
Dim myD1 As New UpdateGUIDelegate(AddressOf myForm.UpdateGUI)
Me.txtEvent.Invoke(myD1, Bval)
This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
ASKER CERTIFIED SOLUTION
Yannick Lapierre

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
Daniel Junges

step into the Invoke to see if it is right referenced
jmsjms

ASKER
I'm getting in a muddle here.

To clarify:

DO I need to have the Private Delegate Sub UpdateGUIDelegate(ByVal Bval As Integer) line in the seperate class or the form code?

I need

                  Dim myD1 As New UpdateGUIDelegate(AddressOf myform.UpdateGUI)
                  myform.txtEvent.Invoke(myD1, intA)

in the code that the thread runs?

and

      Public Sub UpdateGUI(ByVal Bval As Integer)
            Me.txtEvent.Text = "A=" & Bval
      End Sub

in the forms code?

jmsjms

ASKER
Yannick - Thanks for that.  Together with some code I found this seems to have done the trick.

Could I ask - do you need the withEvents in the line         Dim WithEvents YourInstanceOfWorkingClass As YourWorkingClass   ?

I've dimed my class without the withevents but used addhandleer.  Is this OK.

Here's my code. It's complete, just the bits that are relevant.  Comments appreciated from both of you !  :-)

GUI (windows form) class
  Dim myWorkers(3) As JWorker
  Dim myThreads(3) As Threading.Thread
  Dim FlagStop As Boolean = False

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
	Dim I As Integer
	For I = 1 To 3
	myWorkers(I) = New JWorker(I)
	AddHandler myWorkers(I).JWorkerEvent, AddressOf Jeventrecieved
	myThreads(I) = New System.Threading.Thread(AddressOf myWorkers(I).WorkCount)
	myThreads(I).IsBackground = True
	Next
End Sub

Private Delegate Sub JeventDelegate(ByVal myInt As Integer)

Private Sub Jeventrecieved(ByVal myInt As Integer)
 If Me.InvokeRequired Then
   Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {myInt})
      Else
    Me.txtEvent.Text = "A=" & myInt
  End If
End Sub

Worker Class
Public Class JWorker
	Public Event JWorkerEvent(ByVal intV As Integer)

	Dim myform As Form1

	Public intA As Integer
	Public intB As Integer
	
	Public Sub New(ByRef ID As Integer)
		intA = 0
		intB = 0
	End Sub

	Public Sub WorkCount()
		While intA < 10000
			While intB < 100
				intB += 1
				System.Threading.Thread.Sleep(50)
			End While
			RaiseEvent JWorkerEvent(intA)
			intB = 0
			intA += 1
		End While
		intA = 0
	End Sub
End Class

Open in new window

⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
jmsjms

ASKER
In order to handle taking more info from the workerclass, I've added the event class (slightly changed from your code).

Public Class JWorkerEventArgs
      Inherits System.EventArgs
      Private _MyNumber As Integer

      Public ReadOnly Property MyNumber()
        Get
          Return _MyNumber
        End Get
      End Property

      Public Sub New(ByVal myNumber As Integer)
        _MyNumber = myNumber
      End Sub
End Class

Changed the raseEvent line to

 RaiseEvent JWorkerEvent(Me, New JWorkerEventArgs(intA))

Changed the event handler to

      Private Delegate Sub JeventDelegate(ByVal sender As System.Object, ByVal e As JWorkerEventArgs)
      Private Sub Jeventrecieved(ByVal sender As System.Object, ByVal e As JWorkerEventArgs)
            If Me.InvokeRequired Then
                  Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {e})
            Else
                  Me.txtEvent1.Text = sender.ToString & " " & e.MyNumber
            End If
      End Sub

When I run the app, I get a TargetParameterCountException was unhandled error in the line

Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {e})

Any ideas?

This is really helping me get to grips with something that's outfoxed me for months!  Thanks for your help.
jmsjms

ASKER
Ah,

Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {sender, e})

seemed to do it.

Anyway, here's the code
GUI (form Class)
===============

	Dim myWorkers(3) As JWorker
	Dim myThreads(3) As Threading.Thread
	Dim FlagStop As Boolean = False

	Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
		Dim I As Integer
		For I = 1 To 3
			myWorkers(I) = New JWorker(I)
			AddHandler myWorkers(I).JWorkerEvent, AddressOf Jeventrecieved
			myThreads(I) = New System.Threading.Thread(AddressOf myWorkers(I).WorkCount)
			myThreads(I).IsBackground = True
		Next
	End Sub

#Region "Update GUI"

	Private Delegate Sub JeventDelegate(ByVal sender As System.Object, ByVal e As JWorkerEventArgs)
	Private Sub Jeventrecieved(ByVal sender As System.Object, ByVal e As JWorkerEventArgs)
		If Me.InvokeRequired Then
			Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {sender, e})
		Else
			Me.txtEvent1.Text = sender & " " & e.MyNumber
		End If
	End Sub

Worker Class and Arguement class
===================================

Public Class JWorkerEventArgs
	Inherits System.EventArgs
	Private _MyNumber As Integer

	Public ReadOnly Property MyNumber()
		Get
			Return _MyNumber
		End Get
	End Property

	Public Sub New(ByVal myNumber As Integer)
		_MyNumber = myNumber
	End Sub
End Class

Public Class JWorker
	Public Event JWorkerEvent As EventHandler(Of JWorkerEventArgs)

	Public intA As Integer
	Public intB As Integer

	Property A() As Integer
		Get
			Return intA
		End Get
		Set(ByVal value As Integer)
			intA = A
		End Set
	End Property

	Property B() As Integer
		Get
			Return intB
		End Get
		Set(ByVal value As Integer)
			intB = B
		End Set
	End Property

	Public Sub New(ByRef ID As Integer)
		intA = 0
		intB = 0
	End Sub

	Public Sub WorkCount()
		While intA < 10000
			While intB < 100
				intB += 1
				System.Threading.Thread.Sleep(50)
			End While
			RaiseEvent JWorkerEvent(Me, New JWorkerEventArgs(intA))
			intB = 0
			intA += 1
		End While
		intA = 0
	End Sub
End Class

Open in new window

jmsjms

ASKER
Would still appreciate any comments!  :-)
I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
Yannick Lapierre

Salut,

Ok, AddHandler is a good solution.

I try your code, it work. I don't understand what your code should do.
You can test my rewriting code with ThreadPool, and dynamique List of thread. you can find some idea with that.



Public Class Form1

    Dim myWorkers As New List(Of JWorker)
    Dim FlagStop As Boolean = False

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        System.Threading.ThreadPool.SetMaxThreads(2, 100)

        For i As Integer = 1 To 3
            DoWork(i)
        Next

    End Sub

    Private Sub DoWork(ByVal ID As Integer)     ' Assume ID is unique.

        Dim MyWorker As New JWorker(ID)
        SyncLock myWorkers
            myWorkers.Add(MyWorker)
        End SyncLock
        AddHandler MyWorker.JWorkerWorking, AddressOf Jeventrecieved
        AddHandler MyWorker.JWorkerFinised, AddressOf RemoveFromList
        System.Threading.ThreadPool.QueueUserWorkItem(AddressOf MyWorker.WorkCount)

    End Sub

    Private Delegate Sub JeventDelegate(ByVal myInt As Integer, ByVal myID As Integer)

    Private Sub Jeventrecieved(ByVal myInt As Integer, ByVal myID As Integer)
        If Me.InvokeRequired Then
            Me.Invoke(New JeventDelegate(AddressOf Jeventrecieved), New Object() {myInt, myID})
        Else
            Me.txtEvent.Text = System.String.Format("A= {0} ({1})", myInt, myID)
        End If
    End Sub
    
    Private Sub RemoveFromList(ByVal myID As Integer)

        SyncLock myWorkers
            For i As Integer = 0 To myWorkers.Count - 1
                If myWorkers(i).ID = myID Then
                    myWorkers.RemoveAt(i)
                    Exit For
                End If
            Next
        End SyncLock

    End Sub

End Class

'Worker Class
Public Class JWorker

    Public Event JWorkerWorking(ByVal intV As Integer, ByVal ID As Integer)
    Public Event JWorkerFinised(ByVal ID As Integer)

    Public intA As Integer
    Public intB As Integer

    Public Property ID As Integer

    Public Sub New(ByVal id As Integer)
        Me.ID = id
        intA = 0
        intB = 0
    End Sub

    Public Sub WorkCount()
        While intA < 10000
            While intB < 100
                intB += 1
                System.Threading.Thread.Sleep(0)
            End While
            RaiseEvent JWorkerWorking(intA, Me.ID)
            intB = 0
            intA += 1
        End While
        intA = 0
        RaiseEvent JWorkerFinised(ID)
    End Sub

End Class

Open in new window

jmsjms

ASKER
THanks for the code.

My code is just a test application to see how I can get data from 3 threads that are continually running (unless they error).  

MANY THANKS for your help Guys.  As I'm mostly using Yannicks code solution I'll give him most of the points.
jmsjms

ASKER
Thanks to both.  I've been trying to figure this out for months!
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.