C#串口调试工具 (WPF/MVVM结构完整示例版)
前文
由于经常用到串口调试, 尽管有现成的软件, 因为前端时间涉及一个二次开发, 就因为一个RtsEnable设置, 折腾半天, 网上各种版本的也很多, 功能扩展的很开也多。所以现在自己做了一个够用版,基于自己的需求,简单的实现发送接收功能, 至于那些扩展功能可以自己根据需求添加。
正文
先上个运行效果图:

项目架构
该实例用的GalaSoft.Mvvm, 该插件可以直接在NuGet中并且添加。

1.串口参数 , 为了方便, 端口号并没有用动态加载的方式, 如下枚举结构:
/// <summary>
/// 端口号
/// </summary>
public enum Port
{
COM1,
COM2,
COM3,
COM4,
COM5,
COM6,
COM7,
COM8,
COM9,
COM10,
COM11,
COM12,
COM13,
COM14,
COM15,
COM16,
COM17,
COM18,
COM19,
COM20,
COM21,
COM22,
COM23,
COM24,
COM25,
COM26,
COM27,
COM28,
COM29,
COM30
} /// <summary>
/// 奇偶校验
/// </summary>
public enum CheckMode
{
None = ,
Odd = ,
Even = ,
Mark = ,
Space =
} /// <summary>
/// 停止位
/// </summary>
public enum StopBit
{
One=,
Two=,
OnePointFive=,
}
2.串口参数配置类 ,
作用: 主要用于绑定界面的参数选项。
/// <summary>
/// 串口参数设置类
/// </summary>
public class ComParameterConfig : ViewModelBase
{
public ComParameterConfig()
{
Port = System.Enum.GetValues(typeof(Port));
CheckMode = System.Enum.GetValues(typeof(CheckMode));
StopBit = System.Enum.GetValues(typeof(StopBit));
BaudRate = new List<int>() { , , , , , , , , , , , , , };
DataBit = new List<int>() { , , };
} private Array port;
private Array checkMode;
private Array stopBit;
private List<int> dataBit;
private List<int> baudRate; /// <summary>
/// 端口
/// </summary>
public Array Port
{
get { return port; }
set { port = value; RaisePropertyChanged(); }
} /// <summary>
/// 校验模式
/// </summary>
public Array CheckMode
{
get { return checkMode; }
set { checkMode = value; RaisePropertyChanged(); }
} /// <summary>
/// 停止位
/// </summary>
public Array StopBit
{
get { return stopBit; }
set { stopBit = value; RaisePropertyChanged(); }
} /// <summary>
/// 波特率
/// </summary>
public List<int> BaudRate
{
get { return baudRate; }
set { baudRate = value; RaisePropertyChanged(); }
} /// <summary>
/// 数据位
/// </summary>
public List<int> DataBit
{
get { return dataBit; }
set { dataBit = value; RaisePropertyChanged(); }
} }
3.当前配置参数类
作用: 用于保存当前的串口参数、串口功能开关接收数据等业务。
/// <summary>
/// 当前配置参数
/// </summary>
public class CurrentParameter : ViewModelBase
{
#region Private private int baudRdate = ;
private int dataBit = ;
private Port port;
private CheckMode checkMode;
private StopBit stopBit = StopBit.One;
private SerialPort serialPort; private string dataReceiveInfo;
private string sendData;
private bool isOpen;
private bool receiveFormat16 = true;
private bool sendFormat16 = true; private int sendCount;
private int receiveCount; #endregion #region UI绑定参数 /// <summary>
/// 发送数量
/// </summary>
public int SendCount
{
get { return sendCount; }
set { sendCount = value; RaisePropertyChanged(); }
} /// <summary>
/// 接收数量
/// </summary>
public int ReceiveCount
{
get { return receiveCount; }
set { receiveCount = value; RaisePropertyChanged(); }
} /// <summary>
/// 接收区16进制
/// </summary>
public bool ReceiveFormat16
{
get { return receiveFormat16; }
set { receiveFormat16 = value; RaisePropertyChanged(); }
} /// <summary>
/// 接收区数据
/// </summary>
public string DataReceiveInfo
{
get { return dataReceiveInfo; }
set { dataReceiveInfo = value; RaisePropertyChanged(); }
} /// <summary>
/// 发送数据
/// </summary>
public string SendData
{
get
{
return sendData;
}
set { sendData = value; RaisePropertyChanged(); }
} /// <summary>
/// 发送区16进制
/// </summary>
public bool SendFormat16
{
get { return sendFormat16; }
set { sendFormat16 = value; RaisePropertyChanged(); }
} #endregion #region 串口参数信息 /// <summary>
/// 开关
/// </summary>
public bool IsOpen
{
get { return isOpen; }
set { isOpen = value; RaisePropertyChanged(); }
} /// <summary>
/// 数据位
/// </summary>
public int DataBit
{
get { return dataBit; }
set { dataBit = value; RaisePropertyChanged(); }
} /// <summary>
/// 波特率
/// </summary>
public int BaudRdate
{
get { return baudRdate; }
set { baudRdate = value; RaisePropertyChanged(); }
} /// <summary>
/// 端口
/// </summary>
public Port Port
{
get { return port; }
set { port = value; RaisePropertyChanged(); }
} /// <summary>
/// 校验
/// </summary>
public CheckMode CheckMode
{
get { return checkMode; }
set { checkMode = value; RaisePropertyChanged(); }
} /// <summary>
/// 停止位
/// </summary>
public StopBit StopBit
{
get { return stopBit; }
set { stopBit = value; RaisePropertyChanged(); }
} /// <summary>
/// COM
/// </summary>
public SerialPort SerialPort
{
get { return serialPort; }
set { serialPort = value; RaisePropertyChanged(); }
} #endregion #region 串口操作方法 /// <summary>
/// 开启串口
/// </summary>
/// <returns></returns>
public bool Open()
{
if (serialPort != null && serialPort.IsOpen)
{
return Close();
}
try
{
serialPort = new SerialPort();
serialPort.DataBits = this.DataBit;
serialPort.StopBits = ComHelper.GetStopBits(this.StopBit.ToString());
serialPort.Parity = ComHelper.GetParity(this.CheckMode.ToString());
serialPort.PortName = this.Port.ToString();
serialPort.RtsEnable = true;
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open(); if (serialPort.IsOpen)
return IsOpen = true;
else
return IsOpen = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return IsOpen = false;
} /// <summary>
/// 关闭串口
/// </summary>
/// <returns></returns>
public bool Close()
{
try
{
if (serialPort.IsOpen)
{
serialPort.Close();
return IsOpen = serialPort.IsOpen;
}
else
{
return IsOpen = serialPort.IsOpen;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return IsOpen = false;
}
} /// <summary>
/// 发送数据
/// </summary>
public void Send()
{ if (SendFormat16)
{
byte[] bytes = CRC.StringToHexByte(SendData);
this.SerialPort.Write(bytes, , bytes.Length);
SendCount = bytes.Length; //不做增量
}
else
{
this.SerialPort.Write(SendData);
SendCount = SendData.Length;
}
Messenger.Default.Send("", "PlaySendFlashing");
} /// <summary>
/// 清空接收区
/// </summary>
public void Clear()
{
this.DataReceiveInfo = string.Empty;
} /// <summary>
/// 清空发送区和缓存区
/// </summary>
public void ClearText()
{
this.SendData = string.Empty;
this.SendCount = ;
this.ReceiveCount = ;
} #endregion /// <summary>
/// 返回事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Messenger.Default.Send("", "PlayReciveFlashing"); byte[] readBuffer = new byte[SerialPort.ReadBufferSize];
SerialPort.Read(readBuffer, , readBuffer.Length); ReceiveCount = SerialPort.ReceivedBytesThreshold; //不做增量 if (ReceiveFormat16)
{
//不做增量
DataReceiveInfo = CRC.ByteToString(readBuffer, true);
}
else
{
DataReceiveInfo = Encoding.ASCII.GetString(readBuffer);
} }
}
4.核心MainViewModel类
作用: 关联首页的上下文, 通过DataContext绑定, 关联界面元素、命令等作用。
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
ComParameterConfig = new ComParameterConfig();
CurrentParameter = new CurrentParameter();
} private ComParameterConfig comParameter; /// <summary>
/// 参数类
/// </summary>
public ComParameterConfig ComParameterConfig
{
get { return comParameter; }
set { comParameter = value; RaisePropertyChanged(); }
} private CurrentParameter currentParameter; /// <summary>
/// 当前配置参数
/// </summary>
public CurrentParameter CurrentParameter
{
get { return currentParameter; }
set { currentParameter = value; RaisePropertyChanged(); }
} #region Command private RelayCommand _ToOpen;
public RelayCommand ToOpen
{
get
{
if (_ToOpen == null)
{
_ToOpen = new RelayCommand(Open);
}
return _ToOpen;
}
set
{
_ToOpen = value;
}
}
/// <summary>
/// 根据配置打开端口
/// </summary>
public void Open()
{
this.CurrentParameter.Open();
} private RelayCommand _ToClick;
public RelayCommand ToClick
{
get
{
if (_ToClick == null)
{
_ToClick = new RelayCommand(Click);
}
return _ToClick;
}
set
{
_ToClick = value;
}
} /// <summary>
/// 发送数据
/// </summary>
public void Click()
{
this.CurrentParameter.Send();
} private RelayCommand _ToClear; public RelayCommand ToClear
{
get
{
if (_ToClear == null)
{
_ToClear = new RelayCommand(Clear);
}
return _ToClear;
}
set
{
_ToClear = value;
}
} /// <summary>
/// 清空接收区
/// </summary>
public void Clear()
{
this.CurrentParameter.Clear();
} private RelayCommand _ToClearText; public RelayCommand ToClearText
{
get
{
if (_ToClearText == null)
{
_ToClearText = new RelayCommand(ClearText);
}
return _ToClearText;
}
set
{
_ToClearText = value;
}
} /// <summary>
/// 清空界面值
/// </summary>
public void ClearText()
{
this.CurrentParameter.ClearText();
} #endregion
}
5.CRC校验核心类
作用:主要实现数据校验, 含ModbusCR标准校验
/// <summary>
/// CRC校验
/// </summary>
public class CRC
{ #region CRC16 public static byte[] CRC16(byte[] data)
{
int len = data.Length;
if (len > )
{
ushort crc = 0xFFFF; for (int i = ; i < len; i++)
{
crc = (ushort)(crc ^ (data[i]));
for (int j = ; j < ; j++)
{
crc = (crc & ) != ? (ushort)((crc >> ) ^ 0xA001) : (ushort)(crc >> );
}
}
byte hi = (byte)((crc & 0xFF00) >> ); //高位置
byte lo = (byte)(crc & 0x00FF); //低位置 return new byte[] { hi, lo };
}
return new byte[] { , };
}
#endregion #region ToCRC16 public static string ToCRC16(string content)
{
return ToCRC16(content, Encoding.UTF8);
} public static string ToCRC16(string content, bool isReverse)
{
return ToCRC16(content, Encoding.UTF8, isReverse);
} public static string ToCRC16(string content, Encoding encoding)
{
return ByteToString(CRC16(encoding.GetBytes(content)), true);
} public static string ToCRC16(string content, Encoding encoding, bool isReverse)
{
return ByteToString(CRC16(encoding.GetBytes(content)), isReverse);
} public static string ToCRC16(byte[] data)
{
return ByteToString(CRC16(data), true);
} public static string ToCRC16(byte[] data, bool isReverse)
{
return ByteToString(CRC16(data), isReverse);
}
#endregion #region ToModbusCRC16 public static string ToModbusCRC16(string s)
{
return ToModbusCRC16(s, true);
} public static string ToModbusCRC16(string s, bool isReverse)
{
return ByteToString(CRC16(StringToHexByte(s)), isReverse);
} public static string ToModbusCRC16(byte[] data)
{
return ToModbusCRC16(data, true);
} public static string ToModbusCRC16(byte[] data, bool isReverse)
{
return ByteToString(CRC16(data), isReverse);
}
#endregion #region ByteToString public static string ByteToString(byte[] arr, bool isReverse)
{
try
{
byte hi = arr[], lo = arr[];
return Convert.ToString(isReverse ? hi + lo * 0x100 : hi * 0x100 + lo, ).ToUpper().PadLeft(, '');
}
catch (Exception ex) { throw (ex); }
} public static string ByteToString(byte[] arr)
{
try
{
return ByteToString(arr, true);
}
catch (Exception ex) { throw (ex); }
} #endregion #region StringToHexString public static string StringToHexString(string str)
{
StringBuilder s = new StringBuilder();
foreach (short c in str.ToCharArray())
{
s.Append(c.ToString("X4"));
}
return s.ToString();
} #endregion #region StringToHexByte private static string ConvertChinese(string str)
{
StringBuilder s = new StringBuilder();
foreach (short c in str.ToCharArray())
{
if (c <= || c >= )
{
s.Append(c.ToString("X4"));
}
else
{
s.Append((char)c);
}
}
return s.ToString();
} private static string FilterChinese(string str)
{
StringBuilder s = new StringBuilder();
foreach (short c in str.ToCharArray())
{
if (c > && c < )
{
s.Append((char)c);
}
}
return s.ToString();
} /// <summary>
/// 字符串转16进制字符数组
/// </summary>
/// <param name="hex"></param>
/// <returns></returns>
public static byte[] StringToHexByte(string str)
{
return StringToHexByte(str, false);
} /// <summary>
/// 字符串转16进制字符数组
/// </summary>
/// <param name="str"></param>
/// <param name="isFilterChinese">是否过滤掉中文字符</param>
/// <returns></returns>
public static byte[] StringToHexByte(string str, bool isFilterChinese)
{
string hex = isFilterChinese ? FilterChinese(str) : ConvertChinese(str); //清除所有空格
hex = hex.Replace(" ", "");
//若字符个数为奇数,补一个0
hex += hex.Length % != ? "" : ""; byte[] result = new byte[hex.Length / ];
for (int i = , c = result.Length; i < c; i++)
{
result[i] = Convert.ToByte(hex.Substring(i * , ), );
}
return result;
}
#endregion }
WPF技术点:
1.自定义样式按钮
<Style x:Key="CommonButtonBase" TargetType="{x:Type Button}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value=""/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value=""/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius=""
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#000000"/>
<Setter Property="Opacity" Value="0.1"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="#FFFF00"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="false">
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type Button}" x:Key="Btn0093EABase" BasedOn="{StaticResource CommonButtonBase}">
<Setter Property="Background" Value="#0093EA"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value=""/>
<Setter Property="Height" Value=""/>
<Setter Property="Margin" Value=""/>
</Style>
2.转换器用于绑定按钮
public class FontConverters : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && bool.TryParse(value.ToString(), out bool result))
{
if (result)
{
return "关闭串口";
}
}
return "打开串口";
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
//用于绑定UI的颜色状态显示
public class ColorConverters : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && bool.TryParse(value.ToString(), out bool result))
{
if (result)
{
return new SolidColorBrush((Color)System.Windows.Media.ColorConverter.ConvertFromString("#2E8B57"));
}
}
return new SolidColorBrush((Color)System.Windows.Media.ColorConverter.ConvertFromString("#FF6347"));
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
3.引用字体
<TextBlock Text="" Margin="20 5 0 5" FontFamily="pack://application:,,,/Font/#iconfont"
Foreground="White" FontSize="" VerticalAlignment="Center"/>
4.绑定命令和元素
<TextBlock Text="端 口:" Style="{DynamicResource TxtComStyle}"/>
<ComboBox Grid.Row="" Grid.Column="" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.Port}"
ItemsSource="{Binding ComParameterConfig.Port}"
/>
<TextBlock Text="波 特 率:" Style="{DynamicResource TxtComStyle}"/>
<ComboBox Grid.Row="" Grid.Column="" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.BaudRdate}"
ItemsSource="{Binding ComParameterConfig.BaudRate}"
/>
<TextBlock Text="数 据 位:" Style="{DynamicResource TxtComStyle}"/>
<ComboBox Grid.Row="" Grid.Column="" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.DataBit}"
ItemsSource="{Binding ComParameterConfig.DataBit}"
/>
<TextBlock Text="校 验 位:" Style="{DynamicResource TxtComStyle}"/>
<ComboBox Grid.Row="" Grid.Column="" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.CheckMode}"
ItemsSource="{Binding ComParameterConfig.CheckMode}"
/>
<TextBlock Text="停 止 位:" Style="{DynamicResource TxtComStyle}"/>
<ComboBox Grid.Row="" Grid.Column="" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.StopBit}"
ItemsSource="{Binding ComParameterConfig.StopBit}"
/>
<TextBlock Text="状 态:" Style="{DynamicResource TxtComStyle}"/>
<TextBlock Text="" Style="{DynamicResource TxtComStyle1}"
Foreground="{Binding CurrentParameter.IsOpen,Converter={StaticResource ColorConverters}}" />
写在最后
主项目的结构图 , 如下:

C#串口调试工具 (WPF/MVVM结构完整示例版)的更多相关文章
- WPF+MVVM+EF示例1
实现了那些功能,先看看效果图: 项目工程目录: 接下来开始具体的步骤: 第一步:在VS中新建工程 第二步:使用NuGet 安装EntityFramework 第三步:使用NuGet 安装EntityF ...
- WPF MVVM架构 EF、WCF、IOC 设计示例经典
概要 该演示项目利用WPF应用程序构建的MVVM架构示例, 运用了Unity容器接口注入, MVVM的经典设计, 后台利用的EF+WCF. 后台实现: 从数据库生成的emdx 结合上下文进行数据交互, ...
- WPF MVVM+EF增删改查 简单示例(二) 1对1 映射
WPF MVVM+EF增删改查 简单示例(一)实现了对学生信息的管理. 现在需求发生变更,在录入学生资料的时候同时需要录入学生的图片信息,并且一名学生只能有一张图片资料.并可对学生的图片资料进行更新. ...
- WPF MVVM模式的一些理解
/*本文转自 http://www.cnblogs.com/sirkevin/archive/2012/11/28/2793471.html */ 使用WPF+Mvvm开发一年多,期间由于对Mvvm模 ...
- WPF MVVM初体验
首先MVVM设计模式的结构, Views: 由Window/Page/UserControl等构成,通过DataBinding与ViewModels建立关联: ViewModels:由一组命令,可以绑 ...
- 串口调试工具(Python2.7+pyserial+Tkinter)
需要与串口设备进行通讯,那么一个调试工具是必须的. 根据我自己的需要,写了个简易版本的串口调试工具: 预览图: ====================== 项目结构: COM --SerialHel ...
- 串口调试者v2.1------开源c#串口调试工具
第一步:上图 第二步:上代码 >>>>>>>>>>>源代码下载<<<<<<<<< ...
- WPF自学入门(十)WPF MVVM简单介绍
前面文章中,我们已经知道,WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通知去更新UI. 我们不管 ...
- WPF自学入门(十一)WPF MVVM模式Command命令
在WPF自学入门(十)WPF MVVM简单介绍中的示例似乎运行起来没有什么问题,也可以进行更新.但是这并不是我们使用MVVM的正确方式.正如上一篇文章中在开始说的,MVVM的目的是为了最大限度地降低了 ...
随机推荐
- Java web课程学习之Request和Response
request和response l HttpServletRequest l 请求转发 l HttpServletResponse l 请求重定向 请求流程 每次请求service(),都会由容 ...
- UVALive-8077 Brick Walls 找规律
题目链接:https://cn.vjudge.net/problem/UVALive-8077 题意 有一个用砖头磊起来的墙,现在又有一只蚂蚁,想沿着砖缝从起点跑到终点. 问最短路长度. 思路 找规律 ...
- TI低功耗蓝牙(BLE)介绍
TI低功耗蓝牙(BLE)介绍 本文档翻译和修改自参考资料:CC2540Bluetooth Low Energy Software Developer’s Guide (Rev. B),部分图片直接引用 ...
- 小学生都能学会的python(列表[ ])
小学生都能学会的python(列表[ ]) 1. 什么是列表(list) 能装东西的东西 列表中装的数据是没有限制的, 大小基本上是够用的 列表使用[]来表示. 在列表中每个元素与元素之间用逗号隔开 ...
- vue-router的创建(1)
vue-router的创建 <!doctype html> <html lang="en"> <head> <meta charset=& ...
- Jquery-操作select下拉菜单
jQuery获取Select选择的Text和Value: 1. var checkText=jQuery("#select_id").find("option:selec ...
- zzulioj--1712--Monty Hall problem(蒙提霍尔问题)
1721: Monty Hall problem Time Limit: 1 Sec Memory Limit: 128 MB Submit: 186 Solved: 71 SubmitSt ...
- angular4(1)angular脚手架
angular2之后有了类似于vue-cli的脚手架工具,很方便的帮助我们搭建项目: 1.安装angular命令行工具:npm install @angular/cli -g 2.检测angular- ...
- [jzoj 4528] [GDOI2019模拟2019.3.26] 要换换名字 (最大权闭合子图)
题目链接: https://jzoj.net/senior/#contest/show/2683/0 题目: 题解: 不妨枚举一个点,让两颗树都以这个点为根,求联通块要么点数为$0$,要么包括根(即联 ...
- [JZOJ 5912] [NOIP2018模拟10.18] VanUSee 解题报告 (KMP+博弈)
题目链接: https://jzoj.net/senior/#contest/show/2530/2 题目: 众所周知,cqf童鞋对哲学有着深入的理解和认识,并常常将哲学思想应用在实际生活中,例如锻炼 ...