AdvancedHMI Software
General Category => Support Questions => Topic started by: qwideman on September 23, 2013, 11:40:37 AM
-
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.
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
-
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.
-
Ok... that makes sense. I made those changes, but still some difficulty. Now I have :
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?
-
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.
-
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
Me.PLC.Read(40100) 'line A
from class "Mainform"
But I can't do this:
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
-
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.
-
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.
'*****************************************************
'* 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:
Dim MyClassInstance as MyClass
MyClassInstance=new MyClass()
MyClassInstance.ComComponent=Me.EthernetIPForCLXCom1
Then in your class you use m_ComComponent to read/write data
-
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
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
-
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.