• ベストアンサー

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  

質問者が選んだベストアンサー

  • ベストアンサー
  • piyo2000
  • ベストアンサー率49% (144/293)
回答No.1

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

moruchi
質問者

補足

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

その他の回答 (1)

  • redfox63
  • ベストアンサー率71% (1325/1856)
回答No.2

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

moruchi
質問者

お礼

redfox63様、早速のご回答の程、ありがとうございました。 下記のpiyo2000様へのお返事の方に記述いたしましたが、 RaiseEventと、Invokeを使用して、やっと受信データを画面に表示することができました! 『Eventで処理』について、これからもう少し勉強していきたいと思います! まだまだ私は不勉強なところも多いのですが、いろいろご教示いただきまして、本当にありがとうございました!

関連するQ&A

専門家に質問してみよう