-
以前、以下の記事でテキストボックスの入力される文字を数字のみに限定するコントロールについて記述しましたが、 このコントロールは数値入力には適さないので、今回は少し使える様に入力の見た目は電卓の感じにしてみました。 (数字が1の位から左側へシフトしていきます。)
今回のコントロールの動きは以下の様になります。- 数値の表示は3桁毎にカンマ編集での表示とする。
- カーソル(キャレット)は常にコントロールの右端に存在し動かない。 (矢印キーは動作しない)
- マイナス値は「-」(マイナスキー)を押下することで数値をプラスとマイナスを反転する。
- 「BackSpace」キーで最終文字を削除する。
- クリップボードからのペーストは出来ない様にする。
前回のコントロールでは KeyPress イベントで入力されたキーの制御をしていましたが、 今回はほとんどの処理を KeyDown イベントで行い、 KeyPress イベントでの処理は全て行わない様にします。
今回の処理ではテキストボックスの Text プロパティへの再表示を自分で強制的に行っているため、 KeyPress イベントでの処理をスキップしないと表示がおかしくなるためです。 (文字がダブって表示されたりします)
今回の処理で肝となる KeyDown イベントでは以下のキー処理を行います。- [Enter]:[Ctrl]キーが押下されていない場合、自分の親のコントロールの次のフォーカスへ移動
- [↑][↓][←][→]:矢印キーは処理無し(キーを捨てる)
- [0]~[9]:入力文字列が数値桁数を超えない場合にカンマ編集表示
- [-]:数値のプラス、マイナスの反転処理
- [BackSpace]:最終文字を削除
- その他のキー:処理無し(キーを捨てる)
尚、クリップボードからのペースト処理をしない様に WndProc をオーバーライドしペーストのメッセージを処理しない様にしています。
テキストボックスの電卓の様な入力コントロールクラス
Public Class ClsNumTextBox3 Inherits TextBox '数値桁数("-",","を含まない数値の桁数) Private _NumericUnits As Integer = 9 '数値マイナスフラグ Private _fMinus As Boolean = False 'クリップボード貼付メッセージ Const WM_PASTE As Integer = &H302 '''''' コンストラクタ ''' Public Sub New() MyBase.New() 'IMEを無効にする MyBase.ImeMode = ImeMode.Disable 'MaxLengthは取り敢えず16を設定 Me.MaxLength = 16 'テキストは右寄せ Me.TextAlign = HorizontalAlignment.Right End Sub '''''' WndProc メソッド(クリップボード処理の為) ''' ''' <param name="m">メッセージ</param> Protected Overrides Sub WndProc(ByRef m As Message) Select Case m.Msg Case WM_PASTE 'クリップボードからのコピーは処理しない Return End Select 'メッセージのデフォルト処理を呼出す MyBase.WndProc(m) End Sub '''''' 数値桁数プロパティ ''' Public WriteOnly Property NumericUnits As Integer Set(value As Integer) Me._NumericUnits = value '退避値に設定 End Set End Property '''''' 数値での取得設定プロパティ ''' '''設定値 '''取得値 Public Property Value As Decimal Set(value As Decimal) Dim strNum As String = Math.Abs(value).ToString If strNum.Length > Me._NumericUnits Then MsgBox("ERR:数値桁数 " & Me._NumericUnits & " より大きい値が設定された") Else Me._fMinus = (value < 0) 'マイナスフラグ設定 Me.Text = strNum '取敢えず数値文字列設定 Call Me.RepairNumString("", Me._fMinus) End If End Set Get '文字列からカンマ除去 Dim strNum As String = Me.Text.Replace(",", "") 'Decimal変換値を返す Return ToDec(strNum) End Get End Property '''''' KeyDownイベントの処理 ''' Protected Overrides Sub OnKeyDown(e As KeyEventArgs) MyBase.OnKeyDown(e) Dim strAdd As String = "" Dim fEdit As Boolean = False 'キーコードによる処理 Select Case e.KeyCode Case Keys.Enter If e.Control = False Then '[Ctrl]キーが押下されていない場合、自分の親のコントロールの次のフォーカスへ移動 Me.Parent.SelectNextControl(Me, Not e.Shift, True, True, True) End If Case Keys.Up, Keys.Down, Keys.Left, Keys.Right e.Handled = True Case Keys.D0 To Keys.D9, Keys.NumPad0 To Keys.NumPad9 'キーが [0]~[9] '入力文字列が数値桁数を超えないかチェック Dim strNum As String = Me.Text.Replace("-", "").Replace(",", "") If strNum.Length >= Me._NumericUnits Then e.Handled = True Else Select Case e.KeyCode Case Keys.D0 To Keys.D9 strAdd = "0123456789".Substring(e.KeyCode - Keys.D0, 1) Case Keys.NumPad0 To Keys.NumPad9 strAdd = "0123456789".Substring(e.KeyCode - Keys.NumPad0, 1) End Select fEdit = True '編集表示フラグON e.Handled = True End If Case Keys.Subtract 'キーが [-] Me._fMinus = Not Me._fMinus 'マイナスフラグ反転 fEdit = True '編集表示フラグON Case Keys.Back '最後尾1文字削除 RepairNumString("", Me._fMinus, True) Me.SelectionStart = Me.Text.Length If Me.Text.Length = 0 Then '文字列が空になった場合はマイナスフラグOFF Me._fMinus = False End If e.Handled = True Case Else e.Handled = True End Select If fEdit = True Then '編集表示フラグONの場合、編集表示 Call Me.RepairNumString(strAdd, Me._fMinus) Me.SelectionStart = Me.Text.Length End If End Sub '''''' KeyPressイベントの処理 ''' Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs) MyBase.OnKeyPress(e) '全ての文字をイベントをキャンセル e.Handled = True End Sub '''''' MouseDownイベント処理 ''' Protected Overrides Sub OnMouseDown(e As MouseEventArgs) MyBase.OnMouseDown(e) If Not (Me.SelectionStart = Me.Text.Length) Then 'カーソル位置を最後尾に強制移動 Me.SelectionStart = Me.Text.Length End If End Sub '''''' 文字列の編集表示 ''' ''' <param name="strAdd">最後尾付加文字</param> ''' <param name="fMinus">数値マイナスフラグ</param> ''' <param name="fBackSpace">BACKSPACEフラグ</param> '''文字列を3桁ごとにカンマ編集する Private Sub RepairNumString(ByVal strAdd As String, ByVal fMinus As Boolean, Optional fBackSpace As Boolean = False) '文字を最後に追加する Dim strNum As String = Me.Text.Replace("-", "").Replace(",", "") & strAdd If fBackSpace = True Then 'BackSpaceの指定の場合、最後の文字を削除 If strNum <> "" Then strNum = strNum.Substring(0, strNum.Length - 1) End If End If '3桁ごとのカンマ表示 Dim str As String = Me.ToDec(strNum).ToString("#,###") 'マイナスの場合は"-"付加 If fMinus = True Then If str <> "" Then str = "-" & str End If End If Me.Text = str 'マイナスの場合は赤色設定 If fMinus = True Then If Not Me.ForeColor = Color.Red Then Me.ForeColor = Color.Red End If Else If Not Me.ForeColor = Color.Black Then Me.ForeColor = Color.Black End If End If End Sub '''''' 文字列をDecimal数値変換 ''' ''' <param name="str">文字列</param> '''変換後の数値 Private Function ToDec(ByVal str As String) As Decimal Dim dec As Decimal = 0 Try If str <> "" Then dec = CDec(str) End If Catch ex As Exception End Try Return dec End Function End Class今回のコントロールを2個とボタンをフォームに張り付け、 そのボタンで1個目の数値を2個目の数値コントロールにコピーを行う例を以下に示します。
電卓風数値入力コントロールの例
Public Class frmTextBox3 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try Dim dec As Decimal = Me.NumTextBox1.Value Me.NumTextBox2.Value = dec Catch ex As Exception End Try End Sub End Class
ボタンを押下することで、上のコントロールの Value プロパティから値を取得し、 その値を下のコントロールの Value プロパティに設定します。 その結果、値がコピーされたことが分かります。 (上の画像は先にマイナス値をコピーした後で、上のコントロールで「-」キーを押下した様子です)関連する記事
⇒テキストボックスの入力を数字のみにする方法
⇒テキストボックス拡張クラスにプロパティを追加する方法
⇒テキストボックスの Leave イベントでのエラー処理でフォーカス強制移動する方法について
PR -
通常、長い処理を行うプログラムの場合、処理を行っている間にマウスカーソルを待機状態にして、現在処理中であることをユーザに示します。 これを行うにはフォームの Cursor プロパティを Cursors.WaitCursor に設定します。
また、元に戻す時は Cursor プロパティを Cursors.Default に設定します。フォームのマウスカーソルを待機状態する方法
Public Class frmCursor Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'フォームのカーソルを待機状態にする Me.Cursor = Cursors.WaitCursor '10秒待つ(長い処理の代わりにSleep関数を用いる) System.Threading.Thread.Sleep(10000) 'フォームのカーソルを元に戻す Me.Cursor = Cursors.Default End Sub End Class上のプログラムで「長い処理の代わり」と書きましたが、実際にはデータベース処理など時間の掛かる処理を書くわけですが、 その中でエラーの対応の為に Try ... Catch ... End でエラー処理を記述すると思います。
このエラー処理の中で適切にカーソルを元に戻してやらないと、カーソルが待機状態のままになってしまいます。
(これは明らかなバグで、フォーム上の処理はできるが、カーソルが矢印に戻ってないので変な感じになります)
これを回避するためにも、カーソル設定処理は Try ... Catch ... End の外側で行うか、 もしくは以下のソースの様に Try の Finnaly の中で行うと良いと思います。
カーソル処理を Finally ブロックで行う方法
Public Class frmCursor Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try 'フォームのカーソルを待機状態にする Me.Cursor = Cursors.WaitCursor '10秒待つ(ここでは通常の処理においてエラーが発生するかもしれない) System.Threading.Thread.Sleep(10000) Catch ex As Exception 'ここに例外処理を記述する Finally 'フォームのカーソルを元に戻す Me.Cursor = Cursors.Default End Try End Sub End Class関連する記事
⇒フォームクラス生成時に設定値を同時に渡す方法
⇒フォーム上の複数のコントロール(TextBox)の入力変化を確認する方法について
⇒フォームのマウスカーソルを待機状態する方法について
⇒フォームクラスの継承の方法について
-
何かのデータをデータベースから読込み TextBox 等に表示し、それぞれ TextBox のいずれかに入力の変更が在ったかどうかを判断する方法を示します。
以下のソースではフォームロードイベント時に仮のデータを初期値として、フォーム上の5個の TextBox に設定しています。 さらに、TextBox の TextChanged イベントで入力が在ったことを処理するため、内部の変更フラグをONしていますが、 そのイベント処理ハンドラを AddHandler により各 TextBox に関連付けています。
仮に【登録】【終了】ボタンを設置し、【登録】ボタン処理では実際の登録はしないのですが、 登録したものとして、変更フラグをOFFとして再初期化しています。 また、【終了】ボタン処理ではフラグを確認して、確認メッセージを表示する様にしています。フォーム上の複数の TextBox の入力変化を確認する処理
Public Class frmInpCheck 'データに変更が在った証拠のフラグ Private fDirty As Boolean = False '【登録】ボタンクリックイベント処理 Private Sub btnWrite_Click(sender As Object, e As EventArgs) Handles btnWrite.Click MsgBox("登録します。") '登録処理を以下に記述... 'フラグをOFF Me.fDirty = False End Sub '【終了】ボタンクリックイベント処理 Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click If Me.fDirty = True Then If MsgBox("データが変更されています。終了してもよろしいですか?", _ MsgBoxStyle.Question + MsgBoxStyle.YesNo, "TEST") = MsgBoxResult.No Then 'いいえの場合は処理を取止め Exit Sub End If End If '自分を閉じる Me.Close() End Sub 'フォームロードイベント時処理 Private Sub frmInpCheck_Load(sender As Object, e As EventArgs) Handles Me.Load 'フォームロード時に画面のTextBoxを初期化する '(一般的にはデータベース等からデータを取得し表示する...) Me.TextBox1.Text = "A1234" Me.TextBox2.Text = "BBBB" Me.TextBox3.Text = "C120000" Me.TextBox4.Text = "DDDDDD" Me.TextBox5.Text = "EEE" 'フォームに属するコントロールがTextBoxの場合、TextChangedイベントの処理を関連付ける For Each ctrl As Control In Me.Controls If TypeOf ctrl Is TextBox Then AddHandler CType(ctrl, TextBox).TextChanged, AddressOf event_TextChanged End If Next End Sub 'TextBoxの内容変化イベント処理 Private Sub event_TextChanged(sender As Object, e As EventArgs) 'データの変更フラグON!! Me.fDirty = True End Sub End Classこれを実行すると以下の様な表示になります。
TextBox のデータ4を変更し、【終了】ボタンをクリックすると以下の表示になります。
尚、 AddHandler でイベントを関連付けているところを、 イベント処理の Subプロシージャ で Handlesキーワードの後ろに5個分の TextBox を以下の様に記述しても同じことができます。
TextChangedイベント処理について
'通常の方法でのTextBoxの内容変化イベント処理 '(Handles の後ろに必要なコントロールを全て記述することで AddHandler と同様になる) Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles _ TextBox1.TextChanged, TextBox2.TextChanged, TextBox3.TextChanged, _ TextBox4.TextChanged, TextBox5.TextChanged 'データの変更フラグON!! Me.fDirty = True End Sub
この方法は見た目にも余りかっこよくないですし、仮に TextBox が増えたりした場合には変更が面倒なのでお勧めしません。 ただ、こんな方法もあるということを示しました。
今回は TextBox のみを例にしましたが、実際は日付入力や CheckBox などもありますので それぞれのコントロールで値が変更されたイベントのタイミングでフラグをONしてやればよいと思います。
(CheckBox の場合は、 CheckedChangedイベント)関連する記事
⇒フォームクラス生成時に設定値を同時に渡す方法
⇒フォーム上の複数のコントロール(TextBox)の入力変化を確認する方法について
⇒フォームのマウスカーソルを待機状態する方法について
⇒フォームクラスの継承の方法について
-
プログラム上でKEYを持つリスト構造を使う場合に利用するのがコレクションクラスです。 良く使うコレクションクラスには以下の様に3種類のものがあります。
クラス名 KEY操作 Index操作 備考 ArrayList × ○ Indexでの操作しかできないコレクション基本クラス
取り扱うデータ型は Object 型Hashtable ○ × 連想配列的なKEYを持つコレクションクラス
KEY及び、取り扱うデータ型は Object 型SortedList ○ ○ ArrayList と Hashtable の性質を併せ持ったコレクションクラス
Indexは指定されたKEY順にソートされた結果が割り振られます
単なる Object 型の配列として使う場合は ArrayList でもいいのですが、 データ処理をする場合には何がしかのキーがあって、それに紐づくデータがあるものです。 そのためキーが扱える Hashtable SortedList を利用します。
これらのコレクションクラスはデータとして扱えるのは、 Object 型なので結果 データ型としてOKなものは何でもになります。 いろんなサイトで、データとして文字列が扱われているものはあるのですが、今回は少しだけ複雑にするため、 ユーザクラスをデータにもつものを示したいと思います。
以下のソースを見て下さい。ユーザクラスとしてTest を宣言しています。
このクラスを SortedList のデータとして追加等を行います。
このプログラムはモジュールファイルに Sub Main を作成しその中に処理を記述しています。 これをコンパイルする場合はプロジェクト・プロパティでスタートアップオブジェクトとして MdlMainSortList を指定して下さい。
クラスデータをリストする SortedList を宣言した後、 テストクラスのデータを4件生成しながら SortedList に追加を行います。 キーとしては4件とも同一の書式で文字列として処理しています。
その後確認の為に SortedList.Values から順次データを取得して、内容を表示しています。 (この時取得されるのはクラス型なので、いわゆる参照型です。)
さて、内部のデータの変更を行う為にキーを指定しデータを取得します。 ここでクラスのパブリック変数に直接値を入れることで内部データの変更が行われます。 (クラスの参照を SortedList はデータとして持っているだけで、その参照により実体データにアクセスします)
再度 SortedList の中身を全て確認するのですが、今回はIndexを使ってデータを取得しています。SortedListでのクラスデータの使い方
Module MdlMainSortList 'テストクラス Private Class Test Public Key As String 'KEYとなる文字列 Public Name As String '名称の文字列 Public Data As Integer 'あるデータとしてのInteger型データ Public Flag As Boolean 'フラグ 'コンストラクタ Sub New(ByVal aKey As String, ByVal aName As String, ByVal aData As String) Me.Key = aKey Me.Name = aName Me.Data = aData Me.Flag = False End Sub End Class <STAThread()> _ Sub Main() 'クラスデータをリストするSortedList Dim SortTest As New SortedList Dim clsTest As Test '敢えてKEYの順番通りでは無い様に SortedList に追加 clsTest = New Test("KEY004", "NAME004", 444) SortTest.Add("KEY004", clsTest) clsTest = New Test("KEY001", "NAME001", 100) SortTest.Add("KEY001", clsTest) clsTest = New Test("KEY003", "NAME003", 333) SortTest.Add("KEY003", clsTest) clsTest = New Test("KEY002", "NAME002", 222) SortTest.Add("KEY002", clsTest) Debug.Print("**************************") 'SortedList のKEY順番に取り出す For Each clsTest In SortTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next '1個のデータをKEYで内容を取り出し、そのクラスの中身を変更する clsTest = SortTest.Item("KEY002") '取り出した clsTest はクラスデータの参照なので、以下の処理でクラスの中身を変更 clsTest.Name = "NAMEaaa" clsTest.Data = 555 clsTest.Flag = True Debug.Print("**************************") 'KEY順番に指標で取り出す For i As Integer = 0 To SortTest.Count - 1 'クラスの参照を取り出す clsTest = CType(SortTest.GetByIndex(i), Test) '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString & " : " & clsTest.Flag.ToString) Next End Sub End Module結果の出力を開発環境の「出力」ウインドウに表示していますが、以下の様になります。
("KEY002"のデータが変更されていることが分かります。)
次に、 SortedList を Hashtable に変えてみた例を示します。 特に SortedList の場合と変わりはありませんが、 Indexを使ってのデータへのアクセスは出来ないので、キーを使うことになります。
ここでキーについての注意ですが、私はキーとしては文字列で設定する様にしています。 例えば扱うデータのキーとして「得意先コード(数値で5桁)」「商品コード(数値で8桁)」の2個があった場合は、 それぞれのコードを先頭ゼロ付きで桁数固定で文字列変換し連結したものをキーとしています。
もし「商品コード」等が文字列(漢字を含まないとする)の場合でしたら、 先頭に空白を補って桁数を揃えて連結します。そうしないと比較がうまくいかないからです。Hashtableでのクラスデータの使い方
Module MdlMainHashList 'テストクラス Private Class Test Public Key As String 'KEYとなる文字列 Public Name As String '名称の文字列 Public Data As Integer 'あるデータとしてのInteger型データ Public Flag As Boolean 'フラグ 'コンストラクタ Sub New(ByVal aKey As String, ByVal aName As String, ByVal aData As String) Me.Key = aKey Me.Name = aName Me.Data = aData Me.Flag = False End Sub End Class <STAThread()> _ Sub Main() 'クラスデータをリストするHashtable Dim HashTest As New Hashtable Dim clsTest As Test '敢えてKEYの順番通りでは無い様に SortedList に追加 clsTest = New Test("KEY004", "NAME004", 444) HashTest.Add("KEY004", clsTest) clsTest = New Test("KEY001", "NAME001", 100) HashTest.Add("KEY001", clsTest) clsTest = New Test("KEY003", "NAME003", 333) HashTest.Add("KEY003", clsTest) clsTest = New Test("KEY002", "NAME002", 222) HashTest.Add("KEY002", clsTest) 'KEY順番に指標で取り出す Debug.Print("**************************") For Each clsTest In HashTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next '1このデータをKEYで内容を取り出し、そのクラスの中身を変更する clsTest = HashTest.Item("KEY002") '取り出した clsTest はクラスデータの参照なので、以下の処理でクラスの中身を変更 clsTest.Name = "NAMEaaa" clsTest.Data = 555 clsTest.Flag = True 'KEY順番に指標で取り出す Debug.Print("**************************") For Each clsTest In HashTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next End Sub End Module
-
シリアル通信を利用して外部装置とデータの送受信を行う場合、パソコン側が主で、外部装置が従となる関係が多いと思います。 外部装置は、パソコン側から送信されてくるコマンドに対応して処理等を行い、処理結果を返してくるやり方です。
私の経験上、この外部装置との通信で多いのはシーケンサ(PLC)等とのデータの送受信がありました。 シーケンサはメーカ毎にデータの送受信を行うコマンドが全く異なります。 その為、各メーカ毎のコマンドの規約に合わせて処理を行う様にプログラムするわけです。
今回の例は、下図の様なPLCからのデータ読み出しと書込みの簡単なコマンドを想定しています。読み出しコマンドも書き込みコマンドも、最初にPLC側にコマンドを送信し、結果を受信することを想定しています。
さて、このコマンドを処理するクラスを作成するのですが、今までのクラスを大部分生かすわけですが、 以下のメソッドを作成します。- Open シリアルポートのオープン処理を行う。(今までと同様)
- Close シリアルポートのクローズ処理を行う。(今までと同様)
- ReadData PLCのアドレスを与えて、PLCデータの読込処理を行う。
- WriteData PLCのアドレス・データを与えて、PLCデータへの書込処理を行う。
今回のクラスにおいてデータ受信ハンドラの処理内では、専ら応答データを受信することとしています。 受信データの最終文字としての CR(0DH) を検知した時点で応答受信が在ったものとしています。
ReadData WriteData 共に最初にPLC側にコマンドを送信しますので、 この部分を別関数 SendData として宣言しています。 また、応答受信用には別関数 ReceiveData として宣言しています。
ReceiveData では応答受信を待つために、100msecの待ちをシステムの Sleep 関数で50回ループしています。 そのループの中に応答受信フラグがONするのをみて、受信が在ったことを検知しています。 (この待ち時間を短くすればプログラムの応答速度はよくなります。)
SerialPortコントロールの使い方その6(シリアル通信クラス・ハンドシェィク受信クラス)
Imports System.IO.Ports ''' ----------------------------------------------------------------------- '''
''' シリアル通信クラス・ハンドシェィク受信 ''' ''' ----------------------------------------------------------------------- Public Class ClsSerialRcvHandshake '''''' シリアルポートクラス ''' '''Private SerialPort As SerialPort ''' ''' コマンド応答受信フラグ ''' '''''' コマンドを送信した後での応答を受信したフラグ。送受信文字列のターミネータ[ETX](0x03)を発見した時にオンする ''' Private fRxResponse As Boolean = False '''''' 読込データ格納先 ''' '''データ受信イベントで蓄えられる Private RxBuf() As Byte '''''' エラーメッセージ ''' Private strLastError As String = "" '''''' 応答メッセージ ''' Private strLastResponse As String = "" '''''' 送受信文字列のターミネータ(0x0d) ''' Private Const CMD_TERM As Byte = &HD ''' ----------------------------------------------------------------------- '''''' コンストラクタ ''' ''' <param name="SerialPort">シリアルポートコントロール</param> ''' ----------------------------------------------------------------------- Sub New(ByVal SerialPort As SerialPort) 'シリアルポートの退避 Me.SerialPort = SerialPort '受信イベントのハンドラ設定 AddHandler Me.SerialPort.DataReceived, AddressOf DataReceivedHandler End Sub ''' ----------------------------------------------------------------------- '''''' エラーメッセージプロパティ ''' ''' ----------------------------------------------------------------------- ReadOnly Property LastError As String Get Return Me.strLastError End Get End Property ''' ----------------------------------------------------------------------- '''''' 応答メッセージプロパティ ''' ''' ----------------------------------------------------------------------- ReadOnly Property LastResponse As String Get Return Me.strLastResponse 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) As Byte '読込 SP.Read(Buf, 0, DataLen) ' If RxBuf Is Nothing Then '格納バッファの領域確保 RxBuf = Array.CreateInstance(GetType(Byte), DataLen) 'コピー Array.Copy(Buf, RxBuf, DataLen) Else '後ろに追加する Dim Length As Integer = RxBuf.Length ReDim Preserve RxBuf(Length + DataLen - 1) Buf.CopyTo(RxBuf, Length) End If '格納先の最後の文字が「CR」(0x0d)か確認する If RxBuf(UBound(RxBuf)) = CMD_TERM Then 'コマンド応答受信フラグON Me.fRxResponse = True End If Catch ex As Exception 'エラー処理 End Try End Sub ''' ----------------------------------------------------------------------- '''''' 接点エリアリード ''' ''' <param name="Addr">接点アドレス(4文字:[10^3][10^2][10^1][16^0]</param> ''' <param name="Data">接点データ</param> '''応答受信結果(True:OK, False:NG) '''''' 送信コマンド:"RD" + NNNN + CR ''' 受信コマンド:"OK" + MMMM + CR ⇒ データが正常の場合(MMMM:データ値) ''' :"NG" + 0000 + CR ⇒ データがエラーの場合(エラーでは0000:ゼロ) ''' ''' ----------------------------------------------------------------------- Public Function ReadData(ByVal Addr As String, ByRef Data As Integer) As Boolean '戻り値初期化 ReadData = False Try '最終応答,エラーをクリア Me.strLastResponse = "" Me.strLastError = "" '接点NOの4文字チェック If Addr.Length <> 4 Then Exit Function End If '送信コマンド生成 Dim strCMD As String = "RD" & Addr 'コマンド送信 If Me.SendData(strCMD) = False Then Return False End If 'コマンド受信 Dim strRX As String = "" If Me.ReceiveData(strRX) = False Then Return False End If '最終応答を退避 Me.strLastResponse = strRX '接点結果を返す If strRX.Substring(0, 2) = "OK" Then 'データ値をInteger変換 Data = CInt(strRX.Substring(2, 4)) '正常を返す ReadData = True Else 'エラー受信 Me.strLastError = "NG応答" End If Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' 接点への書込み ''' ''' <param name="Addr">接点アドレス(4文字:[10^3][10^2][10^1][16^0]</param> ''' <param name="Data">接点データ</param> '''応答受信結果(True:OK, False:NG) '''''' 送信コマンド:"WR" + NNNN + ":" + MMMM + CR ''' 受信コマンド:"OK" + CR ⇒ 書込み正常の場合 ''' :"NG" + CR ⇒ 書込みエラーの場合 ''' ''' ----------------------------------------------------------------------- Public Function WriteData(ByVal Addr As String, ByVal Data As Integer) As Boolean '戻り値初期化 WriteData = False Try '最終応答,エラーをクリア Me.strLastResponse = "" Me.strLastError = "" 'アドレス4文字チェック If Addr.Length <> 4 Then Exit Function End If '送信コマンド生成 Dim strCMD As String = "WR" & Addr & ":" + Data.ToString("0000") 'コマンド送信 If Me.SendData(strCMD) = False Then Return False End If 'コマンド受信 Dim strRX As String = "" If Me.ReceiveData(strRX) = False Then Return False End If '最終応答を退避 Me.strLastResponse = strRX '接点結果を返す If strRX.Substring(0, 2) = "NG" Then 'エラー受信 Me.strLastError = "NG応答" 'エラーを返す Return False End If '正常を返す Return True Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- '''''' コマンド送信 ''' ''' <param name="strSend">送信文字列</param> '''送信結果(True:OK, False:NG) '''''' ----------------------------------------------------------------------- Private Function SendData(ByVal strSend As String) As Boolean '戻り値初期化 SendData = False Try 'ポートオープンチェック If Me.SerialPort.IsOpen = False Then Return False End If 'Shift JISとしてバイト型配列に変換 Dim bytesData As Byte() bytesData = System.Text.Encoding.GetEncoding(932).GetBytes(strSend) 'CRの1バイト分領域拡張 Dim nIdx As Integer = UBound(bytesData) ReDim Preserve bytesData(nIdx + 1) bytesData(nIdx + 1) = CMD_TERM 'コマンド応答受信フラグOFF Me.fRxResponse = False 'コマンド送信 Me.RxBuf = Nothing '受信バッファのクリア Me.SerialPort.Write(bytesData, 0, bytesData.Length) '正常 Return True Catch ex As Exception 'エラー処理 End Try End Function ''' ----------------------------------------------------------------------- ''' ''' レスポンス受信処理 ''' ''' <param name="RcvRes">受信レスポンス</param> '''受信結果(True:OK, False:NG) '''''' ----------------------------------------------------------------------- Private Function ReceiveData(ByRef RcvRes As String) As Boolean '戻り値初期化 ReceiveData = False Try '----- '受信フラグがONするまで待つ '----- 'リトライ件数 Dim nRetry As Integer = 50 '50×100msec=5sec While nRetry > 0 System.Threading.Thread.Sleep(100) Application.DoEvents() If Me.fRxResponse = True Then '受信フラグONの場合、ループを抜ける Exit While End If nRetry -= 1 End While '----- 'タイムアウトチェック '----- If nRetry = 0 Then 'タイムアウト表示 Me.strLastError = "Receive Time Out Error!" Return False End If '受信バイトを文字列変換 Dim strRx As String = System.Text.Encoding.GetEncoding(932).GetString(Me.RxBuf) strRx = strRx.Substring(0, strRx.Length - 1) '最後の「CR」は省く '----- '受信データ処理 '----- ''BCCの計算 '受信レスポンスを返す RcvRes = strRx '結果正常 Return True Catch ex As Exception 'エラー処理 End Try End Function End Class
このクラスを使用した例を以下のソースに示します。
SerialPortコントロールの使い方その6(シリアル通信クラス・ハンドシェィク受信クラス)使用例
Public Class frmSerialRcvHandshake 'シリアル通信クラス Private mclsSerial As ClsSerialRcvHandshake '''''' フォームクローズイベント ''' Private Sub frmSerialRcv_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'シリアル通信のクローズ mclsSerial.Close() End Sub '''''' フォームロードイベント ''' Private Sub frmSerialRcv_Load(sender As Object, e As EventArgs) Handles Me.Load 'シリアル通信クラスの生成 mclsSerial = New ClsSerialRcvHandshake(Me.SerialPort1) 'シリアル通信のオープン If mclsSerial.Open() = False Then 'オープンエラー(必要があれば処理する) End If End Sub '''''' 受信処理 ''' Private Sub btnRX_Click(sender As Object, e As EventArgs) Handles btnRX.Click Try Dim nData As Integer Dim strAddr As String = CInt(txtRxAddr.Text).ToString("0000") Me.txtRxData.Text = "" Me.txtRxRes.Text = "" Me.txtRxErr.Text = "" '受信 If Me.mclsSerial.ReadData(strAddr, nData) = True Then Me.txtRxData.Text = nData.ToString("0000") Me.txtRxRes.Text = Me.mclsSerial.LastResponse Me.txtRxErr.Text = "" Else Me.txtRxData.Text = "" Me.txtRxRes.Text = Me.mclsSerial.LastResponse Me.txtRxErr.Text = Me.mclsSerial.LastError End If Catch ex As Exception MsgBox(ex.Message) End Try End Sub '''''' 送信処理 ''' Private Sub btnTX_Click(sender As Object, e As EventArgs) Handles btnTX.Click Try Dim nData As Integer = CInt(Me.txtTxData.Text) Dim strAddr As String = CInt(txtTxAddr.Text).ToString("0000") Me.txtTxRes.Text = "" Me.txtTxErr.Text = "" '送信 If Me.mclsSerial.WriteData(strAddr, nData) = True Then Me.txtTxRes.Text = Me.mclsSerial.LastResponse Me.txtTxErr.Text = "" Else Me.txtTxRes.Text = Me.mclsSerial.LastResponse Me.txtTxErr.Text = Me.mclsSerial.LastError End If Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Class
このフォームは、以下の図の様に、シリアルコントロール及び、 受信処理用のアドレス、受信結果表示データ、応答結果文字列表示用、エラー表示用の各テキストボックスと、 送信処理用にアドレス、送信データ、応答結果文字列表示用、エラー表示用の各テキストボックスを 画面に張り付けてあります。シリアル通信クラスをフォームの静的変数として宣言します。 受信ボタンをクリックした時には、アドレス値を取得し、シリアル通信クラスの ReadData メソッドにそのアドレスを渡して、 受信を行います。メソッドが正常に終了した時には、データ値と受信文字列を表示し、エラーが在った場合にはエラー内容を表示しています。
また、送信ボタンをクリックした時には、アドレス値及びデータ値を取得し、シリアル通信クラスの WriteData メソッドにアドレス、データ値を渡して、 送信を行います。メソッドが正常に終了した時には、受信文字列を表示し、エラーが在った場合にはエラー内容を表示しています。
以下の図は、受信処理での正常時・エラー発生時・タイムアウトエラー時の表示を示します。
(尚、外部装置の外部装置の替わりとして、前回まで使用してきた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コントロールの使い方その3(外部装置からの垂れ流しデータ受信)
⇒SerialPortコントロールの使い方その4(データ受信時にイベントを発生させる)
⇒SerialPortコントロールの使い方その5(データ受信時及び、信号変化時にイベントを発生させる)