- ベストアンサー
SerialPort処理でInvokeメソッドを使用するとエラーが発生。
はじめまして。こんばんわ。 同一プロジェクトの複数のフォームから、RS232C接続処理を行っております。 まったく同じ処理内容なので、クラスを使用しようとしております。 しかし、データ受信時、Invokeメソッドを使用して、各フォームのイベントをCALLしますと、『InvalidOperationException』が発生し、『ウィンドウ ハンドルが作成される前、コントロールで Invoke または BeginInvoke を呼び出せません。』というエラーメッセージが表示されてしまいます。 ソースを下記に記載いたします。どなたか、原因・対処方法がわかる方がいらっしゃいましたら、御手数をおかけいたしますが、ご教示の程、よろしくお願い申し上げます。 ============================== 呼び出し元フォーム ============================== Public Class Form1 Private cls232CIns As cls232C Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click cls232CIns.openport() End Sub Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load cls232CIns = New cls232C() End Sub Public Sub DispData(ByVal data As String) TextBox1.Text = data End Sub End Class ============================== SerialPort通信クラス ============================== Imports System.IO.Ports Public Class cls232C WithEvents SP1 As SerialPort Delegate Sub RecvDataDisp(ByVal dataR As String) Public Sub New() SP1 = New SerialPort("COM6", 9600) End Sub Public Sub openport() SP1.Open() End Sub Public Sub closeport() SP1.Close() End Sub Public Sub ReceiveData(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived Dim getdata As String getdata = SP1.ReadLine Form1.Invoke(New RecvDataDisp(AddressOf Form1.DispData), getdata) ←ここでエラー発生 End Sub End Class
- みんなの回答 (2)
- 専門家の回答
質問者が選んだベストアンサー
ReceiveData()では、「Form1」という暗黙のインスタンスが参照されています。 同一プロジェクトの複数のフォームから、とありますので 例えばForm1のインスタンスが無い状態でForm2上のcls232Cが機能した場合、 Form1.DispDataという関数のアドレスはForm1のインスタンスが無いので存在しないことになります。 (暗黙のインスタンスを利用して)特定のフォームを指すというクラスの設計がそもそもおかしいですよね。 「Form1」が必ず無いとcls232Cは動作しませんからね。 まずは暗黙のインスタンスを排除する必要があると思います。 例えば、Form_Loadでは cls232CIns = New cls232C(Me) とし、クラスでは Private m_Form As Form Public Sub New(oForm As Form) SP1 = New SerialPort("COM6", 9600) m_Form = oForm End Sub Invokeメソッドは m_Form.Invoke(New RecvDataDisp(AddressOf m_Form.DispData), getdata) とすればいいです。要は、呼び出し元のフォームを教えてあげ、Invokeするのも呼び出し元のFormに限定するわけです。 ただ、これは呼び出すフォームは必ずDispData()の実装を(訳もなく)強いられるという点など、マイナス点の多い実装です。 私ならこうします。 Imports System.IO.Ports Public Class cls232C Public Event Received(ByVal getdata As String) WithEvents SP1 As SerialPort ’~略~ Public Sub ReceiveData(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived Dim getdata As String getdata = SP1.ReadLine RaiseEvent Received(getdata) End Sub こうするとForm上のcls232CにはReceivedというイベントが出来ているはずです。 Private Sub cls232CIns_Received(ByVal getdata As String) Handles cls232CIns.Received TextBox1.Text = getdata End Sub
その他の回答 (1)
- redfox63
- ベストアンサー率71% (1325/1856)
Invokeでっていうより Eventで処理したほうがいいかもしれないですよ clsC232Cの定義に Public Event ReciveDataDisp( ByVal sData as String ) として定義して Public Sub ReceiveData(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived Dim getdata As String getdata = SP1.ReadLine RaiseEvent ReciveDataDisp( setdata ) End Sub といった具合に記述しておきます 呼び出すフォーム側では WithEvent cls232CIns As cls232C 定義して ReciveDataのイベントハンドラを定義してやればいいように思います Private Sub cls232CIns_ReciveData(ByVal sData As String) Handles cls232CIns.ReciveDataDisp TextBox1.Text = sData End Sub
お礼
redfox63様、早速のご回答の程、ありがとうございました。 下記のpiyo2000様へのお返事の方に記述いたしましたが、 RaiseEventと、Invokeを使用して、やっと受信データを画面に表示することができました! 『Eventで処理』について、これからもう少し勉強していきたいと思います! まだまだ私は不勉強なところも多いのですが、いろいろご教示いただきまして、本当にありがとうございました!
補足
piyo2000様、早速のご回答の程、ありがとうございました。 ※『お礼』の長さが1000Byteまでで記述できなかったので、『補足』の方に記述させていただきます。 早速RaiseEventを使用してみたのですが、フォームの > TextBox1.Text = getdata の箇所で、InvalidOperationExceptionエラーが発生してしまいました。 内容は、『有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'TextBox1' がアクセスされました。』とのことです。 調査してみました所、DataReceivedイベントハンドラはセカンダリスレッドから呼び出され、 Windows.Forms の UI 要素にアクセスする場合は Control.Invoke() メソッドを使用する必要がある・・・ とのことでした。 頂きましたご回答と、『同じフォーム内のみでSerialPortデータを受信する』というサンプルを 組合せて(?)、下記のようにしたら動作いたしました! 迅速にいろいろとご教示いただき、本当にありがとうございました! ============================== 呼び出し元フォーム ============================== Public Class Form1 'Private cls232CIns As cls232C ←削除 WithEvents cls232CIns As cls232C ←追加 Delegate Sub RecvDataDisp(ByVal dataR As String) ←追加 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click cls232CIns.openport() End Sub Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load cls232CIns = New cls232C() End Sub Private Sub cls232CIns_Received(ByVal getdata As String) Handles cls232CIns.Received ←追加 //セカンダリスレッドからメインスレッド呼び出し Me.Invoke(New RecvDataDisp(AddressOf Me.DispData), getdata) End Sub Public Sub DispData(ByVal data As String) TextBox1.Text = data Debug.WriteLine("受信データ=" & data) End Sub End Class ============================== SerialPort通信クラス ============================== Imports System.IO.Ports Public Class cls232C2 Public Event Received(ByVal getdata As String) ←追加 WithEvents SP1 As SerialPort 'Delegate Sub RecvDataDisp(ByVal dataR As String) ←削除 Public Sub New() SP1 = New SerialPort("COM6", 9600) End Sub Public Sub openport() SP1.Open() End Sub Public Sub closeport() SP1.Close() End Sub Public Sub ReceiveData(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived Dim getdata As String getdata = SP1.ReadLine 'Form1.Invoke(New RecvDataDisp(AddressOf Form1.DispData), getdata) ←削除 RaiseEvent Received(getdata) ←追加 End Sub End Class