上一篇簡單介紹了C#的一些基本知識,并成功的Hello,World,那麼從這篇開始,我們來自己動手寫一個序列槽助手:
1、構思功能
序列槽助手在單片機開發中經常被用來調試,最基本的功能就是接收功能和發送功能,其次,序列槽在打開前需要進行一些設定:序列槽清單選擇、波特率、資料位、校驗位、停止位,這樣就有了一個基本的雛形;然後我們在下一篇中在此功能上添加:ASCII/HEX顯示,發送,發送新行功能,重複自動發送功能,顯示接收資料時間這幾項擴充功能;
2、設計布局
根據以上功能,将整個界面分為兩塊:設定界面(不可縮放)+ 接收區和發送區(可縮放),下面就來依次拖放控件實作:
1)容器控件(Panel)
Panel是容器控件,是一些小控件的容器池,用來給控件進行大緻分組,要注意容器是一個虛拟的,隻會在設計的時候出現,不會顯示在設計完成的界面上,這裡我們将整個界面分為6個容器池,如圖:
2)文本标簽控件(Lable)
用于顯示一些文本,但是不可被編輯;改變其顯示内容有兩種方法:一是直接在屬性面闆修改“Text”的值,二是通過代碼修改其屬性,見如下代碼;另外,可以修改Font屬性修改其顯示字型及大小,這裡我們選擇微軟雅黑,12号字型;
label1.Text = "序列槽"; //設定label的Text屬性值
3)下拉組合框控件(ComboBox)
用來顯示下拉清單;通常有兩種模式,一種是DropDown模式,既可以選擇下拉項,也可以選擇直接編輯;另一種是DropDownList模式,隻能從下拉清單中選擇,兩種模式通過設定DropDownStyle屬性選擇,這裡我們選擇第二種模式;
那麼,如何加入下拉選項呢?對于比較少的下拉項,可以通過在屬性面闆中Items屬性中加入,比如停止位設定,如圖,如果想要出現預設值,改變Text屬性就可以,但要注意必須和下拉項一緻:
另外一種是直接在頁面加載函數代碼中加入,比如波特率的選擇,代碼如下:
private void Form1_Load(object sender, EventArgs e)
{
int i;
//單個添加for (i = 300; i <= 38400; i = i*2)
{
comboBox2.Items.Add(i.ToString()); //添加波特率清單
}
//批量添加波特率清單
string[] baud = { "43000","56000","57600","115200","128000","230400","256000","460800" };
comboBox2.Items.AddRange(baud);
//設定預設值
comboBox1.Text = "COM1";
comboBox2.Text = "115200";
comboBox3.Text = "8";
comboBox4.Text = "None";
comboBox5.Text = "1";
}
4)按鈕控件(Button)
5)文本框控件(TextBox)
TextBox控件與label控件不同的是,文本框控件的内容可以由使用者修改,這也滿足我們的發送文本框需求;在預設情況下,TextBox控價是單行顯示的,如果想要多行顯示,需要設定其Multiline屬性為true;
TextBox的方法中最多的是APPendText方法,它的作用是将新的文本資料從末尾處追加至TextBox中,那麼當TextBox一直追加文本後就會帶來本身長度不夠而無法顯示全部文本的問題,此時我們需要使能TextBox的縱向滾動條來跟蹤顯示最新文本,是以我們将TextBox的屬性ScrollBars的值設定為Vertical即可;
至此,我們的顯示控件就全部添加完畢,但是還有一個最重要的空間沒有添加,這種控件叫做隐式控件,它是運作于背景的,使用者看不見,更不能直接控制,是以也成為元件,接下來我們添加最主要的序列槽元件;
6)序列槽元件(SerialPort)
這種隐式控件添加後位于設計器下面 ,序列槽常用的屬性有兩個,一個是端口号(PortName),一個是波特率(BaudRate),當然還有資料位,停止位,奇偶校驗位等;序列槽打開與關閉都有接口可以直接調用,序列槽同時還有一個IsOpen屬性,IsOpen為true表示序列槽已經打開,IsOpen為flase則表示序列槽已經關閉。
添加了序列槽元件後,我們就可以通過它來擷取電腦目前端口,并添加到可選清單中,代碼如下:
//擷取電腦目前可用序列槽并添加到選項清單中
comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
啟動後可以看到界面布局效果圖如下(確定USB轉序列槽CH340已連接配接):
3、搭建背景
界面布局完成後,我們就要用代碼來搭建整個軟體的背景,這部分才是重中之重。
首先,我們先來控制打開/關閉序列槽,大緻思路是:當按下打開序列槽按鈕後,将設定值傳送到序列槽控件的屬性中,然後打開序列槽,按鈕顯示關閉序列槽,再次按下時,序列槽關閉,顯示打開按鈕;
在這個過程中,要注意一點,當我們點選打開按鈕時,會發生一些我們程式設計時無法處理的事件,比如硬體序列槽沒有連接配接,序列槽打開的過程中硬體突然斷開,這些被稱之為異常,針對這些異常,C#也有try..catch處理機制,在try中放置可能産生異常的代碼,比如打開序列槽,在catch中捕捉異常進行處理,詳細代碼如下:
private void button1_Click(object sender, EventArgs e) {
try
{
//将可能産生異常的代碼放置在try塊中
//根據目前序列槽屬性來判斷是否打開
if (serialPort1.IsOpen)
{
//序列槽已經處于打開狀态
serialPort1.Close(); //關閉序列槽
button1.Text = "打開序列槽";
button1.BackColor = Color.ForestGreen;
comboBox1.Enabled = true;
comboBox2.Enabled = true;
comboBox3.Enabled = true;
comboBox4.Enabled = true;
comboBox5.Enabled = true;
textBox_receive.Text = ""; //清空接收區
textBox_send.Text = ""; //清空發送區
}
else
{
//序列槽已經處于關閉狀态,則設定好序列槽屬性後打開
comboBox1.Enabled = false;
comboBox2.Enabled = false;
comboBox3.Enabled = false;
comboBox4.Enabled = false;
comboBox5.Enabled = false;
serialPort1.PortName = comboBox1.Text;
serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);
serialPort1.DataBits = Convert.ToInt16(comboBox3.Text);
if (comboBox4.Text.Equals("None"))
serialPort1.Parity = System.IO.Ports.Parity.None;
else if(comboBox4.Text.Equals("Odd"))
serialPort1.Parity = System.IO.Ports.Parity.Odd;
else if (comboBox4.Text.Equals("Even"))
serialPort1.Parity = System.IO.Ports.Parity.Even;
else if (comboBox4.Text.Equals("Mark"))
serialPort1.Parity = System.IO.Ports.Parity.Mark;
else if (comboBox4.Text.Equals("Space"))
serialPort1.Parity = System.IO.Ports.Parity.Space;
if (comboBox5.Text.Equals("1"))
serialPort1.StopBits = System.IO.Ports.StopBits.One;
else if (comboBox5.Text.Equals("1.5"))
serialPort1.StopBits = System.IO.Ports.StopBits.OnePointFive;
else if (comboBox5.Text.Equals("2"))
serialPort1.StopBits = System.IO.Ports.StopBits.Two;
serialPort1.Open(); //打開序列槽
button1.Text = "關閉序列槽";
button1.BackColor = Color.Firebrick;
}
}
catch (Exception ex)
{
//捕獲可能發生的異常并進行處理
//捕獲到異常,建立一個新的對象,之前的不可以再用
serialPort1 = new System.IO.Ports.SerialPort();
//重新整理COM口選項
comboBox1.Items.Clear();
comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
//響鈴并顯示異常給使用者
System.Media.SystemSounds.Beep.Play();
button1.Text = "打開序列槽";
button1.BackColor = Color.ForestGreen;
MessageBox.Show(ex.Message);
comboBox1.Enabled = true;
comboBox2.Enabled = true;
comboBox3.Enabled = true;
comboBox4.Enabled = true;
comboBox5.Enabled = true;
}
}
接下來我們建構發送和接收的背景代碼,序列槽發送和接收都是在序列槽成功打開的情況下進行的,是以首先要判斷序列槽屬性IsOpen是否為1;
序列槽發送有兩種方法,一種是字元串發送WriteLine,一種是Write(),可以發送一個字元串或者16進制發送(見下篇),其中字元串發送WriteLine預設已經在末尾添加換行符;
private void button2_Click(object sender, EventArgs e)
{
try
{
//首先判斷序列槽是否開啟
if (serialPort1.IsOpen)
{
//序列槽處于開啟狀态,将發送區文本發送
serialPort1.Write(textBox_send.Text);
}
}
catch (Exception ex)
{
//捕獲到異常,建立一個新的對象,之前的不可以再用
serialPort1 = new System.IO.Ports.SerialPort();
//重新整理COM口選項
comboBox1.Items.Clear();
comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
//響鈴并顯示異常給使用者
System.Media.SystemSounds.Beep.Play();
button1.Text = "打開序列槽";
button1.BackColor = Color.ForestGreen;
MessageBox.Show(ex.Message);
comboBox1.Enabled = true;
comboBox2.Enabled = true;
comboBox3.Enabled = true;
comboBox4.Enabled = true;
comboBox5.Enabled = true;
}
}
接下來開始最後一個任務 —— 序列槽接收,在使用序列槽接收之前要先為序列槽注冊一個Receive事件,相當于單片機中的序列槽接收中斷,然後在中斷内部對緩沖區的資料進行讀取,如圖,輸入完成後回車,就會跳轉到響應代碼部分:
//序列槽接收事件處理
private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
}
同樣的,序列槽接收也有兩種方法,一種是16進制方式讀(下篇介紹),一種是字元串方式讀,在剛剛生成的代碼中編寫,如下:
//序列槽接收事件處理
private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
//因為要通路UI資源,是以需要使用invoke方式同步ui
this.Invoke((EventHandler)(delegate
{
textBox_receive.AppendText(serialPort1.ReadExisting());
}
)
);
}
catch (Exception ex)
{
//響鈴并顯示異常給使用者
System.Media.SystemSounds.Beep.Play();
MessageBox.Show(ex.Message);
}
}
這裡又有了一個新的知識點,這個序列槽接收處理函數屬于一個單獨的線程,不屬于main的主線程,而接收區的TextBox是在主線程中建立的,是以當我們直接用serialPort1.ReadExisting()讀取回來字元串,然後用追加到textBox_receive.AppendText()追加到接收顯示文本框中的時候,序列槽助手在運作時沒有反應,甚至報異常,如圖:
是以,這個時候我們就需要用到invoke方式,這種方式專門被用于解決從不是建立控件的線程通路它,加入了invoke方式後,序列槽助手就可以正常接收到資料了,如圖: