Link to home
Start Free TrialLog in
Avatar of jmsjms
jmsjmsFlag 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

Avatar of Daniel Junges
Daniel Junges
Flag of Brazil image

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
Avatar of jmsjms

ASKER

Thanks Junges.

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

ASKER

(The thread runs code held in a seperate class to the form)
Avatar of 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...

yes, must be sure that the Form1 is already initialized, otherwise it get this error
Avatar of 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
SOLUTION
Avatar of Daniel Junges
Daniel Junges
Flag of Brazil image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of 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?
Avatar of 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'.
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)
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
step into the Invoke to see if it is right referenced
Avatar of 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?

Avatar of 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

Avatar of 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.
Avatar of 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

Avatar of jmsjms

ASKER

Would still appreciate any comments!  :-)
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

Avatar of 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.
Avatar of jmsjms

ASKER

Thanks to both.  I've been trying to figure this out for months!