欢迎来到C#上位机教程的零基础入门!本教程将帮助你从零开始学习C#编程,并使用C#构建上位机应用程序,本教程将以经典的串口工具开发为Demo,讲述一个项目如何去实现,本节内容不涉及到串口工具的具体功能实现,主要讲述下面几个概念:
1、异步线程更新UI:this.Invoke()
在上一章,我们重点就介绍了DataReceived事件,DataReceived在辅助线程中执行,数据要更新到UI的线程时,这个操作就要跨线程了,在 C# 中,this.Invoke() 是一个用于在 Windows 窗体应用程序中跨线程访问控件的方法。由于 Windows 窗体应用程序是单线程模型,即 UI 线程负责处理用户界面的绘制和事件处理,如果在其他线程中访问 UI 控件,可能会引发线程间操作无效的异常。为了解决这个问题,可以使用 this.Invoke() 方法来在 UI 线程上执行指定的委托或方法。该方法的语法如下:this.Invoke(delegate)其中,delegate 是一个委托或方法,它将在 UI 线程上同步执行。下面是一个示例,演示了如何在其他线程中更新 UI 控件的文本:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void UpdateTextBox(string text)
{
if (textBox.InvokeRequired)
{
// 在 UI 线程上同步执行委托
this.Invoke(new Action
}
else
{
textBox.Text = text;
}
}
private void button_Click(object sender, EventArgs e)
{
// 在新线程中更新文本框
Thread thread = new Thread(() =>
{
// 模拟耗时操作
Thread.Sleep(2000);
// 更新文本框
UpdateTextBox("Hello, World!");
});
thread.Start();
}
}
}
在上述示例中,当用户点击按钮时,会创建一个新线程,在新线程中模拟一个耗时操作,并通过 UpdateTextBox() 方法更新文本框的内容。由于在新线程中无法直接访问 UI 控件,因此使用 this.Invoke() 方法将更新操作委托到 UI 线程上执行,以避免线程间操作无效的异常。请注意,this.Invoke() 方法会阻塞当前线程,直到委托或方法在 UI 线程上执行完成。因此,在使用 this.Invoke() 方法时,需要注意避免在 UI 线程上执行耗时操作,以免导致界面卡顿或无响应。
2、数据接收缓存区和和数据发送缓存区的建立
首先为什么要建立缓存区
数据的临时存储:数据接收和发送缓存区用于临时存储从串口接收到的数据和待发送的数据。串口通信中,数据的传输速率可能会受到各种因素的影响,如串口的波特率、数据包的大小等。建立缓存区可以确保数据在传输过程中不会丢失,同时也可以方便地对数据进行处理和管理。
数据的处理和解析:通过建立数据接收缓存区,可以将从串口接收到的数据逐个字节地存储在缓存区中,以便进行后续的处理和解析。例如,可以根据特定的数据格式或协议对接收到的数据进行解析,提取出有用的信息。
数据的拼接和分割:有时候,从串口接收到的数据可能会被分成多个数据包进行传输。通过建立数据接收缓存区,可以将接收到的数据包逐个存储在缓存区中,然后根据特定的分隔符或数据包长度进行拼接,以还原完整的数据。
数据的发送控制:建立数据发送缓存区可以对待发送的数据进行控制和管理。例如,可以将待发送的数据存储在缓存区中,然后根据需要进行分批发送,或者根据特定的发送策略进行发送,以提高数据传输的效率和可靠性。
我们将通过实例List
动态大小:List
方便操作:List
可读性和易用性:使用 List
兼容性:List
下面使用简单的例子来演示了如何使用 List
using System;
using System.Collections.Generic;
using System.IO.Ports;
namespace SerialPortTool
{
class Program
{
static List
static List
static void Main(string[] args)
{
SerialPort serialPort = new SerialPort("COM1", 9600);
serialPort.DataReceived += SerialPort_DataReceived;
// 添加数据到发送缓存区
sendBuffer.AddRange(new byte[] { 0x01, 0x02, 0x03 });
// 从发送缓存区发送数据
SendData(serialPort);
// 接收数据并处理
ReceiveData();
Console.ReadLine();
}
static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort = (SerialPort)sender;
// 读取接收缓冲区中的数据
int bytesToRead = serialPort.BytesToRead;
byte[] buffer = new byte[bytesToRead];
serialPort.Read(buffer, 0, bytesToRead);
// 添加数据到接收缓存区
receiveBuffer.AddRange(buffer);
// 处理接收缓存区中的数据
ProcessReceivedData();
}
static void SendData(SerialPort serialPort)
{
// 发送发送缓存区中的数据
byte[] buffer = sendBuffer.ToArray();
serialPort.Write(buffer, 0, buffer.Length);
}
static void ReceiveData()
{
// 处理接收缓存区中的数据
ProcessReceivedData();
}
static void ProcessReceivedData()
{
// 处理接收缓存区中的数据
foreach (byte data in receiveBuffer)
{
// 处理每个字节的数据
Console.WriteLine(data);
}
// 清空接收缓存区
receiveBuffer.Clear();
}
}
}
在上述示例中,我们使用List
3、字符编码格式
在 C# 中处理串口工具中的字符编码格式,可以使用 Encoding 类提供的方法来进行编码和解码操作。Encoding 类位于 System.Text 命名空间中,提供了各种编码和解码方式,如 UTF-8、ASCII、Unicode 等。
下面是一些常见的字符编码处理操作:
将字符串编码为字节数组:
byte[] bytes = new byte[] { 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33 };
string str = Encoding.UTF8.GetString(bytes);
将字节数组解码为字符串:
byte[] bytes = new byte[] { 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33 };
string str = Encoding.UTF8.GetString(bytes);
在本示例中将使用“GB2312”编码格式,我们将用到 下面这个方法接收数据
receDataTB.Text = Encoding.GetEncoding("gb2312").GetString( receDataTB.Text = Encoding.GetEncoding("gb2312").GetString(receiveBuffer.ToArray()).Replace("\0", "\\0"););