做技术的通病,什么都想学,什么都想亲手做一遍。不然总感觉心里不踏实。
考研期间,利用晚上一点时间,照葫芦画瓢,练习使用c#快速开发一个简单的串口小助手。
这种前后端分离的设计方法,大大提高了开发速度,比Python的wxPython写软件方便很多。
但是使用c#写软件在软件群里问问题的时候,却被人家用qt的嘲笑。(悲伤脸)
好吧,不管怎样,亲手实践一下总不会错的。简单介绍一下吧。界面设计:
1、相对重要的串口接收事件
首先注册一个接收事件,相当于单片机中的串口中断函数。
接下来为了完成接收字节的计数,需要串口按照字节接收,并区分HEX和ASCII接收形式。
下面是接收函数的实现,其中包括可选择显示接收时间:
//串口接受事件处理 private void SerialPort1_DataReceieved(object sender, SerialDataReceivedEventArgs e) { //方法2:按字节读取 int num = serialPort1.BytesToRead; //获取缓冲区字节数 byte[] received_buf = new byte[num]; receieve_count += num; serialPort1.Read(received_buf, 0, num); //将缓冲区数据读取到received_buf sb.Clear(); //防止出错,先清空字符串构造器 if(radioButton2.Checked) //以HEX形式接收 { foreach(byte b in received_buf) { sb.Append(b.ToString("X2") + " "); //byte转化为2位16进制文本进行显示,中间用空格隔开 } } else //默认ascii形式接收 { sb.Append(Encoding.ASCII.GetString(received_buf));//将接收数组解码为ascii数组 } //显示到接收文本框内 try { Invoke((EventHandler)(delegate { if(checkBox1.Checked) { //显示时间 current_time = System.DateTime.Now; textBox_R.AppendText(current_time.ToString("HH:mm:ss") + " " + sb.ToString()); } else { textBox_R.AppendText(sb.ToString()); } if (checkBox2.Checked) { //接收自动换行 textBox_R.AppendText(Environment.NewLine); } //更新状态栏 label9.Text = "Rx:" + receieve_count.ToString() + "Bytes"; })); } catch (Exception ex) { //响铃并显示异常信息 System.Media.SystemSounds.Beep.Play(); MessageBox.Show(ex.Message); } }
还有一个重要的问题就是如何解决接收过程中实时显示接收的字符串,而不是等接收完毕后在显示的问题。
因为在当前工作线程中直接修改文本框的话会出现错误。
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使得多线程中安全的更新界面显示。
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
2、定时发送功能
//自动定时发送 private void checkBox3_CheckedChanged(object sender, EventArgs e) { if(checkBox3.Checked) { //选择自动发送 numericUpDown1.Enabled = false; timer1.Interval = (int)numericUpDown1.Value; //定时器赋值,单位:毫秒 timer1.Start(); label7.Text = "串口已打开" + "自动发送中..."; }
和使用单片机时差不多,这里相当于设置了一个定时器,只不过默认定时单位是1ms,所以,按照单片机编程的思想,设置了定时器肯定还需要定时器中断函数,因此还需要注册一个定时器事件:
//定时时间到 private void timer1_tick(object sender, EventArgs e) { button2_Click(button2, new EventArgs());//调用发送按钮的回调函数 }
3、运行
完整工程: