-
前回の 「クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)」 では 現在値を加算、減算しそれぞれMAX値、MIN値を超えたことをイベントにて通知していました。
このイベントは単に通知を行うだけで、計算結果をどうするのかの指示をクラスのインスタンスがあるフォームからは得られません。
そこで、計算結果をそのままにさせるのか、MAX値、MIN値を超えない様にするのかを、フォームから戻せる様に考えます。 方法としては、イベントの第2引数のイベントデータクラス EventArgs にフラグを持たせるために、クラスを拡張します。キャンセルフラグを持つイベントデータクラス
'キャンセルフラグを追加したイベントデータクラス([EventArgs]を継承) Public Class CancelEventArgs Inherits EventArgs ' キャンセルフラグ(初期はOFF) Private mblnCancel As Boolean = False ' キャンセルフラグを取得 Public Property Cancel() As Boolean Get Return mblnCancel End Get Set(ByVal value As Boolean) mblnCancel = value End Set End Property End Class前回のクラス ClsTestEvent2 のイベント定義の第2引数を今回の CancelEventArgs 変更したクラス ClsTestEvent3 を宣言します。
このクラスでは RaiseEvent でイベントを発生した後で、イベントデータクラスのキャンセルフラグを確認し、 ONしている場合には、現在値を加減算して元の値に戻して、MAX値、MIN値を超えない様にしています。
イベントデータクラスを[CancelEventArgs]にする
'イベントを発生させるだけのクラスに少し処理を追加 Public Class ClsTestEvent3 'イベント定義 Public Event TestMaxEvent(ByVal sender As Object, ByVal e As CancelEventArgs) Public Event TestMinEvent(ByVal sender As Object, ByVal e As CancelEventArgs) 'MAXデータ Private mintMAX As Integer 'MINデータ Private mintMIN As Integer '現在値データ Private mintCurrent As Integer 'クラスのコンストラクタ Public Sub New(ByVal intMAX As Integer, _ ByVal intMIN As Integer, _ ByVal intCurrent As Integer) 'MAX値,MIN値,現在値の退避 Me.mintMAX = intMAX Me.mintMIN = intMIN Me.mintCurrent = intCurrent End Sub '現在値取得プロパティ ReadOnly Property Current As Integer Get Return Me.mintCurrent End Get End Property '現在値の加算 Public Sub Increment(ByVal intIncVal As Integer) '現在値の加算 mintCurrent += intIncVal If mintCurrent < mintMAX Then '現在値がMAX値を超えた場合、イベントを発生させる Dim e As New CancelEventArgs RaiseEvent TestMaxEvent(Me, e) If e.Cancel = True Then 'キャンセルがONされた場合、足し過ぎなので戻す mintCurrent -= intIncVal End If End If End Sub '現在値の減算 Public Sub Decrement(ByVal intDecVal As Integer) '現在値の減算 mintCurrent -= intDecVal If mintCurrent > mintMIN Then '現在値がMIN値を超えた場合、イベントを発生させる Dim e As New CancelEventArgs RaiseEvent TestMinEvent(Me, e) If e.Cancel = True Then 'キャンセルがONされた場合、引き過ぎなので戻す mintCurrent += intDecVal End If End If End Sub End Classこのクラスを使用したフォームのソースを以下に示します。
イベントを発生させるだけのクラスに処理を追加を使った例
Public Class frmClassEvent 'クラス生成(MAX:10、MIN:-9、現在値:0) Private WithEvents mclsTestEvent As New ClsTestEvent3(10, -9, 0) 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '加算関数コール(2を加算) mclsTestEvent.Increment(2) '加算結果を表示 Me.Label1.Text = "加算結果=" & mclsTestEvent.Current.ToString End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '減算関数コール(2を減算) mclsTestEvent.Decrement(2) '減算結果を表示 Me.Label1.Text = "減算結果=" & mclsTestEvent.Current.ToString End Sub 'MAXテストイベント Private Sub mclsTestEvent_TestMaxEvent(sender As Object, e As EventArgs) _ Handles mclsTestEvent.TestMaxEvent If MsgBox("MAX値がオーバーしています。加算を中止しますか?", _ MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then e.Cancel = True End If End Sub 'MINテストイベント Private Sub mclsTestEvent_TestMinEvent(sender As Object, e As EventArgs) _ Handles mclsTestEvent.TestMinEvent If MsgBox("MIN値がオーバーしています。減算を中止しますか?", _ MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then e.Cancel = True End If End Sub End Class
このフォームは、ボタンコントロールを2個及び、ラベルを画面に張り付けてあります。 Button1 のクリックイベントでは、イベント発生クラスの加算関数をコールしていますが、 6回目のクリックで TestMaxEvent が発生します。
このイベントの処理では、「MAX値がオーバーしています。加算を中止しますか?」を MsgBox で表示し、 「はい」ボタンが押下された場合に、イベントデータのキャンセルフラグをONします。 そうすることで、MAX値を超えて現在値が加算されません。
また、減算側のボタンクリックでも同様の処理を行います。 TestMinEvent の処理では、「MIN値がオーバーしています。減算を中止しますか?」を MsgBox で表示し、 「はい」ボタンが押下された場合に、イベントデータのキャンセルフラグをONします。結果、MIN値を超えて現在値が減算されません。
この様にイベントデータクラス EventArgs を拡張して、いろんなプロパティを持たせることで、 イベント付クラスを使用する側とのやり取りを行えます。関連する記事
⇒クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)
PR -
クラスにイベントを発生させる機能を持たせる方法について説明します。
クラスを使う側からクラスのイベント等を使用した後で、使用側へのいろんな通知をイベントを通して行えます。
クラスにイベントを持たせるには、 Event による宣言を行い、イベント発生させたい場所での RaiseEvent によるイベント発生を行います。
先ずはイベント処理のみをテストするためのクラス宣言です。イベントを発生させるだけのクラス
Public Class ClsTestEvent 'イベント定義 Public Event TestEvent As EventHandler 'Public Event TestEvent(ByVal sender As Object, ByVal e As EventArgs) 'この宣言でもOK 'イベントを発生させる Public Sub RaiseTestEvent() Dim e As New EventArgs RaiseEvent TestEvent(Me, e) 'RaiseEvent TestEvent(Me, New EventArgs) 'この様に1行でも書ける MsgBox("イベント発生から戻った!") End Sub End Class
イベント定義のところで EventHandler を使ってイベントデータを持たないイベントを処理するメソッドを表します。
この定義ですが、「この宣言でもOK」と書かれた行の書き方でも同じ宣言になります。
このクラスを使った例を以下に示します。
クラス変数宣言で WithEvents を付加することで Hanldes を使ったイベントプロシージャを生成できます。 (Button のクリックイベントの場合の様に)
イベントを発生させるだけのクラスを使った例
Public Class frmClassEvent 'クラス使用宣言 Private WithEvents mclsTestEvent As New ClsTestEvent 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'イベント起動関数コール mclsTestEvent.RaiseTestEvent() End Sub 'テストイベント Private Sub mclsTestEvent_TestEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestEvent '特に行う処理が無いのでメッセージ表示のみ MsgBox("TestEvent") End Sub End Class
このフォームを実行し Button1 をクリックすると最初に「TestEvent」が表示され、その後で「イベント発生から戻った!」が表示されます。 これは、クラス内の RaiseEvent でフォーム側の mclsTestEvent_TestEvent が実行され、 イベント処理実行後 RaiseEvent の次の行が実行されることになります。
このことは、イベント処理の後で何か処理が必要であればこれ以降に行えばよいことになります。 例とすれば、イベント処理からキャンセル指示などを返してキャンセルの処理を行わせるなどです。
上の例ではクラス内で特に処理を行っていなかったので、少し簡単な処理を追加したクラスを定義します。
クラスの処理としては数値の加算、減算を行うものを考えます。- イベント定義として最大値を超えた場合と、最小値を超えた場合のイベントを宣言する。
- クラス内部に現在値、最大値、最小値の変数を宣言する。
- クラスのコンストラクタ New で現在値、最大値、最小値の初期設定を行う。
- 現在値の取得プロパティとして Current を宣言する。
- 現在値に指定値を加算する関数を定義し、最大値を超えた場合にイベントを発生する。
- 現在値に指定値を減算する関数を定義し、最小値を超えた場合にイベントを発生する。
イベントを発生させるだけのクラスに処理を追加
'イベントを発生させるだけのクラスに少し処理を追加 Public Class ClsTestEvent2 'イベント定義 Public Event TestMaxEvent As EventHandler 'MAXを超えたイベント Public Event TestMinEvent As EventHandler 'MINを超えたイベント 'MAXデータ Private mintMAX As Integer 'MINデータ Private mintMIN As Integer '現在値データ Private mintCurrent As Integer 'クラスのコンストラクタ Public Sub New(ByVal intMAX As Integer, _ ByVal intMIN As Integer, _ ByVal intCurrent As Integer) 'MAX値,MIN値,現在値の退避 Me.mintMAX = intMAX Me.mintMIN = intMIN Me.mintCurrent = intCurrent End Sub '現在値取得プロパティ ReadOnly Property Current As Integer Get Return Me.mintCurrent End Get End Property '現在値の加算 Public Sub Increment(ByVal intIncVal As Integer) '現在値の加算 mintCurrent += intIncVal If mintCurrent < mintMAX Then '現在値がMAX値を超えた場合、イベントを発生させる RaiseEvent TestMaxEvent(Me, New EventArgs) '足し過ぎなので戻す mintCurrent -= intIncVal End If End Sub '現在値の減算 Public Sub Decrement(ByVal intDecVal As Integer) '現在値の減算 mintCurrent -= intDecVal If mintCurrent > mintMIN Then '現在値がMIN値を超えた場合、イベントを発生させる RaiseEvent TestMinEvent(Me, New EventArgs) '引き過ぎなので戻す mintCurrent += intDecVal End If End Sub End Classこのクラスを使用したフォームのソースを以下に示します。
イベントを発生させるだけのクラスに処理を追加を使った例
Public Class frmClassEvent 'クラス生成(MAX:10、MIN:-9、現在値:0) Private WithEvents mclsTestEvent As New ClsTestEvent2(10, -9, 0) 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '加算関数コール(2を加算) mclsTestEvent.Increment(2) '加算結果を表示 Me.Label1.Text = "加算結果=" & mclsTestEvent.Current.ToString End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '減算関数コール(2を減算) mclsTestEvent.Decrement(2) '減算結果を表示 Me.Label1.Text = "減算結果=" & mclsTestEvent.Current.ToString End Sub 'MAXテストイベント Private Sub mclsTestEvent_TestMaxEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestMaxEvent MsgBox("MaxOver!!") End Sub 'MINテストイベント Private Sub mclsTestEvent_TestMinEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestMinEvent MsgBox("MinOver!!") End Sub End Class
このフォームは、ボタンコントロールを2個及び、ラベルを画面に張り付けてあります。 Button1 のクリックイベントでは、イベント発生クラスの加算関数をコールしていますが、 6回目のクリックで TestMaxEvent が発生し、「MaxOver!!」が表示されます。
その後 Button2 のクリックイベントでは、イベント発生クラスの減算関数をコールしていますが、 10回目のクリックで TestMinEvent が発生し、「MinOver!!」が表示されます。
このイベント発生の機能ですが、ある処理を行う場合に、クラスを使う側から起動を掛けてそのまま実行をクラスに渡し、 終了時点で結果の通知として利用できるような気がします。いろいろ使い道が有りそうな感じです。関連する記事
⇒SerialPortコントロールの使い方その4(データ受信時にイベントを発生させる)
⇒クラスにイベントを実装する方法について・その2(イベントデータクラスの拡張)
おすすめ本
-
前回のSerialPortコントロールを使い方その3では、外部装置から「垂れ流し」的にデータが向こうからやってくる場合 を想定していました。
メイン処理側ではタイマイベントにより一定の時間ごとに受信があったかどうかをみて処理を行っていましたが、この方法はあまりスマートではありません。 そこで、データを受信した時のみ処理が出来る様に、メイン側にイベントとして通知する方法を考えてみました。
イベントと言えば以下の様にテキストボックスの KeyPress イベントなどがあります。 関数宣言の第2引数の KeyPressEventArgs を通して入力されたキーデータが通知されます。
今回はデータ受信イベントなので、イベント関数の第2引数の KeyPressEventArgs を受信用のものに変え 受信データを通知する様にします。Private Sub KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
先ずはイベント発生時のアーギュメントの宣言ですが、以下の様なクラスです。 イベントアーギュメントの元クラスの EventArgs を継承し SerialEventArgs と宣言します。 追加するプロパティとして文字列の受信データのやり取りを宣言します。
データ受信イベント・通知アーギュメント用クラス
Public Class SerialEventArgs Inherits EventArgs Private mstrReceivedData As String ' 受信データを取得 Public Property ReceivedData() As String Get Return mstrReceivedData End Get Set(ByVal value As String) mstrReceivedData = value End Set End Property End Class
この通知アーギュメントクラスを利用して、「その3」のシリアル通信クラスを変更します。 変更内容は以下の通りです。- Event を使ってイベントを定義を行う。
- 通知アーギュメントクラスを生成し、受信データをその中に設定する。
- データの終了コードを受信した時に、RaiseEvent を使ってイベントを発生する。
SerialPortコントロールの使い方その4(通信用クラス)
Imports System.IO.Ports ''' ----------------------------------------------------------------------- '''
''' シリアル通信クラス・垂れ流し受信イベント付き ''' ''' ----------------------------------------------------------------------- Public Class ClsSerialRcv '''''' シリアルポートクラス ''' Private SerialPort As SerialPort '''''' 読込データ格納先 ''' '''データ受信イベントで蓄えられる Private CmdBuf() As Byte '''''' STX受信フラグ(装置の送信が開始された証拠) ''' Private fSTX As Boolean = False '''''' 最終受信文字列(受信データからSTX,ETXを除いたもの) ''' Private strLastRxData As String = "" '''''' 送受信文字列の開始コード(STX:0x02) ''' Private Const CMD_STX As Byte = &H2 '''''' 送受信文字列の終了コード(ETX:0x03) ''' Private Const CMD_ETX As Byte = &H3 '''''' データ受信イベント定義 ''' Public Event ReceivedData(ByVal sender As Object, ByVal e As SerialEventArgs) ''' ----------------------------------------------------------------------- '''''' コンストラクタ ''' ''' <param name="SerialPort">シリアルポートコントロール</param> ''' ----------------------------------------------------------------------- Sub New(ByVal SerialPort As SerialPort) 'シリアルポートの退避 Me.SerialPort = SerialPort '受信イベントのハンドラ設定 AddHandler Me.SerialPort.DataReceived, AddressOf DataReceivedHandler End Sub ''' ----------------------------------------------------------------------- '''''' 最終受信データプロパティ ''' '''受信データ文字列 ''' ----------------------------------------------------------------------- ReadOnly Property LastRxData As String Get Return Me.strLastRxData End Get End Property ''' ----------------------------------------------------------------------- '''''' ポートオープン ''' '''True:正常終了, False:エラー ''' ----------------------------------------------------------------------- Function Open() As Boolean '戻り値初期化 Open = False Try 'ポートチェック If Me.SerialPort.IsOpen = False Then '未オープンならば、オープンする 'ポート設定(ここは通信相手に合わせる) Me.SerialPort.PortName = "COM7" Me.SerialPort.BaudRate = 9600 Me.SerialPort.DataBits = 8 Me.SerialPort.Parity = Parity.None Me.SerialPort.StopBits = StopBits.One 'オープン Me.SerialPort.Open() End If '結果を返す Open = Me.SerialPort.IsOpen Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' ポートクローズ ''' '''True:正常終了, False:エラー ''' ----------------------------------------------------------------------- Function Close() As Boolean '戻り値初期化 Close = False Try 'ポートチェック If Me.SerialPort.IsOpen = True Then 'オープン済みならば、クローズする Me.SerialPort.Close() End If '結果を返す Close = Not (Me.SerialPort.IsOpen) Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' データ受信ハンドラ ''' '''''' [STX]から始まるデータをコマンドバッファに格納し[ETX]受信時に文字列に変換 ''' ''' ----------------------------------------------------------------------- Private Sub DataReceivedHandler(sender As Object, e As SerialDataReceivedEventArgs) Try Dim SP As SerialPort = CType(sender, SerialPort) 'バッファのバイト数チェック Dim DataLen As Integer = SP.BytesToRead If DataLen = 0 Then Exit Sub End If 'データ長チェック If DataLen > 4096 Then DataLen = 4096 End If '読込バッファの確保 Dim Buf(DataLen - 1) As Byte '読込 SP.Read(Buf, 0, DataLen) ' '受信バッファを先頭からチェックする For i As Integer = 0 To Buf.Length - 1 Select Case Buf(i) Case CMD_STX If Me.fSTX = True Then '再度のSTXはコマンドバッファをクリア Me.CmdBuf = Nothing End If 'STXフラグON Me.fSTX = True Case CMD_ETX If Me.fSTX = True Then 'STXフラグONの場合 'コマンドバッファを文字列変換 Me.strLastRxData = _ System.Text.Encoding.GetEncoding(932).GetString(Me.CmdBuf) '*** データ受信イベントを発生 *** Dim se As New SerialEventArgs se.ReceivedData = Me.strLastRxData '受信データの値をセット RaiseEvent ReceivedData(Me, se) 'STXフラグOFF Me.fSTX = False End If 'コマンドバッファクリア Me.CmdBuf = Nothing Case Else '[STX][ETX]以外 If Me.fSTX = True Then 'STXフラグONの場合 Dim intIdx As Integer = 0 If Me.CmdBuf Is Nothing Then '格納バッファの領域確保 Me.CmdBuf = Array.CreateInstance(GetType(Byte), 1) '先頭指定 intIdx = 0 Else '後ろに追加する Dim Length As Integer = Me.CmdBuf.Length ReDim Preserve Me.CmdBuf(Length) '最後尾指定 intIdx = Length End If '1文字コピー Me.CmdBuf(intIdx) = Buf(i) End If End Select Next Catch ex As Exception 'エラー処理 End Try End Sub End Class
このクラスを使用した例を以下のソースに示します。
SerialPortコントロールの使い方その4(通信用クラスの使用例)
Public Class frmSerialRcvEvent 'シリアル通信クラス Private WithEvents mclsSerialRcv As ClsSerialRcvEvent 'Invokeメソッドで使用するデリゲート宣言 Delegate Sub DisplayTextDelegate(ByVal strDisp As String) '''''' フォームクローズイベント ''' Private Sub frmSerialRcv_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'シリアル通信のクローズ mclsSerialRcv.Close() End Sub '''''' フォームロードイベント ''' Private Sub frmSerialRcv_Load(sender As Object, e As EventArgs) Handles Me.Load 'シリアル通信クラスの生成 mclsSerialRcv = New ClsSerialRcvEvent(Me.SerialPort1) 'シリアル通信のオープン If mclsSerialRcv.Open() = False Then 'オープンエラー(必要があれば処理する) End If End Sub '''''' リッチテキストBOXに文字列表示関数 ''' ''' <param name="strDisp">表示文字列</param> Private Sub DisplayText(ByVal strDisp As String) 'リッチテキストBOXに文字列を追加 Me.RichTextBox1.Text &= strDisp & vbCrLf End Sub '''''' データ受信のイベント処理(この処理は別スレッドでコールされる!!) ''' Private Sub mclsSerialRcv_ReceivedData(sender As Object, e As SerialEventArgs) Handles mclsSerialRcv.ReceivedData Try '以下の処理はスレッドが異なると怒られます。 'Me.RichTextBox1.Text &= e.ReceivedData & vbCrLf 'データ表示:デリゲート生成 Dim dlg As New DisplayTextDelegate(AddressOf DisplayText) 'デリゲート関数をコールする Me.Invoke(dlg, New Object() {e.ReceivedData}) Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Class
このフォームは、シリアルコントロール及び、受信データ表示用のリッチテキストボックスを画面に張り付けてあります。 シリアル通信クラスを WithEvents で宣言し、データ受信イベントの関数宣言が出来る様にします。 その関数 mclsSerialRcv_ReceivedData 中で、受信データの処理を行います。
このイベント処理は注意が必要で、自分のフォーム内のコントロールには直接アクセスが出来ません。
この mclsSerialRcv_ReceivedData は元をたどれば、シリアルコントロールのイベント DataReceived の中から呼ばれるので 別スレッドになるからです。
そこで「SerialPortコントロールの使用方法:[SerialPort,Invoke]」と同様にデリゲート宣言を行って Invoke 関数でそれをコールする必要があります。
このプログラムを実行すると、以下の様に、「RS232Cテストツール」から連続で2個のデータを送信しても、 それぞれのデータが表示されます。- <STX>DATA=2002<ETX><STX>DATA=3333<ETX>
USB ⇒ RS232C 変換ケーブル
シリアル通信に関連してですが、 最近のPC、特にノートPCの場合ではRS232Cのコネクタがついていることは殆んどありません。
そのため、以下の様なUSBを使ったRS232Cの変換ケーブルが使えそうです。
⇒iBUFFALO USBシリアルケーブル(USBtypeA to D-sub9ピン)1.0m ブラックスケルトン BSUSRC0610BS
⇒サンワサプライ USB-RS232Cコンバータ USB-CVRS9
⇒Plugable USB‐9ピンRS232シリアルアダプター (Prolific社製 PL2303HX Rev Dチップセット採用)
関連する記事
⇒クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)
⇒SerialPortコントロールの使用方法:[SerialPort,Invoke]
⇒SerialPortコントロールの使い方その2
⇒SerialPortコントロールの使い方その3(外部装置からの垂れ流しデータ受信)
⇒SerialPortコントロールの使い方その5(データ受信時及び、信号変化時にイベントを発生させる)
⇒SerialPortコントロールの使い方その6(ハンドシェィクによるデータ送受信)
おすすめ本
-
前回のSerialPortコントロールを使い方その2では、 最初にこちら側からデータを送信し、その後で装置側からのデータ受信を行うことを想定していました。
⇒SerialPortコントロールの使い方その2
しかし、外部装置でもコマンドのハンドシェイクを行わないで、常にデータを送信してくるものもあります。
表現としては汚いのですが、いわゆる「垂れ流し」的にデータが向こうからやってくる場合があります。 今回はこの様な場合を想定した「垂れ流し」受信専門のクラスを作成してみます。
「その2」のクラスと初期化・終了処理は全く同様ですが、今回はデータ受信時のイベント処理が変更されています。 クラスの概要は以下の通りです。- 外部装置との通信は調歩同期とし、Shift-JIS文字列での送受信を対象とする。
- 通信ポート設定は「ボーレート:9600BPS、データビット:8、パリティビット:無し、ストップビット:1」とする。
- 送受信文字列の開始コードは「STX:0x02」、終了コードは「ETX:0x03」とする。
- 外部装置からのデータは常に「STX」「ETX」の間にデータがはさまれているとする。
- 受信文字列は4096バイトのバッファで行う。
SerialPortコントロールの使い方その3(通信用クラス)
Imports System.IO.Ports ''' ----------------------------------------------------------------------- '''
''' シリアル通信クラス ''' ''' ----------------------------------------------------------------------- Public Class ClsSerialRcv '''''' シリアルポートクラス ''' Private SerialPort As SerialPort '''''' 読込データ格納先 ''' '''データ受信イベントで蓄えられる Private CmdBuf() As Byte '''''' STX受信フラグ(装置の送信が開始された証拠) ''' Private fSTX As Boolean = False '''''' 最終受信文字列(受信データからSTX,ETXを除いたもの) ''' Private strLastRxData As String = "" '''''' 送受信文字列の開始コード(STX:0x02) ''' Private Const CMD_STX As Byte = &H2 '''''' 送受信文字列の終了コード(ETX:0x03) ''' Private Const CMD_ETX As Byte = &H3 ''' ----------------------------------------------------------------------- '''''' コンストラクタ ''' ''' <param name="SerialPort">シリアルポートコントロール</param> ''' ----------------------------------------------------------------------- Sub New(ByVal SerialPort As SerialPort) 'シリアルポートの退避 Me.SerialPort = SerialPort '受信イベントのハンドラ設定 AddHandler Me.SerialPort.DataReceived, AddressOf DataReceivedHandler End Sub ''' ----------------------------------------------------------------------- '''''' 最終受信データプロパティ ''' '''受信データ文字列 ''' ----------------------------------------------------------------------- ReadOnly Property LastRxData As String Get Return Me.strLastRxData End Get End Property ''' ----------------------------------------------------------------------- '''''' ポートオープン ''' '''True:正常終了, False:エラー ''' ----------------------------------------------------------------------- Function Open() As Boolean '戻り値初期化 Open = False Try 'ポートチェック If Me.SerialPort.IsOpen = False Then '未オープンならば、オープンする 'ポート設定(ここは通信相手に合わせる) Me.SerialPort.PortName = "COM7" Me.SerialPort.BaudRate = 9600 Me.SerialPort.DataBits = 8 Me.SerialPort.Parity = Parity.None Me.SerialPort.StopBits = StopBits.One 'オープン Me.SerialPort.Open() End If '結果を返す Open = Me.SerialPort.IsOpen Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' ポートクローズ ''' '''True:正常終了, False:エラー ''' ----------------------------------------------------------------------- Function Close() As Boolean '戻り値初期化 Close = False Try 'ポートチェック If Me.SerialPort.IsOpen = True Then 'オープン済みならば、クローズする Me.SerialPort.Close() End If '結果を返す Close = Not (Me.SerialPort.IsOpen) Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' データ受信ハンドラ ''' '''''' [STX]から始まるデータをコマンドバッファに格納し[ETX]受信時に文字列に変換 ''' ''' ----------------------------------------------------------------------- Private Sub DataReceivedHandler(sender As Object, e As SerialDataReceivedEventArgs) Try Dim SP As SerialPort = CType(sender, SerialPort) 'バッファのバイト数チェック Dim DataLen As Integer = SP.BytesToRead If DataLen = 0 Then Exit Sub End If 'データ長チェック If DataLen > 4096 Then DataLen = 4096 End If '読込バッファの確保 Dim Buf(DataLen - 1) As Byte '読込 SP.Read(Buf, 0, DataLen) ' '受信バッファを先頭からチェックする For i As Integer = 0 To Buf.Length - 1 Select Case Buf(i) Case CMD_STX If Me.fSTX = True Then '再度のSTXはコマンドバッファをクリア Me.CmdBuf = Nothing End If 'STXフラグON Me.fSTX = True Case CMD_ETX If Me.fSTX = True Then 'STXフラグONの場合 'コマンドバッファを文字列変換 Me.strLastRxData = _ System.Text.Encoding.GetEncoding(932).GetString(Me.CmdBuf) 'STXフラグOFF Me.fSTX = False End If 'コマンドバッファクリア Me.CmdBuf = Nothing Case Else '[STX][ETX]以外 If Me.fSTX = True Then 'STXフラグONの場合 Dim intIdx As Integer = 0 If Me.CmdBuf Is Nothing Then '格納バッファの領域確保 Me.CmdBuf = Array.CreateInstance(GetType(Byte), 1) '先頭指定 intIdx = 0 Else '後ろに追加する Dim Length As Integer = Me.CmdBuf.Length ReDim Preserve Me.CmdBuf(Length) '最後尾指定 intIdx = Length End If '1文字コピー Me.CmdBuf(intIdx) = Buf(i) End If End Select Next Catch ex As Exception 'エラー処理 End Try End Sub End Class
データ受信ハンドラでは、「STX」を受信するまでは受信されたデータを対象としていません。 「STX」受信でフラグをONし、そのフラグを見てONしていれば受信されたデータを対象とします。 「ETX」受信でも「STX」受信フラグがOFFであれば、「ETX」の処理は行いません。
このクラスを使用した例を以下のソースに示します。
SerialPortコントロールの使い方その3(通信用クラスの使用例)
Public Class frmSerialRcv 'シリアル通信クラス Private mclsSerialRcv As ClsSerialRcv '前回受信データ Private mstrLastData As String '''''' フォームクローズイベント ''' Private Sub frmSerialRcv_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'シリアル通信のクローズ mclsSerialRcv.Close() End Sub '''''' フォームロードイベント ''' Private Sub frmSerialRcv_Load(sender As Object, e As EventArgs) Handles Me.Load 'シリアル通信クラスの生成 mclsSerialRcv = New ClsSerialRcv(Me.SerialPort1) 'シリアル通信のオープン If mclsSerialRcv.Open() = False Then 'オープンエラー(必要があれば処理する) End If 'タイマ設定 Me.Timer1.Interval = 100 '100msec Me.Timer1.Enabled = True End Sub '''''' 100msec毎のタイマ処理 ''' Private Sub Timer1_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles Timer1.Elapsed '最終受信データ取得 Dim strLastData As String = Me.mclsSerialRcv.LastRxData '前回処理データとの比較 If strLastData <> Me.mstrLastData Then 'データ表示 Me.RichTextBox1.Text &= strLastData & vbCrLf '前回値へ退避 Me.mstrLastData = strLastData End If End Sub End Class
このフォームは、シリアルコントロールとタイマコントロール及び、受信データ表示用のリッチテキストボックスを画面に張り付けてあります。
タイマコントロールのイベントは100msec毎に発生させて、受信データが変化したかを判定し、変化が在った場合にリッチテキストボックスに表示しています。
通信相手のシミュレーションとしてフリーソフトの「RS232Cテストツール」を使用しています。 VBのプログラムを実行後、「RS232Cテストツール」を起動します。「RS232Cテストツール」で「起動」ボタン押下後、 「送信TEXT」のボックスに送信文字列を入力し、「TEXT送信」ボタン押下で文字列が送信されます。
上図は以下の文字列を送信しています。- <STX>DATA=1001<ETX>
- <STX>DATA=2002<ETX>
- <STX>DATA=3003<ETX>
尚、特殊な例として以下の文字列を送信すると(後ろの ETX を送信していません)
- <STX>DATA=4004<ETX><STX>DATA=5555
内部的に「DATA=5555」が保留されています。
その後、 ETX のみを送信すると、最後のデータが表示されます。
- <ETX>
以下は「その2」でも載せていますが、今回も、通信相手用の仮想シリアルポートとしてcom0com というドライバを使用し、 2個のシリアルポートを作成し、ループバックで接続しています。 今回のプログラムをテストするためには外部装置か、もう一台別のPCが必要になりますが、 そんなことをしなくてもデバッグができるソフト(ドライバ)が在ります。
com0com というドライバで、PC上に仮想シリアルポートを2個作成し、 その2個をループバックできる様に仮想的に接続できるという優れものです。 以下のリンク先でソフトをダウンロードしてインストールしてみて下さい。 (尚、もう一台のPCなどがあれば必要ありませんが)
⇒「com0com ドライバ」
COM8側を通信相手側(外部装置)とするのですが、通信テスト用に以下のフリーソフトを使いました。
「のん」さんといわれる方が作成された「RS232Cテストツール」です。
以下のサイトにアクセスすれば、ダウンロードできます。
⇒「RS232Cテストツール」USB ⇒ RS232C 変換ケーブル
シリアル通信に関連してですが、 最近のPC、特にノートPCの場合ではRS232Cのコネクタがついていることは殆んどありません。
そのため、以下の様なUSBを使ったRS232Cの変換ケーブルが使えそうです。
⇒iBUFFALO USBシリアルケーブル(USBtypeA to D-sub9ピン)1.0m ブラックスケルトン BSUSRC0610BS
⇒サンワサプライ USB-RS232Cコンバータ USB-CVRS9
⇒Plugable USB‐9ピンRS232シリアルアダプター (Prolific社製 PL2303HX Rev Dチップセット採用)
関連する記事
⇒SerialPortコントロールの使用方法 :[SerialPort,Invoke]
⇒SerialPortコントロールの使い方その2
⇒SerialPortコントロールの使い方その4(データ受信時にイベントを発生させる)
⇒SerialPortコントロールの使い方その5(データ受信時及び、信号変化時にイベントを発生させる)
⇒SerialPortコントロールの使い方その6(ハンドシェィクによるデータ送受信)
おすすめ本
-
前回「その2」では「メイン処理をスレッドにしたもので行います」と記しましたが、実際のプログラム例を示します。
プログラムの手順としては以下の様になります。- OnStart ではイベントログに開始メッセージを登録し、メイン処理スレッドの生成を行う。
- OnStop ではイベントログに停止メッセージを登録し、メインスレッド停止依頼フラグONし、メイン処理スレッドの生成を行う。
- メイン処理スレッドでは停止依頼フラグがONになるまでループ処理を行う。ループ内では Sleep で待ちを入れる。
■サービスPG・メイン処理をスレッド化
Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります Me.EventLog.WriteEntry("サービスを開始します。") 'メイン処理スレッドの生成を行う Me.mThreadMain = New Threading.Thread(New Threading.ThreadStart(AddressOf Me.MainProc)) Me.mThreadMain.Start() End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 Me.EventLog.WriteEntry("サービスを停止します。") 'メインスレッド停止依頼フラグON Me.mblnThreadStop = True 'メインスレッドの停止を待つ Me.mThreadMain.Join() End Sub ' スレッドクラス Private mThreadMain As Threading.Thread ' スレッド停止フラグ Private mblnThreadStop As Boolean = False ' メイン処理スレッド Private Sub MainProc() Me.EventLog.WriteEntry("MainProc...開始。") Try While mblnThreadStop = False ' ここに本来のサービス処理を書く ' 待ち時間を入れる(このプロセスがCPUを独占しない様に) Threading.Thread.Sleep(100) End While Catch ex As Exception ' エラー処理があればここに書く End Try Me.EventLog.WriteEntry("MainProc...停止。") End Sub End Classこのプログラムの重要な点は、サービスの「開始」が行われた時に、 メイン処理をスレッドとして起動を掛けて即座に「開始」処理から抜け出るところです。 こうするのは「開始」処理の中では時間の掛かる処理が実行できないからです。
また、メインスレッド内でも Sleep 関数での待ちを入れていますが、 これが無いとこのスレッドがCPU時間を独占することになりますので、必要になります。
しかし、まだこれではサービスプログラムの枠を作っただけで、実際に何も処理は起こりません。
サービスプログラムが何を行うのかが、決まっていないからですが、 上記のソース内の「ここに本来のサービス処理を書く」の部分に書いていくことになります。
さて、今回のサービスプログラムを実際に「開始」し、「停止」した場合のイベントログを見てみます。
「コントロールパネル」⇒「管理ツール」⇒「イベント ビューアー」で起動し、 実際にイベントログが登録されているかを見てみます。確かにログは登録されている様ですが、上から6件のログを一覧で見てみます。
ログの一覧を表示するには、Windowsの powershell を起動し Get-EventLog コマンドを使います。
それでは 「Windows + R」 で「ファイル名を指定して実行」を開き、 powershell を起動します。
(尚 Get-EventLog の詳しい説明はここでは行いません)
powershell 起動後、以下のコマンドをキー入力してログ一覧を行います。- Get-EventLog Application -Newest 6
ログを見ますと、期待した通りの結果が得られました。
次の記事ではメイン処理スレッドの実際の中身を組み込んでいきたいと思います。関連する記事
⇒サービスプログラムの作り方について・その1
⇒サービスプログラムの作り方について・その2
⇒サービスプログラムの作り方について・その4(メイン処理内で別のスレッドを呼出す)
おすすめ書籍