Author Topic: Invoke or BeginInvoke cannot be called on a control until the window handle has  (Read 5158 times)

qwideman

  • Newbie
  • *
  • Posts: 41
    • View Profile
I'm getting the following error: "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."at line 612 of ModbusTcpComm and I haven't been able to get rid of it. I am trying to monitor the PLC using a "backgroundworker" component on a  form that I want to keep minimized most of the time. However, that form should show a progressbar to indicate that the program is running. There may be a better approach for what I am trying to do, I'm open.

Regardless, I would like to know what I did wrong.
I got rid of unnecessary parts, and now I have just one form, with a ModbusTcpComm component (PLC), and a Background worker on it.



' I added the following code at the bottom of MainForm.vb. Notice that this contains an "End Class", which replaces the one that's there.   

Code: [Select]

 Dim asd As New secondclass

    Private Sub MainForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        MsgBox(asd.readPLC(40100))
    End Sub
End Class

Public Class secondclass
    Public Function readPLC(ByVal address As UInteger)
        Return MainForm.PLC.Read(address)
    End Function
End Class

Any help would be much appreciated!

Thanks,
Quinton Wideman
Rolan Inc

Archie

  • Administrator
  • Hero Member
  • *****
  • Posts: 5323
    • View Profile
    • AdvancedHMI
You don't want to initiate a read on the load event. The driver pushes a DataReceived event back to the main UI thread faster than Windows creates a handle for the form.

You can use a timer by starting it in the load event and starting the background worker in the timer tick event. This will still be a race which may work on some PCs and not others.

The best route would be to setup a timer, in the timer check Me.IsHandleCreated, if true then execute the code and disable the timer, otherwise leave the timer enabled so it will continue to check until the handle is created.

qwideman

  • Newbie
  • *
  • Posts: 41
    • View Profile
Ok... that makes sense. I made those changes, but still some difficulty. Now I have :

Code: [Select]
    Dim asd As New secondclass

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        If Me.IsHandleCreated = True Then
            Timer1.Enabled = False
            BackgroundWorker1.RunWorkerAsync()
        End If
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        'MsgBox(Me.PLC.Read(40100))
        MsgBox(asd.readPLC(40100))
    End Sub
End Class
Public Class secondclass

    Public Function readPLC(ByVal address As UInteger)
        Return MainForm.PLC.Read(address)
    End Function

End Class


It still doesn't work, same error. I realize that I wouldn't have to use the second class to do this, but this is just a small part of my code, and I want to use this class for multiple objects in my program. I know that the commented line works, but I want to make use of the second class for testing purposes. Can you please explain?

Archie

  • Administrator
  • Hero Member
  • *****
  • Posts: 5323
    • View Profile
    • AdvancedHMI
As a rule of thumb in Object Oriented Programming, you don't want a hard coded reference to an external instance. But your code should still work.

In the code snippet you are showing, the readPLC does not use any instance members of the class, so it should be a shared member:

Public Shared Function readPLC(.....

Check your driver and make sure the SynchronizingObject property is set to your form.

qwideman

  • Newbie
  • *
  • Posts: 41
    • View Profile
I appreciate your advice. I'll try to brush up on my technique later, but I'd like to see this working for now.


I know I can do this
Code: [Select]
Me.PLC.Read(40100)  'line A
from class "Mainform"

But I can't do this:
Code: [Select]
MainForm.PLC.Read(40100) 'line B
from my second class. If I comment "line B", I don't get the error. If I replace "line B" with "Return 100", it will happily return the value of 100.
So it seems to me that for some reason my instance of the second class isn't communicating with the ModbusTcpComm on MainForm.

PS: Yes, my driver's synchronizing property is set to mainform.  I'm still not understanding it, but I might be able to design a workaround.
      (Basically I made a class with custom functions for one register in the PLC: I can assign .name .address .emailalertsenabled .location_of_csv_file_to_save_to and etc. properties. I also wanted a function in this class that was supposed to read the value from the PLC. That way I could name each instance and read the current value from a function: temperature.currentvalue, pressure.currentvalue etc.)



Quinton Wideman
Rolan Inc



qwideman

  • Newbie
  • *
  • Posts: 41
    • View Profile
I think I'm getting this.

In my class (which has no form associated with it) I added a DataSubscriber as a property. Now myclass.thedatasubscriber.plcaddressvalue can be equal to = myclass.modbusoffset + myclass.plcaddress

It looks promising at this point; if someone has advice for me on style, feel free to let me know. I'm not totally sure if I can explain what Archie meant by "a hard-coded reference to an external instance".  Are  (thisinstance.property = 7 and thatinstance.property = 4) examples of that?
I eliminated the function readPLC when I couldn't get it working.



EDIT: Its working for me now. Thanks Archie.
« Last Edit: September 24, 2013, 05:23:42 PM by qwideman »

Archie

  • Administrator
  • Hero Member
  • *****
  • Posts: 5323
    • View Profile
    • AdvancedHMI
It looks promising at this point; if someone has advice for me on style, feel free to let me know. I'm not totally sure if I can explain what Archie meant by "a hard-coded reference to an external instance".  Are  (thisinstance.property = 7 and thatinstance.property = 4) examples of that?
To avoid a hard coded reference to an external instance, you would set up a property. Once you instantiate the class, set the property to the instance.

Code: [Select]
    '*****************************************************
    '* Property - Component to communicate to PLC through
    '*****************************************************
    Private m_ComComponent As AdvancedHMIDrivers.EthernetIPForCLXCom
    Public Property ComComponent() As AdvancedHMIDrivers.EthernetIPForCLXCom
        Get
            Return m_ComComponent
        End Get
        Set(ByVal value AsAdvancedHMIDrivers. EthernetIPForCLXCom)
            m_ComComponent = value
        End Set
    End Property

This when you create an instance, set the property. For example, in your MainForm code:
Code: [Select]
Dim MyClassInstance as MyClass
MyClassInstance=new MyClass()
MyClassInstance.ComComponent=Me.EthernetIPForCLXCom1

Then in your class you use m_ComComponent to read/write data

qwideman

  • Newbie
  • *
  • Posts: 41
    • View Profile
Thank you! That works great and is a lot neater. I didn't realize that I could add a Comm Driver directly to my class, so I was using my class to read a value from a DataSubscriber.

Now I would just use

Code: [Select]
Me.m_ComComponent.Read(40100)

Will this command cause interference? Are there any cases, such as simultaneous "reads" that would cause an error where a DataSubscriber might automatically prevent an error?


Thanks again!
Your reply explains a few things for me, I should look at vb in more depth.

Quinton

Archie

  • Administrator
  • Hero Member
  • *****
  • Posts: 5323
    • View Profile
    • AdvancedHMI
Will this command cause interference? Are there any cases, such as simultaneous "reads" that would cause an error where a DataSubscriber might automatically prevent an error?
The Allen Bradley Ethernet driver have been made more  thread safe as of version 3.5 by using a queue, but the other drivers can sometime become problematic when sending too many commands from various sources.