C#自制用户控件
——串口控件的二次开发
C# 是一个简单的、现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的。其开发工具(IDE)Visual Studio是及其强大,用过该IDE的朋友都知道其控件库中自带一个名称为 SerialPort 的控件(截图如下),它主要是用来进行串口工具开发,很多.NET平台下的串口工具都少不了他的身影。下面就简单介绍一下如何利用C#进行用户控件开发,然后再在自带串口控件的基础上,重新开发一个可视化串口控件(截图如下)。
1.准备工作
以下是博主的开发环境,版本的不同可能有所差异:
- Windows 10
- Visual Studio 2017
在对串口控件进行二次开发之前,推荐大家可以看一下下面这篇博文,介绍的是用户自定义控件中的属性、事件和其他一下特性的总结。
【C#中的自定义控件中的属性、事件及一些相关特性的总结】
在上面这篇博文中我们知道了我们所使用的控件中的属性、事件是如何定义的,然后我们就可以在其基础上进行控件的二次开发了。
2.详细步骤
1.新建项目
打开VS2017,新建项目选择类库,名称改为MySerial(名称根据各自的习惯修改就行)。
2. 添加用户控件
项目创建完成后,在项目里会有一个名称为Class1的文件,这里直接删除就行,接下来右键项目添加用户控件,名称改为“MySerial”,大小改为(245, 66),为了美观,字体改为微软雅黑。
3. 添加相关控件
从工具栏中分别拖动 一个SerialPort(Name为Serial)、两个Label、一个Comobox(Name为cbx_Port和cbx_BaudRate)、一个PictureBox(Name为pic_Stat,Size为(25,25))、一个Button(Name为btn_Con,Text为连接),在cbx_BaudRate的Items中添加以下几种常见的波特率,同时布局如下:
添加素材,素材命名如下(自己可以找其他素材替换):
4. 添加操作类
在项目里新建文件夹,名称为Class_Serial,添加Serial_Info.cs和Serial_Set.cs两个类。
Serial_Info.cs(保存单个串口信息):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MySerial.Class_Serial
{
public class Serial_Info
{
/// <summary>
/// 串口号
/// </summary>
public string Name { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Des { get; set; }
/// <summary>
/// 方法覆盖,返回串口相关信息字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Name + " - " + Des;
}
}
}
Serial_Set.cs(获取本机符合条件的串口):
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
namespace MySerial.Class_Serial
{
public class Serial_Set
{
/// <summary>
/// 获取串口号
/// </summary>
/// <returns>串口类型组</returns>
public static List<Serial_Info> GetPorts()
{
List<Serial_Info> back = new List<Serial_Info>();
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity")) //调用WMI,获取Win32_PnPEntity,即所有设备
{
var hardInfos = searcher.Get();
foreach (var hardInfo in hardInfos)
{
if ((hardInfo.Properties["Name"].Value != null) &&
(hardInfo.Properties["Name"].Value.ToString().Contains("COM")) &&
(hardInfo.Properties["Name"].Value.ToString().ToUpper().Contains("USB"))) //筛选出名称中包含COM的
{
Serial_Info temp = new Serial_Info();
string s = hardInfo.Properties["Name"].Value.ToString(); //获取名称
int p = s.IndexOf('(');
temp.Des = s.Substring(0, p); //截取描述(名称)
temp.Name = s.Substring(p + 1, s.Length - p - 2); //截取串口号
back.Add(temp);
}
}
searcher.Dispose();
}
return back;
}
}
}
【注意】:如果出现如下错误,原因是没有添加对应引用文件,只需要右键项目中的引用,点击添加引用,选择程序集,在搜索中搜索Manage,找到如图项,勾选后点击确定就行:
5. 控件相关代码添加
- 在串口的Load事件中添加以下代码,设置默认波特率。
if (cbx_BaudRate.Items.Count > 0)
cbx_BaudRate.SelectedIndex = 5; //默认波特率 9600
- 在准备工作里已经知道如何设置用户自定义的属性以及方法,所以不再作详细介绍。
- 添加两个方法,用户刷新串口列表和调用控件中的SerialPort发送数据:
#region 方法
/// <summary>
/// 刷新串口列表
/// </summary>
/// <returns>返回可用串口数</returns>
public int Serial_Refresh()
{
cbx_Port.Items.Clear();
foreach (Class_Serial.Serial_Info csi in Class_Serial.Serial_Set.GetPorts())
{
cbx_Port.Items.Add(csi.ToString());
}
if (cbx_Port.Items.Count > 0)
cbx_Port.SelectedIndex = 0;
return cbx_Port.Items.Count;
}
/// <summary>
/// 向串口写数据指令
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
public bool Serial_Write(string _str)
{
try
{
byte[] buffer = System.Text.Encoding.Default.GetBytes(_str); //字符串转换为字节数组
if (Serial.IsOpen)
{
Serial.Write(buffer, 0, buffer.Length);
return true;
}
else
return false;
}
catch
{
return false;
}
}
#endregion
- 添加pic_Stat的Click单击事件,用于刷新串口列表。
/// <summary>
/// 图片单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pbx_Stat_Click(object sender, EventArgs e)
{
Serial_Refresh();
}
- 添加两个变量,用于表示串口开关状态和存放串口消息,添加本控件的串口消息接收事件和串口状态改变事件,添加控件串口状态只读属性。
bool _b_Serial_Sta = false; //串口状态
private StringBuilder Builder = new StringBuilder(); //消息存放变量
[Browsable(true), Description("串口接收消息事件")]
/// <summary>
/// 串口接收消息事件
/// </summary>
public event EventHandler Serial_Received;
[Browsable(true), Description("串口状态改变事件")]
/// <summary>
/// 串口状态改变时间
/// </summary>
public event EventHandler Serial_StataChange;
#region //控件属性
[Browsable(true), Description("串口状态")]
/// <summary>
/// 串口状态
/// </summary>
public bool IsOpen
{
get { return Serial.IsOpen; }
}
#endregion
- 在本控件的Serial控件添加消息接收事件,用户转发消息。
/// <summary>
/// 本控件中的串口控件消息接收事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Serial_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (Serial.IsOpen)
{
byte[] Buffer_Get = null;
try
{
int N = this.Serial.BytesToRead;
Buffer_Get = new byte[N];
this.Serial.Read(Buffer_Get, 0, N);
Builder.Clear(); //清空
Builder.Append(System.Text.Encoding.Default.GetString(Buffer_Get)); //转换为字符串
if (Serial_Received != null && Builder.ToString().Trim() != "")
{
//调用本串口事件转发消息
Serial_Received(Builder, e);
}
}
finally
{
}
}
}
- 然后就是添加连接按钮单击事件,用于打开串口,当串口打开时,图片变为绿色小勾,未打开时为红色原点,串口被占用或者无法打开时为红色感叹号。
/// <summary>
/// 打开关闭串口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Con_Click(object sender, EventArgs e)
{
try
{
if (!_b_Serial_Sta)
{
if (cbx_Port.Text == "" || cbx_BaudRate.Text == "")
{
MessageBox.Show("请选择对应串口和波特率后继续!", "提示");
return;
}
Serial.PortName = cbx_Port.Text.Split('-')[0];
Serial.BaudRate = int.Parse(cbx_BaudRate.Text);
Serial.Open();
btn_Con.Text = "断开";
cbx_Port.Enabled = false;
cbx_BaudRate.Enabled = false;
pbx_Stat.Enabled = false;
pbx_Stat.Image = Properties.Resources.open;
_b_Serial_Sta = true;
}
else
{
if (Serial.IsOpen)
{
Serial.Close();
}
btn_Con.Text = "连接";
cbx_Port.Enabled = true;
cbx_BaudRate.Enabled = true;
pbx_Stat.Enabled = true;
pbx_Stat.Image = Properties.Resources.lost;
_b_Serial_Sta = false;
}
if (Serial_StataChange != null)//触发串口状态改变事件
Serial_StataChange(_b_Serial_Sta, null);
}
catch
{
pbx_Stat.Image = Properties.Resources.lost1;
MessageBox.Show("请检查串口号是否正确或串口是否被占用!", "打开失败");
}
}
- 添加默认事件,我们使用Button按钮时,双击控件就会直接添加一个单击事件,这个单击事件就是默认控件,我们只需要在
public partial class MySerial : UserControl
上面添加以下代码,就可以实现双击添加串口消息接收事件。
[DefaultEvent("Serial_Received")]
- 最后一步,右键项目,点击生成,在我们的项目Debug文件夹就会有一个MySerial.dll文件,在其他项目可以调用该文件从而添加该控件。
3.控件测试
- 右键本项目解决方案,选择添加>添加新项目,选择Windows窗体应用,名称为Demo。
- 项目创建完成后,由于是同一个解决方案,我们在工具栏中将会看见一个MySerial组件,我们将该项下的MySerial拖动到窗体,就能看到我们自制的控件,同时,点击该控件的属性,就会看见如图的两个我们添加的只读属性和事件。
以此添加两个TextBox控件、两个Label控件和一个Button控件,用于创建一个简单的串口助手,布局如下 。
双击我们自制的控件,添加消息接收事件操作代码,然后双击发送按钮,添加发送消息相关代码,在发送的时候可以调用我们添加的串口状态属性来判断串口是否打开。
/// <summary>
/// 串口消息接收事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void mySerial1_Serial_Received(object sender, EventArgs e)
{
Invoke((Action)delegate () //委托显示消息
{
textBox1.Text = sender.ToString();
});
}
/// <summary>
/// 发送按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
if (mySerial1.IsOpen) //调用属性判断串口是否打开,打开则发送,否则提示串口未打开
{
mySerial1.Serial_Write(textBox2.Text);
}
else
{
MessageBox.Show("串口未打开!","提示");
}
}
- 右键Demo项目,点击设为启动项目,编译运行,点击红色原点图片刷新串口列表,选择串口,点击连接,连接成功后发送消息,效果如下。
- 不知道大家有没有记得,我们一共添加了两个事件,一个是消息接收事件,一个是串口状态改变事件,接下来测试串口状态改变事件点击控件,从事件窗体中添加串口状态改变事件添加以下处理代码,运行效果如下。
/// <summary>
/// 串口状态改变事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void mySerial1_Serial_StataChange(object sender, EventArgs e)
{
MessageBox.Show("串口状态:"+mySerial1.IsOpen.ToString(),"提示");
}
4.源码
到此,控件就制作完成,感兴趣的可以将其拓展为功能强大的串口可视化控件。
项目下载地址:可视化串口控件MySerial
以下是基于该控件写的串口调试助手截图。