アプリ版:「スタンプのみでお礼する」機能のリリースについて

はじめまして。こんばんわ。
同一プロジェクトの複数のフォームから、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
 

A 回答 (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

この回答への補足

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

補足日時:2009/08/17 10:46
    • good
    • 0

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
    • good
    • 0
この回答へのお礼

redfox63様、早速のご回答の程、ありがとうございました。

下記のpiyo2000様へのお返事の方に記述いたしましたが、
RaiseEventと、Invokeを使用して、やっと受信データを画面に表示することができました!

『Eventで処理』について、これからもう少し勉強していきたいと思います!

まだまだ私は不勉強なところも多いのですが、いろいろご教示いただきまして、本当にありがとうございました!

お礼日時:2009/08/17 10:52

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています