終於進入接收資料的開始,就從DataReceived事件來進入這個階段。
純接收的模型一般來講用DataReceived事件是很方便的,雖然它其實有一些毛病,我們暫且先拋開這些毛病,從最簡單的程式碼看起。
終於進入接收資料的開始,就從DataReceived事件來進入這個階段。
純接收的模型一般來講用DataReceived事件是很方便的,雖然它其實有一些毛病,我們暫且先拋開這些毛病,從最簡單的程式碼看起。先來建立一個如下圖的Form:
(1) 左方的TextBbox ,設定為Multiline、Both ScrollBars,用來顯示收到的資料。 (2) 右方的Label2用來顯示收到的Byte數。 (3) Button的用途是用來清除TextBox中的文字,由於字串相加的特性,字越多它會越來越慢,但由於處理文字的效率不是要講的重點,所以偷懶地使用最簡單的方法。 |
程式碼如下:
Imports System.IO.Ports
Public Class Form1
Private comport As SerialPort
Private totalLength As Int32 = 0
Private Delegate Sub Display(ByVal buffer As Byte())
Private Sub DisplayText(ByVal buffer As Byte())
TextBox1.Text &= String.Format("{0}{1}", BitConverter.ToString(buffer), Environment.NewLine)
totalLength = totalLength + buffer.Length
Label2.Text = totalLength.ToString()
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
comport = New SerialPort("COM5", 9600, Parity.None, 8, StopBits.One)
AddHandler comport.DataReceived, AddressOf comport_DataReceived
If comport.IsOpen = False Then
comport.Open()
End If
End Sub
Private Sub Button_ClearTextbox_Click(sender As System.Object, e As System.EventArgs) Handles Button_ClearTextbox.Click
TextBox1.Clear()
End Sub
Private Sub comport_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
Dim buffer(1023) As Byte
Dim length As Int32 = DirectCast(sender, SerialPort).Read(buffer, 0, buffer.Length)
Array.Resize(buffer, length)
Dim d As New Display(AddressOf DisplayText)
Me.Invoke(d, New Object() {buffer})
End Sub
End Class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
namespace SimpleReceiveEventCS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private SerialPort comport;
private Int32 totalLength = 0;
delegate void Display(Byte[] buffer);
private void DisplayText(Byte[] buffer)
{
TextBox1.Text += String.Format("{0}{1}", BitConverter.ToString(buffer), Environment.NewLine);
totalLength = totalLength + buffer.Length;
Label2.Text = totalLength.ToString();
}
private void Form1_Load(object sender, EventArgs e)
{
comport = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
comport.DataReceived +=new SerialDataReceivedEventHandler(comport_DataReceived);
if (!comport.IsOpen)
{
comport.Open();
}
}
private void comport_DataReceived(Object sender, SerialDataReceivedEventArgs e)
{
Byte[] buffer = new Byte[1024];
Int32 length = (sender as SerialPort).Read(buffer, 0, buffer.Length);
Array.Resize(ref buffer, length);
Display d = new Display(DisplayText);
this.Invoke(d,new Object[]{buffer});
}
private void Button_ClearTextbox_Click(object sender, EventArgs e)
{
TextBox1.Clear();
}
}
}
一些前面聊過的東西就不再重複,來看點新鮮的。
(1) DataReceived 事件的重要特性
在MSDN文件庫 [SerialPort.DataReceived 事件] 的備註中,提到一件非常重要的事情:
從 SerialPort 物件收到資料時,會在次要執行緒上引發 DataReceived 事件。 由於這個事件是在次要執行緒上引發,而非主執行緒,所以嘗試在主執行緒中修改某些項目 (例如 UI 項目) 可能會引發執行緒例外狀況。 如果必須在主要 Form 或 Control 中修改項目,請使用 Invoke 回傳變更要求,此方法將會針對適當的執行緒執行此作業。
(2) 宣告委派
因為需要使用Control.Invoke的緣故,所以先來宣告個委派 (Delegate) 用用;對初學者來講委派應該是個很難理解的東西,不過它在C++上有一個近親名叫『函式指標』,我自己是覺得這麼名字比委派來得好理解。為了能讓大家簡單理解委派在搞什麼鬼,我就用變數的概念來轉化一下。註: 以下我的說明不是正式的講法,只是為理解方便。
Visual Basic | C# | |
程式碼 | Private Delegate Sub Display(ByVal buffer As Byte()) | delegate void Display(Byte[] buffer); |
解說 | 宣告一個繼承自Delegate 的型別叫 『無回傳值方法』Display(ByVal buffer As Byte()) | 宣告一個繼承自Delegate 的型別叫 『無回傳值方法』Display(Byte[] buffer) |
Visual Basic | C# | |
程式碼 | Dim d As New Display(AddressOf DisplayText) | Display d = new Display(DisplayText); |
解說 | 宣告一個以 Display 委派為型別的變數d,其內容中的函式指標指向 DispalyText Method 所在的記憶體位址。 | 宣告一個以 Display 委派為型別的變數d,其內容中的函式指標指向 DispalyText Method 所在的記憶體位址。 |
Visual Basic | C# | |
程式碼 | Me.Invoke(d, New Object() {buffer}) | this.Invoke(d,new Object[]{buffer}); |
解說 | 在Me (也就是這個Form) 所在的執行緒上執行d所指向的Method,並將第二個參數傳入給d所指向的Method (也就是DiaplsyText) 當參數 | 在this (也就是這個Form) 所在的執行緒上執行d所指向的Method,並將第二個參數傳入給d所指向的Method (也就是DiaplsyText) 當參數 |
註:請參考MSDN文件庫 [Control.Invoke 方法 (Delegate, Object[])]
(3) 加入SerialPort.DataReceived事件委派與撰寫委派函式
AddHandler comport.DataReceived, AddressOf comport_DataReceived
Private Sub comport_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
Dim buffer(1023) As Byte
Dim length As Int32 = DirectCast(sender, SerialPort).Read(buffer, 0, buffer.Length)
Array.Resize(buffer, length)
Dim d As New Display(AddressOf DisplayText)
Me.Invoke(d, New Object() {buffer})
End Sub
comport.DataReceived +=new SerialDataReceivedEventHandler(comport_DataReceived);
private void comport_DataReceived(Object sender, SerialDataReceivedEventArgs e)
{
Byte[] buffer = new Byte[1024];
Int32 length = (sender as SerialPort).Read(buffer, 0, buffer.Length);
Array.Resize(ref buffer, length);
Display d = new Display(DisplayText);
this.Invoke(d,new Object[]{buffer});
}
所以當DataReceived 事件被引發後,就會執行comport_DataReceived方法中的內容使用 SerialPort.Read 方法 (Byte[], Int32, Int32) 讀取緩衝區資料,然後呼叫DispalyText 顯示某些資料在畫面上。不過這只是DataReceived 事件最基礎的用法,主要是為了讓不熟這事件的人能夠初步的瞭解如何使用它,這樣的寫法其實存在著一些問題,下一篇會談到這一部份的問題及如何避免。