C# .NET多窗体场景下如何扩展串口通信类支持第二个窗体?
解决串口通信类支持多窗体(Form1/Form2)的方案
核心思路
将串口类与具体窗体解耦,通过事件机制传递接收的数据,让任意窗体都能订阅串口消息;同时保留统一的发送接口,供所有窗体调用,避免绑定单一窗体的紧耦合问题。
修改步骤
1. 重构SerialComm类,移除Form1依赖并添加事件机制
修改后的串口类代码:
namespace SerialPort_GUI { // 定义接收数据的事件参数类,用于传递接收内容 public class SerialDataReceivedEventArgs : EventArgs { public string ReceivedData { get; set; } public byte[] ReceivedBytes { get; set; } // 如需字节数据可保留 } class SerialComm : SerialPort { private bool IsReceiving = false; private bool IsRuning = false; private bool IsPeriodSending = false; private int sleepTime = 200; Thread receivingThread = null; // 定义接收数据的事件,供外部窗体订阅 public event EventHandler<SerialDataReceivedEventArgs> DataReceived; // 无参构造函数,移除Form1依赖 public SerialComm() { base.WriteTimeout = 100; receivingThread = new Thread(DoReceive); IsRuning = true; receivingThread.Start(); } private void DoReceive() { while (IsRuning) { if (IsOpen && !IsReceiving) { IsReceiving = true; try { // 根据实际需求读取数据,示例为读取字符串 string data = ReadExisting(); // 如需字节数组可替换为:byte[] bytes = ReadBytes(1024); if (!string.IsNullOrEmpty(data)) { // 触发事件,将数据传递给订阅的窗体 DataReceived?.Invoke(this, new SerialDataReceivedEventArgs { ReceivedData = data // ReceivedBytes = bytes // 如需字节数据可启用 }); } } catch (Exception ex) { // 此处可添加异常日志或提示逻辑 } finally { IsReceiving = false; Thread.Sleep(sleepTime); } } else { Thread.Sleep(sleepTime); } } } // 封装公共发送方法,供所有窗体调用 public void SendData(string data) { if (IsOpen) { try { Write(data); } catch (Exception ex) { // 处理发送异常逻辑 } } } // 重载发送方法,支持字节数组 public void SendData(byte[] data) { if (IsOpen) { try { Write(data, 0, data.Length); } catch (Exception ex) { // 处理发送异常逻辑 } } } // 停止串口线程与连接的方法 public void Stop() { IsRuning = false; receivingThread?.Join(); Close(); } } }
2. 在Form1中使用重构后的SerialComm
全局声明串口实例,确保Form2可复用同一连接:
public partial class Form1 : Form { // 全局串口实例,Form2将使用同一实例 public SerialComm serialComm; public Form1() { InitializeComponent(); serialComm = new SerialComm(); // 订阅串口接收事件 serialComm.DataReceived += SerialComm_DataReceived; } private void SerialComm_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 跨线程更新UI需使用Invoke Invoke(new Action(() => { // Form1的UI更新逻辑,比如显示接收数据 txtReceived.Text += e.ReceivedData; })); } // 串口连接与发送密码逻辑 private void btnConnect_Click(object sender, EventArgs e) { try { serialComm.PortName = txtPortName.Text; serialComm.BaudRate = int.Parse(txtBaudRate.Text); serialComm.Open(); // 发送密码 serialComm.SendData("YourDevicePassword"); } catch (Exception ex) { MessageBox.Show("连接失败:" + ex.Message); } } // 打开Form2时传递串口实例 private void btnSettings_Click(object sender, EventArgs e) { Form2 form2 = new Form2(serialComm); form2.ShowDialog(); } // Form1关闭时停止串口 private void Form1_FormClosed(object sender, FormClosedEventArgs e) { serialComm.Stop(); } }
3. 在Form2中使用SerialComm实例
通过构造函数接收Form1传递的串口实例,实现发送与接收逻辑:
public partial class Form2 : Form { private SerialComm _serialComm; // 构造函数接收串口实例 public Form2(SerialComm serialComm) { InitializeComponent(); _serialComm = serialComm; // 订阅接收事件(如需在Form2处理响应) _serialComm.DataReceived += SerialComm_DataReceived; } private void SerialComm_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 跨线程更新UI Invoke(new Action(() => { // Form2的UI反馈逻辑 lblStatus.Text = "设备响应:" + e.ReceivedData; })); } // 示例:设置按钮1的发送逻辑 private void btnSetting1_Click(object sender, EventArgs e) { _serialComm.SendData("SETTING_1_COMMAND"); } // 示例:设置按钮2的发送逻辑 private void btnSetting2_Click(object sender, EventArgs e) { _serialComm.SendData("SETTING_2_COMMAND"); } // 其余10个按钮同理,调用SendData发送对应指令 // Form2关闭时取消事件订阅(可选) private void Form2_FormClosed(object sender, FormClosedEventArgs e) { _serialComm.DataReceived -= SerialComm_DataReceived; } }
关键说明
- 解耦设计:移除串口类对Form1的依赖,改用事件驱动,让任意窗体都能接入串口通信。
- 线程安全:串口接收在后台线程执行,更新UI必须使用
Invoke避免跨线程异常。 - 单实例复用:Form1与Form2共用同一SerialComm实例,确保串口连接唯一,避免重复打开端口。
内容的提问来源于stack exchange,提问作者SAHAR GOHARSHENASAN
相关产品推荐
相关产品推荐

