分享

打造漂亮的串口调试助手(基于WPF + .NET C# VS2013)附源码!

 魅影苍穹 2020-05-26

WPF界面全部用XAML语言手打,基本都是Grid布局,VS很强大,编程很舒服便捷,源码有很详细的注释。

 * 学C#和WPF编的第一个软件,整个编程过程,通过百度不断学习,其中很多借鉴了一叶知秋串口助手的代码
 * 作者是做硬件的,只为学习做简单的上位机程序,C#简单,开发效率高,所以选择C#
 * 以前没有PC端软件编程经验,所以该编程思想继承于单片机编程思想,未用到面向对象和WPF的精髓,不建议模仿,仅供参考
 * 实际上到现在我还不知道面向对象是什么意思 ̄□ ̄||
 * 欢迎反馈BUG QQ45213212 E-MAIL lincolne@126.com

下载点这里http://download.csdn.net/detail/q45213212/7560727

直接上图


以下为主体c#源码

  1. #define MULTITHREAD//多线程收发模式,注释本句则使用单线程模式
  2. //相对单线收发模式,占用系统资源稍微大些,但是执行效果更好,尤其是在大数据收发时的UI反应尤其明显
  3. using Microsoft.Win32;
  4. using SerialComWindow;
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.IO.Ports;
  10. using System.Linq;
  11. using System.Security.Permissions;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Windows;
  16. using System.Windows.Controls;
  17. using System.Windows.Data;
  18. using System.Windows.Documents;
  19. using System.Windows.Input;
  20. using System.Windows.Media;
  21. using System.Windows.Media.Imaging;
  22. using System.Windows.Navigation;
  23. using System.Windows.Shapes;
  24. using System.Windows.Threading;
  25. namespace SerialCom
  26. {
  27. /// <summary>
  28. /// MainWindow.xaml 的交互逻辑
  29. /// </summary>
  30. public partial class MainWindow : Window
  31. {
  32. SerialPort ComPort = new SerialPort();//声明一个串口
  33. private string[] ports;//可用串口数组
  34. private bool recStaus = true;//接收状态字
  35. private bool ComPortIsOpen = false;//COM口开启状态字,在打开/关闭串口中使用,这里没有使用自带的ComPort.IsOpen,因为在串口突然丢失的时候,ComPort.IsOpen会自动false,逻辑混乱
  36. private bool Listening = false;//用于检测是否没有执行完invoke相关操作,仅在单线程收发使用,但是在公共代码区有相关设置,所以未用#define隔离
  37. private bool WaitClose = false;//invoke里判断是否正在关闭串口是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke ,解决关闭串口时,程序假死,具体参见http://news./art/32859/20100524/2067861_4.html 仅在单线程收发使用,但是在公共代码区有相关设置,所以未用#define隔离
  38. IList<customer> comList = new List<customer>();//可用串口集合
  39. DispatcherTimer autoSendTick = new DispatcherTimer();//定时发送
  40. #if MULTITHREAD
  41. private static bool Sending = false;//正在发送数据状态字
  42. private static Thread _ComSend;//发送数据线程
  43. Queue recQueue = new Queue();//接收数据过程中,接收数据线程与数据处理线程直接传递的队列,先进先出
  44. private SendSetStr SendSet = new SendSetStr();//发送数据线程传递参数的结构体
  45. private struct SendSetStr//发送数据线程传递参数的结构体格式
  46. {
  47. public string SendSetData;//发送的数据
  48. public bool? SendSetMode;//发送模式
  49. }
  50. #endif
  51. public MainWindow()//主窗口
  52. {
  53. InitializeComponent();//控件初始化
  54. }
  55. private void Window_Loaded(object sender, RoutedEventArgs e)//主窗口初始化
  56. {
  57. //↓↓↓↓↓↓↓↓↓可用串口下拉控件↓↓↓↓↓↓↓↓↓
  58. ports= SerialPort.GetPortNames();//获取可用串口
  59. if (ports.Length > 0)//ports.Length > 0说明有串口可用
  60. {
  61. for (int i = 0; i < ports.Length; i++)
  62. {
  63. comList.Add(new customer() { com = ports[i] });//下拉控件里添加可用串口
  64. }
  65. AvailableComCbobox.ItemsSource = comList;//资源路劲
  66. AvailableComCbobox.DisplayMemberPath = 'com';//显示路径
  67. AvailableComCbobox.SelectedValuePath = 'com';//值路径
  68. AvailableComCbobox.SelectedValue = ports[0];//默认选第1个串口
  69. }
  70. else//未检测到串口
  71. {
  72. MessageBox.Show('无可用串口');
  73. }
  74. //↑↑↑↑↑↑↑↑↑可用串口下拉控件↑↑↑↑↑↑↑↑↑
  75. //↓↓↓↓↓↓↓↓↓波特率下拉控件↓↓↓↓↓↓↓↓↓
  76. IList<customer> rateList = new List<customer>();//可用波特率集合
  77. rateList.Add(new customer() { BaudRate = '1200' });
  78. rateList.Add(new customer() { BaudRate = '2400' });
  79. rateList.Add(new customer() { BaudRate = '4800' });
  80. rateList.Add(new customer() { BaudRate = '9600' });
  81. rateList.Add(new customer() { BaudRate = '14400' });
  82. rateList.Add(new customer() { BaudRate = '19200' });
  83. rateList.Add(new customer() { BaudRate = '28800' });
  84. rateList.Add(new customer() { BaudRate = '38400' });
  85. rateList.Add(new customer() { BaudRate = '57600' });
  86. rateList.Add(new customer() { BaudRate = '115200' });
  87. RateListCbobox.ItemsSource = rateList;
  88. RateListCbobox.DisplayMemberPath = 'BaudRate';
  89. RateListCbobox.SelectedValuePath = 'BaudRate';
  90. //↑↑↑↑↑↑↑↑↑波特率下拉控件↑↑↑↑↑↑↑↑↑
  91. //↓↓↓↓↓↓↓↓↓校验位下拉控件↓↓↓↓↓↓↓↓↓
  92. IList<customer> comParity = new List<customer>();//可用校验位集合
  93. comParity.Add(new customer() { Parity = 'None', ParityValue = '0' });
  94. comParity.Add(new customer() { Parity = 'Odd', ParityValue = '1' });
  95. comParity.Add(new customer() { Parity = 'Even', ParityValue = '2' });
  96. comParity.Add(new customer() { Parity = 'Mark', ParityValue = '3' });
  97. comParity.Add(new customer() { Parity = 'Space', ParityValue = '4' });
  98. ParityComCbobox.ItemsSource = comParity;
  99. ParityComCbobox.DisplayMemberPath = 'Parity';
  100. ParityComCbobox.SelectedValuePath = 'ParityValue';
  101. //↑↑↑↑↑↑↑↑↑校验位下拉控件↑↑↑↑↑↑↑↑↑
  102. //↓↓↓↓↓↓↓↓↓数据位下拉控件↓↓↓↓↓↓↓↓↓
  103. IList<customer> dataBits = new List<customer>();//数据位集合
  104. dataBits.Add(new customer() { Dbits = '8' });
  105. dataBits.Add(new customer() { Dbits = '7' });
  106. dataBits.Add(new customer() { Dbits = '6' });
  107. DataBitsCbobox.ItemsSource = dataBits;
  108. DataBitsCbobox.SelectedValuePath = 'Dbits';
  109. DataBitsCbobox.DisplayMemberPath = 'Dbits';
  110. //↑↑↑↑↑↑↑↑↑数据位下拉控件↑↑↑↑↑↑↑↑↑
  111. //↓↓↓↓↓↓↓↓↓停止位下拉控件↓↓↓↓↓↓↓↓↓
  112. IList<customer> stopBits = new List<customer>();//停止位集合
  113. stopBits.Add(new customer() { Sbits = '1' });
  114. stopBits.Add(new customer() { Sbits = '1.5' });
  115. stopBits.Add(new customer() { Sbits = '2' });
  116. StopBitsCbobox.ItemsSource = stopBits;
  117. StopBitsCbobox.SelectedValuePath = 'Sbits';
  118. StopBitsCbobox.DisplayMemberPath = 'Sbits';
  119. //↑↑↑↑↑↑↑↑↑停止位下拉控件↑↑↑↑↑↑↑↑↑
  120. //↓↓↓↓↓↓↓↓↓默认设置↓↓↓↓↓↓↓↓↓
  121. RateListCbobox.SelectedValue = '9600';//波特率默认设置9600
  122. ParityComCbobox.SelectedValue = '0';//校验位默认设置值为0,对应NONE
  123. DataBitsCbobox.SelectedValue = '8';//数据位默认设置8位
  124. StopBitsCbobox.SelectedValue = '1';//停止位默认设置1
  125. ComPort.ReadTimeout = 8000;//串口读超时8秒
  126. ComPort.WriteTimeout = 8000;//串口写超时8秒,在1ms自动发送数据时拔掉串口,写超时5秒后,会自动停止发送,如果无超时设定,这时程序假死
  127. ComPort.ReadBufferSize = 1024;//数据读缓存
  128. ComPort.WriteBufferSize = 1024;//数据写缓存
  129. sendBtn.IsEnabled = false;//发送按钮初始化为不可用状态
  130. sendModeCheck.IsChecked = false;//发送模式默认为未选中状态
  131. recModeCheck.IsChecked = false;//接收模式默认为未选中状态
  132. //↑↑↑↑↑↑↑↑↑默认设置↑↑↑↑↑↑↑↑↑
  133. ComPort.DataReceived += new SerialDataReceivedEventHandler(ComReceive);//串口接收中断
  134. autoSendTick.Tick += new EventHandler(autoSend);//定时发送中断
  135. #if MULTITHREAD
  136. Thread _ComRec = new Thread(new ThreadStart(ComRec)); //查询串口接收数据线程声明
  137. _ComRec.Start();//启动线程
  138. #endif
  139. }
  140. private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)//关闭窗口closing
  141. {
  142. MessageBoxResult result = MessageBox.Show('确认是否要退出?', '退出', MessageBoxButton.YesNo);//显示确认窗口
  143. if (result == MessageBoxResult.No)
  144. {
  145. e.Cancel = true;//取消操作
  146. }
  147. }
  148. private void Window_Closed(object sender, EventArgs e)//关闭窗口确认后closed ALT+F4
  149. {
  150. Application.Current.Shutdown();//先停止线程,然后终止进程.
  151. Environment.Exit(0);//直接终止进程.
  152. }
  153. public class customer//各下拉控件访问接口
  154. {
  155. public string com { get; set; }//可用串口
  156. public string com1 { get; set; }//可用串口
  157. public string BaudRate { get; set; }//波特率
  158. public string Parity { get; set; }//校验位
  159. public string ParityValue { get; set; }//校验位对应值
  160. public string Dbits { get; set; }//数据位
  161. public string Sbits { get; set; }//停止位
  162. }
  163. private void Button_Open(object sender, RoutedEventArgs e)//打开/关闭串口事件
  164. {
  165. if (AvailableComCbobox.SelectedValue == null)//先判断是否有可用串口
  166. {
  167. MessageBox.Show('无可用串口,无法打开!');
  168. return;//没有串口,提示后直接返回
  169. }
  170. #region 打开串口
  171. if (ComPortIsOpen == false)//ComPortIsOpen == false当前串口为关闭状态,按钮事件为打开串口
  172. {
  173. try//尝试打开串口
  174. {
  175. ComPort.PortName = AvailableComCbobox.SelectedValue.ToString();//设置要打开的串口
  176. ComPort.BaudRate = Convert.ToInt32(RateListCbobox.SelectedValue);//设置当前波特率
  177. ComPort.Parity = (Parity)Convert.ToInt32(ParityComCbobox.SelectedValue);//设置当前校验位
  178. ComPort.DataBits = Convert.ToInt32(DataBitsCbobox.SelectedValue);//设置当前数据位
  179. ComPort.StopBits = (StopBits)Convert.ToDouble(StopBitsCbobox.SelectedValue);//设置当前停止位
  180. ComPort.Open();//打开串口
  181. }
  182. catch//如果串口被其他占用,则无法打开
  183. {
  184. MessageBox.Show('无法打开串口,请检测此串口是否有效或被其他占用!');
  185. GetPort();//刷新当前可用串口
  186. return;//无法打开串口,提示后直接返回
  187. }
  188. //↓↓↓↓↓↓↓↓↓成功打开串口后的设置↓↓↓↓↓↓↓↓↓
  189. openBtn.Content = '关闭串口';//按钮显示改为“关闭按钮”
  190. OpenImage.Source = new BitmapImage(new Uri('image\\On.png', UriKind.Relative));//开关状态图片切换为ON
  191. ComPortIsOpen = true;//串口打开状态字改为true
  192. WaitClose = false;//等待关闭串口状态改为false
  193. sendBtn.IsEnabled = true;//使能“发送数据”按钮
  194. defaultSet.IsEnabled = false;//打开串口后失能重置功能
  195. AvailableComCbobox.IsEnabled = false;//失能可用串口控件
  196. RateListCbobox.IsEnabled = false;//失能可用波特率控件
  197. ParityComCbobox.IsEnabled = false;//失能可用校验位控件
  198. DataBitsCbobox.IsEnabled = false;//失能可用数据位控件
  199. StopBitsCbobox.IsEnabled = false;//失能可用停止位控件
  200. //↑↑↑↑↑↑↑↑↑成功打开串口后的设置↑↑↑↑↑↑↑↑↑
  201. if (autoSendCheck.IsChecked == true)//如果打开前,自动发送控件就被选中,则打开串口后自动开始发送数据
  202. {
  203. autoSendTick.Interval = TimeSpan.FromMilliseconds(Convert.ToInt32(Time.Text));//设置自动发送间隔
  204. autoSendTick.Start();//开启自动发送
  205. }
  206. }
  207. #endregion
  208. #region 关闭串口
  209. else//ComPortIsOpen == true,当前串口为打开状态,按钮事件为关闭串口
  210. {
  211. try//尝试关闭串口
  212. {
  213. autoSendTick.Stop();//停止自动发送
  214. autoSendCheck.IsChecked = false;//停止自动发送控件改为未选中状态
  215. ComPort.DiscardOutBuffer();//清发送缓存
  216. ComPort.DiscardInBuffer();//清接收缓存
  217. WaitClose = true;//激活正在关闭状态字,用于在串口接收方法的invoke里判断是否正在关闭串口
  218. while (Listening)//判断invoke是否结束
  219. {
  220. DispatcherHelper.DoEvents(); //循环时,仍进行等待事件中的进程,该方法为winform中的方法,WPF里面没有,这里在后面自己实现
  221. }
  222. ComPort.Close();//关闭串口
  223. WaitClose = false;//关闭正在关闭状态字,用于在串口接收方法的invoke里判断是否正在关闭串口
  224. SetAfterClose();//成功关闭串口或串口丢失后的设置
  225. }
  226. catch//如果在未关闭串口前,串口就已丢失,这时关闭串口会出现异常
  227. {
  228. if (ComPort.IsOpen == false)//判断当前串口状态,如果ComPort.IsOpen==false,说明串口已丢失
  229. {
  230. SetComLose();
  231. }
  232. else//未知原因,无法关闭串口
  233. {
  234. MessageBox.Show('无法关闭串口,原因未知!');
  235. return;//无法关闭串口,提示后直接返回
  236. }
  237. }
  238. }
  239. #endregion
  240. }
  241. private void Button_Send(object sender, RoutedEventArgs e)//发送数据按钮点击事件
  242. {
  243. Send();//调用发送方法
  244. }
  245. void autoSend(object sender, EventArgs e)//自动发送
  246. {
  247. Send();//调用发送方法
  248. }
  249. void Send()//发送数据,分为多线程方式和单线程方式
  250. {
  251. #if MULTITHREAD
  252. if (Sending == true) return;//如果当前正在发送,则取消本次发送,本句注释后,可能阻塞在ComSend的lock处
  253. _ComSend = new Thread(new ParameterizedThreadStart(ComSend)); //new发送线程
  254. SendSet.SendSetData = sendTBox.Text;//发送数据线程传递参数的结构体--发送的数据
  255. SendSet.SendSetMode = sendModeCheck.IsChecked;//发送数据线程传递参数的结构体--发送方式
  256. _ComSend.Start(SendSet);//发送线程启动
  257. #else
  258. ComSend();//单线程发送方法
  259. #endif
  260. }
  261. #if MULTITHREAD
  262. private void ComSend(Object obj)//发送数据 独立线程方法 发送数据时UI可以响应
  263. {
  264. lock (this)//由于send()中的if (Sending == true) return,所以这里不会产生阻塞,如果没有那句,多次启动该线程,会在此处排队
  265. {
  266. Sending = true;//正在发生状态字
  267. byte[] sendBuffer = null;//发送数据缓冲区
  268. string sendData = SendSet.SendSetData;//复制发送数据,以免发送过程中数据被手动改变
  269. if (SendSet.SendSetMode == true)//16进制发送
  270. {
  271. try //尝试将发送的数据转为16进制Hex
  272. {
  273. sendData = sendData.Replace(' ', '');//去除16进制数据中所有空格
  274. sendData = sendData.Replace('\r', '');//去除16进制数据中所有换行
  275. sendData = sendData.Replace('\n', '');//去除16进制数据中所有换行
  276. if (sendData.Length == 1)//数据长度为1的时候,在数据前补0
  277. {
  278. sendData = '0' + sendData;
  279. }
  280. else if (sendData.Length % 2 != 0)//数据长度为奇数位时,去除最后一位数据
  281. {
  282. sendData = sendData.Remove(sendData.Length - 1, 1);
  283. }
  284. List<string> sendData16 = new List<string>();//将发送的数据,2个合为1个,然后放在该缓存里 如:123456→12,34,56
  285. for (int i = 0; i < sendData.Length; i += 2)
  286. {
  287. sendData16.Add(sendData.Substring(i, 2));
  288. }
  289. sendBuffer = new byte[sendData16.Count];//sendBuffer的长度设置为:发送的数据2合1后的字节数
  290. for (int i = 0; i < sendData16.Count; i++)
  291. {
  292. sendBuffer[i] = (byte)(Convert.ToInt32(sendData16[i], 16));//发送数据改为16进制
  293. }
  294. }
  295. catch //无法转为16进制时,出现异常
  296. {
  297. UIAction(() =>
  298. {
  299. autoSendCheck.IsChecked = false;//自动发送改为未选中
  300. autoSendTick.Stop();//关闭自动发送
  301. MessageBox.Show('请输入正确的16进制数据');
  302. });
  303. Sending = false;//关闭正在发送状态
  304. _ComSend.Abort();//终止本线程
  305. return;//输入的16进制数据错误,无法发送,提示后返回
  306. }
  307. }
  308. else //ASCII码文本发送
  309. {
  310. sendBuffer = System.Text.Encoding.Default.GetBytes(sendData);//转码
  311. }
  312. try//尝试发送数据
  313. {//如果发送字节数大于1000,则每1000字节发送一次
  314. int sendTimes = (sendBuffer.Length / 1000);//发送次数
  315. for (int i = 0; i < sendTimes; i++)//每次发生1000Bytes
  316. {
  317. ComPort.Write(sendBuffer, i * 1000, 1000);//发送sendBuffer中从第i * 1000字节开始的1000Bytes
  318. UIAction(() =>//激活UI
  319. {
  320. sendCount.Text = (Convert.ToInt32(sendCount.Text) + 1000).ToString();//刷新发送字节数
  321. });
  322. }
  323. if (sendBuffer.Length % 1000 != 0)//发送字节小于1000Bytes或上面发送剩余的数据
  324. {
  325. ComPort.Write(sendBuffer, sendTimes * 1000, sendBuffer.Length % 1000);
  326. UIAction(() =>
  327. {
  328. sendCount.Text = (Convert.ToInt32(sendCount.Text) + sendBuffer.Length % 1000).ToString();//刷新发送字节数
  329. });
  330. }
  331. }
  332. catch//如果无法发送,产生异常
  333. {
  334. UIAction(() =>//激活UI
  335. {
  336. if (ComPort.IsOpen == false)//如果ComPort.IsOpen == false,说明串口已丢失
  337. {
  338. SetComLose();//串口丢失后的设置
  339. }
  340. else
  341. {
  342. MessageBox.Show('无法发送数据,原因未知!');
  343. }
  344. });
  345. }
  346. //sendScrol.ScrollToBottom();//发送数据区滚动到底部
  347. Sending = false;//关闭正在发送状态
  348. _ComSend.Abort();//终止本线程
  349. }
  350. }
  351. #else
  352. private void ComSend()//发送数据 普通方法,发送数据过程中UI会失去响应
  353. {
  354. byte[] sendBuffer = null;//发送数据缓冲区
  355. string sendData = sendTBox.Text;//复制发送数据,以免发送过程中数据被手动改变
  356. if (sendModeCheck.IsChecked == true)//16进制发送
  357. {
  358. try //尝试将发送的数据转为16进制Hex
  359. {
  360. sendData = sendData.Replace(' ', '');//去除16进制数据中所有空格
  361. sendData = sendData.Replace('\r', '');//去除16进制数据中所有换行
  362. sendData = sendData.Replace('\n', '');//去除16进制数据中所有换行
  363. if (sendData.Length == 1)//数据长度为1的时候,在数据前补0
  364. {
  365. sendData = '0' + sendData;
  366. }
  367. else if (sendData.Length % 2 != 0)//数据长度为奇数位时,去除最后一位数据
  368. {
  369. sendData = sendData.Remove(sendData.Length - 1, 1);
  370. }
  371. List<string> sendData16 = new List<string>();//将发送的数据,2个合为1个,然后放在该缓存里 如:123456→12,34,56
  372. for (int i = 0; i < sendData.Length; i += 2)
  373. {
  374. sendData16.Add(sendData.Substring(i, 2));
  375. }
  376. sendBuffer = new byte[sendData16.Count];//sendBuffer的长度设置为:发送的数据2合1后的字节数
  377. for (int i = 0; i < sendData16.Count; i++)
  378. {
  379. sendBuffer[i] = (byte)(Convert.ToInt32(sendData16[i], 16));//发送数据改为16进制
  380. }
  381. }
  382. catch //无法转为16进制时,出现异常
  383. {
  384. autoSendCheck.IsChecked = false;//自动发送改为未选中
  385. autoSendTick.Stop();//关闭自动发送
  386. MessageBox.Show('请输入正确的16进制数据');
  387. return;//输入的16进制数据错误,无法发送,提示后返回
  388. }
  389. }
  390. else //ASCII码文本发送
  391. {
  392. sendBuffer = System.Text.Encoding.Default.GetBytes(sendData);//转码
  393. }
  394. try//尝试发送数据
  395. {//如果发送字节数大于1000,则每1000字节发送一次
  396. int sendTimes = (sendBuffer.Length / 1000);//发送次数
  397. for (int i = 0; i < sendTimes; i++)//每次发生1000Bytes
  398. {
  399. ComPort.Write(sendBuffer, i*1000, 1000);//发送sendBuffer中从第i * 1000字节开始的1000Bytes
  400. sendCount.Text = (Convert.ToInt32(sendCount.Text) + 1000).ToString();//刷新发送字节数
  401. }
  402. if (sendBuffer.Length % 1000 != 0)
  403. {
  404. ComPort.Write(sendBuffer, sendTimes * 1000, sendBuffer.Length % 1000);//发送字节小于1000Bytes或上面发送剩余的数据
  405. sendCount.Text = (Convert.ToInt32(sendCount.Text) + sendBuffer.Length % 1000).ToString();//刷新发送字节数
  406. }
  407. }
  408. catch//如果无法发送,产生异常
  409. {
  410. if (ComPort.IsOpen == false)//如果ComPort.IsOpen == false,说明串口已丢失
  411. {
  412. SetComLose();//串口丢失后相关设置
  413. }
  414. else
  415. {
  416. MessageBox.Show('无法发送数据,原因未知!');
  417. }
  418. }
  419. //sendScrol.ScrollToBottom();//发送数据区滚动到底部
  420. }
  421. #endif
  422. #if MULTITHREAD
  423. private void ComReceive(object sender, SerialDataReceivedEventArgs e)//接收数据 中断只标志有数据需要读取,读取操作在中断外进行
  424. {
  425. if (WaitClose) return;//如果正在关闭串口,则直接返回
  426. Thread.Sleep(10);//发送和接收均为文本时,接收中为加入判断是否为文字的算法,发送你(C4E3),接收可能识别为C4,E3,可用在这里加延时解决
  427. if (recStaus)//如果已经开启接收
  428. {
  429. byte[] recBuffer;//接收缓冲区
  430. try
  431. {
  432. recBuffer = new byte[ComPort.BytesToRead];//接收数据缓存大小
  433. ComPort.Read(recBuffer, 0, recBuffer.Length);//读取数据
  434. recQueue.Enqueue(recBuffer);//读取数据入列Enqueue(全局)
  435. }
  436. catch
  437. {
  438. UIAction(() =>
  439. {
  440. if (ComPort.IsOpen == false)//如果ComPort.IsOpen == false,说明串口已丢失
  441. {
  442. SetComLose();//串口丢失后相关设置
  443. }
  444. else
  445. {
  446. MessageBox.Show('无法接收数据,原因未知!');
  447. }
  448. });
  449. }
  450. }
  451. else//暂停接收
  452. {
  453. ComPort.DiscardInBuffer();//清接收缓存
  454. }
  455. }
  456. void ComRec()//接收线程,窗口初始化中就开始启动运行
  457. {
  458. while (true)//一直查询串口接收线程中是否有新数据
  459. {
  460. if (recQueue.Count > 0)//当串口接收线程中有新的数据时候,队列中有新进的成员recQueue.Count > 0
  461. {
  462. string recData;//接收数据转码后缓存
  463. byte[] recBuffer = (byte[])recQueue.Dequeue();//出列Dequeue(全局)
  464. recData = System.Text.Encoding.Default.GetString(recBuffer);//转码
  465. UIAction(() =>
  466. {
  467. if (recModeCheck.IsChecked == false)//接收模式为ASCII文本模式
  468. {
  469. recTBox.Text += recData;//加显到接收区
  470. }
  471. else
  472. {
  473. StringBuilder recBuffer16 = new StringBuilder();//定义16进制接收缓存
  474. for (int i = 0; i < recBuffer.Length; i++)
  475. {
  476. recBuffer16.AppendFormat('{0:X2}' + ' ', recBuffer[i]);//X2表示十六进制格式(大写),域宽2位,不足的左边填0。
  477. }
  478. recTBox.Text += recBuffer16.ToString();//加显到接收区
  479. }
  480. recCount.Text = (Convert.ToInt32(recCount.Text) + recBuffer.Length).ToString();//接收数据字节数
  481. recScrol.ScrollToBottom();//接收文本框滚动至底部
  482. });
  483. }
  484. else
  485. Thread.Sleep(100);//如果不延时,一直查询,将占用CPU过高
  486. }
  487. }
  488. #else
  489. private void ComReceive(object sender, SerialDataReceivedEventArgs e)//接收数据 数据在接收中断里面处理
  490. {
  491. if (WaitClose) return;//如果正在关闭串口,则直接返回
  492. if (recStaus)//如果已经开启接收
  493. {
  494. try
  495. {
  496. Listening = true;////设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。
  497. Thread.Sleep(10);//发送和接收均为文本时,接收中为加入判断是否为文字的算法,发送你(C4E3),接收可能识别为C4,E3,可用在这里加延时解决
  498. string recData;//接收数据转码后缓存
  499. byte[] recBuffer = new byte[ComPort.BytesToRead];//接收数据缓存
  500. ComPort.Read(recBuffer, 0, recBuffer.Length);//读取数据
  501. recData = System.Text.Encoding.Default.GetString(recBuffer);//转码
  502. this.recTBox.Dispatcher.Invoke(//WPF为单线程,此接收中断线程不能对WPF进行操作,用如下方法才可操作
  503. new Action(
  504. delegate
  505. {
  506. recCount.Text = (Convert.ToInt32(recCount.Text) + recBuffer.Length).ToString();//接收数据字节数
  507. if (recModeCheck.IsChecked == false)//接收模式为ASCII文本模式
  508. {
  509. recTBox.Text += recData;//加显到接收区
  510. }
  511. else
  512. {
  513. StringBuilder recBuffer16 = new StringBuilder();//定义16进制接收缓存
  514. for (int i = 0; i < recBuffer.Length; i++)
  515. {
  516. recBuffer16.AppendFormat('{0:X2}' + ' ', recBuffer[i]);//X2表示十六进制格式(大写),域宽2位,不足的左边填0。
  517. }
  518. recTBox.Text += recBuffer16.ToString();//加显到接收区
  519. }
  520. recScrol.ScrollToBottom();//接收文本框滚动至底部
  521. }
  522. )
  523. );
  524. }
  525. finally
  526. {
  527. Listening = false;//UI使用结束,用于关闭串口时判断,避免自动发送时拔掉串口,陷入死循环
  528. }
  529. }
  530. else//暂停接收
  531. {
  532. ComPort.DiscardInBuffer();//清接收缓存
  533. }
  534. }
  535. #endif
  536. void UIAction(Action action)//在主线程外激活线程方法
  537. {
  538. System.Threading.SynchronizationContext.SetSynchronizationContext(new System.Windows.Threading.DispatcherSynchronizationContext(App.Current.Dispatcher));
  539. System.Threading.SynchronizationContext.Current.Post(_ => action(), null);
  540. }
  541. private void SetAfterClose()//成功关闭串口或串口丢失后的设置
  542. {
  543. openBtn.Content = '打开串口';//按钮显示为“打开串口”
  544. OpenImage.Source = new BitmapImage(new Uri('image\\Off.png', UriKind.Relative));
  545. ComPortIsOpen = false;//串口状态设置为关闭状态
  546. sendBtn.IsEnabled = false;//失能发送数据按钮
  547. defaultSet.IsEnabled = true;//打开串口后使能重置功能
  548. AvailableComCbobox.IsEnabled = true;//使能可用串口控件
  549. RateListCbobox.IsEnabled = true;//使能可用波特率下拉控件
  550. ParityComCbobox.IsEnabled = true;//使能可用校验位下拉控件
  551. DataBitsCbobox.IsEnabled = true;//使能数据位下拉控件
  552. StopBitsCbobox.IsEnabled = true;//使能停止位下拉控件
  553. }
  554. private void SetComLose()//成功关闭串口或串口丢失后的设置
  555. {
  556. autoSendTick.Stop();//串口丢失后要关闭自动发送
  557. autoSendCheck.IsChecked = false;//自动发送改为未选中
  558. WaitClose = true;//;//激活正在关闭状态字,用于在串口接收方法的invoke里判断是否正在关闭串口
  559. while (Listening)//判断invoke是否结束
  560. {
  561. DispatcherHelper.DoEvents(); //循环时,仍进行等待事件中的进程,该方法为winform中的方法,WPF里面没有,这里在后面自己实现
  562. }
  563. MessageBox.Show('串口已丢失');
  564. WaitClose = false;//关闭正在关闭状态字,用于在串口接收方法的invoke里判断是否正在关闭串口
  565. GetPort();//刷新可用串口
  566. SetAfterClose();//成功关闭串口或串口丢失后的设置
  567. }
  568. private void AvailableComCbobox_PreviewMouseDown(object sender, MouseButtonEventArgs e)//刷新可用串口
  569. {
  570. GetPort();//刷新可用串口
  571. }
  572. private void GetPort()//刷新可用串口
  573. {
  574. comList.Clear();//情况控件链接资源
  575. AvailableComCbobox.DisplayMemberPath = 'com1';
  576. AvailableComCbobox.SelectedValuePath = null;//路径都指为空,清空下拉控件显示,下面重新添加
  577. ports = new string[SerialPort.GetPortNames().Length];//重新定义可用串口数组长度
  578. ports = SerialPort.GetPortNames();//获取可用串口
  579. if (ports.Length > 0)//有可用串口
  580. {
  581. for (int i = 0; i < ports.Length; i++)
  582. {
  583. comList.Add(new customer() { com = ports[i] });//下拉控件里添加可用串口
  584. }
  585. AvailableComCbobox.ItemsSource = comList;//可用串口下拉控件资源路径
  586. AvailableComCbobox.DisplayMemberPath = 'com';//可用串口下拉控件显示路径
  587. AvailableComCbobox.SelectedValuePath = 'com';//可用串口下拉控件值路径
  588. }
  589. }
  590. private void sendClearBtn_Click(object sender, RoutedEventArgs e)//清空发送区
  591. {
  592. sendTBox.Text = '';
  593. }
  594. private void recClearBtn_Click(object sender, RoutedEventArgs e)//清空接收区
  595. {
  596. recTBox.Text = '';
  597. }
  598. private void countClearBtn_Click(object sender, RoutedEventArgs e)//计数清零
  599. {
  600. sendCount.Text = '0';
  601. recCount.Text = '0';
  602. }
  603. private void stopRecBtn_Click(object sender, RoutedEventArgs e)//暂停/开启接收按钮事件
  604. {
  605. if (recStaus == true)//当前为开启接收状态
  606. {
  607. recStaus = false;//暂停接收
  608. stopRecBtn.Content = '开启接收';//按钮显示为开启接收
  609. recPrompt.Visibility = Visibility.Visible;//显示已暂停接收提示
  610. recBorder.Opacity = 0;//接收区透明度改为0
  611. }
  612. else//当前状态为关闭接收状态
  613. {
  614. recStaus = true;//开启接收
  615. stopRecBtn.Content = '暂停接收';//按钮显示状态改为暂停接收
  616. recPrompt.Visibility = Visibility.Hidden;//隐藏已暂停接收提示
  617. recBorder.Opacity = 0.4;////接收区透明度改为0.4
  618. }
  619. }
  620. private void autoSendCheck_Click(object sender, RoutedEventArgs e)//自动发送控件点击事件
  621. {
  622. if (autoSendCheck.IsChecked == true && ComPort.IsOpen == true)//如果当前状态为开启自动发送且串口已打开,则开始自动发送
  623. {
  624. autoSendTick.Interval = TimeSpan.FromMilliseconds(Convert.ToInt32(Time.Text));//设置自动发送间隔
  625. autoSendTick.Start();//开始自动发送定时器
  626. }
  627. else//点击之前为开启自动发送状态,点击后关闭自动发送
  628. {
  629. autoSendTick.Stop();//关闭自动发送定时器
  630. }
  631. }
  632. private void Time_KeyDown(object sender, KeyEventArgs e)//发送周期文本控件-键盘按键事件
  633. {
  634. if (e.Key >= Key.D0 && e.Key <= Key.D9 || e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9)//只能输入数字
  635. {
  636. e.Handled = false;
  637. }
  638. else e.Handled = true;
  639. if (e.Key == Key.Enter)//输入回车
  640. {
  641. if (Time.Text.Length == 0 || Convert.ToInt32(Time.Text) == 0)//时间为空或时间等于0,设置为1000
  642. {
  643. Time.Text = '1000';
  644. }
  645. autoSendTick.Interval = TimeSpan.FromMilliseconds(Convert.ToInt32(Time.Text));//设置自动发送周期
  646. }
  647. }
  648. private void Time_TextChanged(object sender, TextChangedEventArgs e)//发送周期文本控件-文本内容改变事件,与上面Time_KeyDown事件相比,可以防止粘贴进来非数字数据
  649. {
  650. //只允许输入数字
  651. TextBox textBox = sender as TextBox;
  652. TextChange[] change = new TextChange[e.Changes.Count];
  653. byte[] checkText = new byte[textBox.Text.Length];
  654. bool result = true;
  655. e.Changes.CopyTo(change, 0);
  656. int offset = change[0].Offset;
  657. checkText = System.Text.Encoding.Default.GetBytes(textBox.Text);
  658. for (int i = 0; i < textBox.Text.Length; i++)
  659. {
  660. result &= 0x2F < (Convert.ToInt32(checkText[i])) & (Convert.ToInt32(checkText[i])) < 0x3A;//0x2f-0x3a之间是数字0-10的ASCII码
  661. }
  662. if (change[0].AddedLength > 0)
  663. {
  664. if (!result || Convert.ToInt32(textBox.Text) > 100000)//不是数字或数据大于100000,取消本次change
  665. {
  666. textBox.Text = textBox.Text.Remove(offset, change[0].AddedLength);
  667. textBox.Select(offset, 0);
  668. }
  669. }
  670. }
  671. private void Time_LostFocus(object sender, RoutedEventArgs e)//发送周期文本控件-失去事件
  672. {
  673. if (Time.Text.Length == 0 || Convert.ToInt32(Time.Text) == 0)//时间为空或时间等于0,设置为1000
  674. {
  675. Time.Text = '1000';
  676. }
  677. autoSendTick.Interval = TimeSpan.FromMilliseconds(Convert.ToInt32(Time.Text));//设置自动发送周期
  678. }
  679. //模拟 Winfrom 中 Application.DoEvents() 详见 http://www./html/study/WPF/2010/1216/4186.html?1292685167
  680. public static class DispatcherHelper
  681. {
  682. [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
  683. public static void DoEvents()
  684. {
  685. DispatcherFrame frame = new DispatcherFrame();
  686. Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrames), frame);
  687. try { Dispatcher.PushFrame(frame); }
  688. catch (InvalidOperationException) { }
  689. }
  690. private static object ExitFrames(object frame)
  691. {
  692. ((DispatcherFrame)frame).Continue = false;
  693. return null;
  694. }
  695. }
  696. private void defaultSet_Click(object sender, RoutedEventArgs e)//重置按钮click事件
  697. {
  698. RateListCbobox.SelectedValue = '9600';//波特率默认设置9600
  699. ParityComCbobox.SelectedValue = '0';//校验位默认设置值为0,对应NONE
  700. DataBitsCbobox.SelectedValue = '8';//数据位默认设置8位
  701. StopBitsCbobox.SelectedValue = '1';//停止位默认设置1
  702. }
  703. private void FileOpen(object sender, ExecutedRoutedEventArgs e)//打开文件快捷键事件crtl+O
  704. {
  705. OpenFileDialog open_fd = new OpenFileDialog();//调用系统打开文件窗口
  706. open_fd.Filter = 'TXT文本|*.txt';//文件过滤器
  707. if (open_fd.ShowDialog() == true)//选择了文件
  708. {
  709. sendTBox.Text = File.ReadAllText(open_fd.FileName);//读TXT方法1 简单,快捷,为StreamReader的封装
  710. //StreamReader sr = new StreamReader(open_fd.FileName);//读TXT方法2 复杂,功能强大
  711. //sendTBox.Text = sr.ReadToEnd();//调用ReadToEnd方法读取选中文件的全部内容
  712. //sr.Close();//关闭当前文件读取流
  713. }
  714. }
  715. private void FileSave(object sender, RoutedEventArgs e)//保存数据按钮crtl+S
  716. {
  717. SaveModWindow SaveMod = new SaveModWindow();//new保存数据方式窗口
  718. SaveMod.Owner = this;//赋予主窗口,子窗口打开后,再次点击主窗口,子窗口闪烁
  719. SaveMod.ShowDialog();//ShowDialog方式打开保存数据方式窗口
  720. if (SaveMod.mode == 'new')//保存为新文件
  721. {
  722. SaveNew();//保存为新文件
  723. }
  724. else if (SaveMod.mode == 'old')//保存到已有文件
  725. {
  726. SaveOld();//保存到已有文件
  727. }
  728. else//取消
  729. {
  730. return;
  731. }
  732. }
  733. private void SaveNew_Click(object sender, RoutedEventArgs e)//文件-保存-保存为新文件click事件
  734. {
  735. SaveNew();//保存为新文件
  736. }
  737. private void SaveOld_Click(object sender, RoutedEventArgs e)//文件-保存-保存到已有文件click事件
  738. {
  739. SaveOld();//保存到已有文件
  740. }
  741. private void SaveNew()//保存为新文件
  742. {
  743. if (recTBox.Text == string.Empty)//接收区数据为空
  744. {
  745. MessageBox.Show('接收区为空,无法保存!');
  746. }
  747. else
  748. {
  749. SaveFileDialog Save_fd = new SaveFileDialog();//调用系统保存文件窗口
  750. Save_fd.Filter = 'TXT文本|*.txt';//文件过滤器
  751. if (Save_fd.ShowDialog() == true)//选择了文件
  752. {
  753. File.WriteAllText(Save_fd.FileName, recTBox.Text);//写入新的数据
  754. File.AppendAllText(Save_fd.FileName, '\r\n------' + DateTime.Now.ToString() + '\r\n');//数据后面写入时间戳
  755. MessageBox.Show('保存成功!');
  756. }
  757. }
  758. }
  759. private void SaveOld()//保存到已有文件
  760. {
  761. if (recTBox.Text == string.Empty)//接收区数据为空
  762. {
  763. MessageBox.Show('接收区为空,无法保存!');
  764. }
  765. else
  766. {
  767. OpenFileDialog Open_fd = new OpenFileDialog();//调用系统保存文件窗口
  768. Open_fd.Filter = 'TXT文本|*.txt';//文件过滤器
  769. if (Open_fd.ShowDialog() == true)//选择了文件
  770. {
  771. File.AppendAllText(Open_fd.FileName, recTBox.Text);//在打开文件末尾写入数据
  772. File.AppendAllText(Open_fd.FileName, '\r\n------' + DateTime.Now.ToString() + '\r\n');//数据后面写入时间戳
  773. MessageBox.Show('添加成功!');
  774. }
  775. }
  776. }
  777. private void info_click(object sender, RoutedEventArgs e)//帮助-关于click事件
  778. {
  779. InfoWindow info = new InfoWindow();//new关于窗口
  780. info.Owner = this;//赋予主窗口,子窗口打开后,再次点击主窗口,子窗口闪烁
  781. info.Show();//ShowDialog方式打开关于窗口
  782. }
  783. private void feedBack_Click(object sender, RoutedEventArgs e)//帮助-反馈click事件
  784. {
  785. FeedBackWindow feedBack = new FeedBackWindow();//new反馈窗口
  786. feedBack.Owner = this;//赋予主窗口,子窗口打开后,再次点击主窗口,子窗口闪烁
  787. feedBack.ShowDialog();//ShowDialog方式打开反馈窗口
  788. }
  789. }
  790. }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多