C#WinForm实现跨平台串口通讯(c# 跨平台gui)

C#WinForm实现跨平台串口通讯(c# 跨平台gui)

编程文章jaq1232025-05-06 12:44:0212A+A-

一、效果展示

二、引用dll

using Gtk;
using System.IO.Ports;

三、代码实现

界面实现

//操作面板
private VBox layout = new VBox();
private HBox mainBox=new HBox();
private SerialPort mSerialPort;
private Thread receiveThread;
private Statusbar statusbar;
private int sendByteCount,recevieByteCount=0;
private string cacheFile=System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"sp.cache");


JsonSerializerOptions options = new JsonSerializerOptions { 
	ReferenceHandler= ReferenceHandler.IgnoreCycles
};
public MainWindow():base(WindowType.Toplevel)
{
	this.Build();
	InitHistoryConfig();
}




#region 状态栏
void UpdateStatus()
{
	string comState = "COM Closed";
	if (mSerialPort != null && mSerialPort.IsOpen)
	{
		comState = #34;{mSerialPort.PortName} Opened";
	}
	string status = string.Format(" STATUS: : {0}      S: {1}      R:{2}", comState, sendByteCount, recevieByteCount);
	statusbar.Pop (0); 
	statusbar.Push (0, status);
}
#endregion


#region 变量声明
//串口设置
private ComboBox comboCom;
//波特率
private ComboBox comboRate;
//停止位
private ComboBox comboStop;
//数据位
private ComboBox comboData;
//奇偶校验
private ComboBox comboJo;
//16进制显示
private CheckButton ck16;
//RTX设置
private CheckButton ckRtx;
//DTR设置
private CheckButton ckDtr;
//16进制发送
private CheckButton ckS16;
//新行发送
private CheckButton ckNewline;
//定时发送
private CheckButton ckTime;
//定时毫秒
private Entry txtTime;
//打开串口按钮
private Button btnSp;
//清除接受按钮
private Button btnClear;
//数据接收区
private TextView textResult;
//数据发送区域
private TextView textSend;
//发送数据
private Button btnSend;
//清除数据
private Button btnSendClear;
//定时器ID
private uint timerSendId = 0;


private Gdk.Pixbuf img;
private Gtk.Image imgRender;
#endregion


#region 界面布局
private void Build()
{
	this.WindowPosition = WindowPosition.Center;
	this.SetDefaultSize(600, 600);
	this.Title = "串口调试助手v1.0.00";
	this.Resizable = false;
	this.DeleteEvent += MainWindow_DeleteEvent;
	this.Add(layout);


	//统计数据
	statusbar = new Statusbar();
	statusbar.Visible = true;
	UpdateStatus();


	layout.PackStart(mainBox, false, false, 0);
	layout.PackEnd(statusbar, false, false, 0);
	this.BuildLayout();
	this.ShowAll();
}
private void BuildLayout()
{
	#region 两列布局
	VBox leftBox = new VBox();
	leftBox.Margin = 5;
	leftBox.SetSizeRequest(200, 400);
	this.mainBox.Add(leftBox);


	VBox rightBox = new VBox();
	rightBox.SetSizeRequest(400, 600);
	rightBox.Margin = 5;
	this.mainBox.Add(rightBox);
	#endregion


	#region 串口操作
	Frame frame1 = new Frame("<b>串口设置</b>");
	((Gtk.Label)frame1.LabelWidget).UseMarkup = true;
	leftBox.PackStart(frame1, true, true, 5);


	VBox vfram = new VBox();
	frame1.Add(vfram);
	//串口设置
	HBox hCom = new HBox();
	var lblCom = new Label { Text = "串口号:" };
	lblCom.UseMarkup = true;
	lblCom.Halign = Align.Start;
	comboCom = new ComboBox();


	comboCom.SetSizeRequest(150, 30);
	hCom.PackStart(lblCom, true, true, 5);
	hCom.PackStart(comboCom, true, true, 5);


	vfram.PackStart(hCom, false, false, 2);
	//波特率
	HBox hRate = new HBox();
	var lblRate = new Label { Text = "波特率:" };
	lblRate.Halign = Align.Start;
	comboRate = new ComboBox();
	comboRate.SetSizeRequest(150, 30);
	hRate.PackStart(lblRate, true, true, 5);
	hRate.PackStart(comboRate, true, true, 5);


	vfram.PackStart(hRate, false, false, 2);




	//停止位
	HBox hStop = new HBox();
	var lblStop = new Label { Text = "停止位:" };
	lblStop.Halign = Align.Start;
	comboStop = new ComboBox();
	comboStop.SetSizeRequest(150, 30);
	hStop.PackStart(lblStop, true, true, 5);
	hStop.PackStart(comboStop, true, true, 5);


	vfram.PackStart(hStop, false, false, 2);




	//数据位
	HBox hData = new HBox();
	var lblData = new Label { Text = "数据位:" };
	lblData.Halign = Align.Start;
	comboData = new ComboBox();
	comboData.SetSizeRequest(150, 30);
	hData.PackStart(lblData, true, true, 5);
	hData.PackStart(comboData, true, true, 5);


	vfram.PackStart(hData, false, false, 2);




	//奇偶校验
	HBox hJo = new HBox();
	var lblJo = new Label { Text = "校验位:" };
	lblJo.Halign = Align.Start;
	comboJo = new ComboBox();
	comboJo.SetSizeRequest(150, 30);
	hJo.PackStart(lblJo, true, true, 5);
	hJo.PackStart(comboJo, true, true, 5);


	vfram.PackStart(hJo, false, false, 2);


	//打开/关闭串口
	HBox co = new HBox();
	btnSp = new Button { Label = "打开串口" };
	btnSp.Name = "btnSp";
	btnSp.AlwaysShowImage = true;
	btnSp.Image = Image.NewFromIconName("window-new", IconSize.Button);
	btnSp.Clicked += BtnSp_Clicked;
	btnSp.SetSizeRequest(110, 30);
	co.PackStart(btnSp, true, true, 5);


	vfram.PackStart(co, false, false, 2);
	//开关表示图
	HBox imgSwitch = new HBox();
	img = new Gdk.Pixbuf("2.png",30,30);
	imgRender =new Image(img);
	imgRender.Margin = 5;
	imgSwitch.PackStart(imgRender, true, true, 5);


	vfram.PackStart(imgSwitch, false, false, 2);
	#endregion


	#region 接受设置
	Frame frame2 = new Frame("<b>接收设置</b>");
	((Gtk.Label)frame2.LabelWidget).UseMarkup = true;
	leftBox.PackStart(frame2, true, true, 5);


	VBox f2vbox=new VBox();
	f2vbox.Margin = 5;


	btnClear = new Button { Label = "清空接收区" };
	btnClear.Clicked += BtnClear_Clicked;
	f2vbox.PackStart(btnClear, false, false, 5);


	ck16 = new CheckButton { Label = "16进制显示" };
	f2vbox.PackStart(ck16, false, false, 5);
	ck16.Toggled += new EventHandler(ck16Toggled);


	ckRtx = new CheckButton { Label = "RTX设置" };
	f2vbox.PackStart(ckRtx, false, false, 5);
	ckRtx.Toggled += new EventHandler(ckRtxToggled);




	ckDtr = new CheckButton { Label = "DTR设置" };
	f2vbox.PackStart(ckDtr, false, false, 5);
	ckDtr.Toggled += new EventHandler(ckDtrToggled);


	frame2.Add(f2vbox);
	#endregion


	#region 发送设置
	Frame frame3 = new Frame("<b>发送设置</b>");
	((Gtk.Label)frame3.LabelWidget).UseMarkup = true;
	leftBox.PackStart(frame3, true, true, 5);


	VBox f3vbox = new VBox();
	f3vbox.Margin = 5;


	ckS16 = new CheckButton { Label = "16进制发送" };
	f3vbox.PackStart(ckS16, false, false, 5);            


	ckNewline = new CheckButton { Label = "发送新行" };
	f3vbox.PackStart(ckNewline, false, false, 5);


	HBox timeBox = new HBox();
	ckTime = new CheckButton { Label = "定时发送" };
	timeBox.PackStart(ckTime, false, false,0);
	ckTime.Toggled += new EventHandler(ckTimeToggled);


	txtTime =new Entry();
	txtTime.SetSizeRequest(100, 20);
	txtTime.Buffer.Text = "1000";
	timeBox.PackStart(txtTime, false, false, 5);


	Label lblTime=new Label { Text="ms"};
	timeBox.PackStart(lblTime, false, false, 5);
	f3vbox.PackStart(timeBox, false, false, 5);


	frame3.Add(f3vbox);


	#endregion


	#region 数据接收区            
	Frame frame4 = new Frame("<b>数据显示</b>");
	((Gtk.Label)frame4.LabelWidget).UseMarkup = true;
	rightBox.PackStart(frame4, true, true, 5);
	var scrollResult = new ScrolledWindow();
	scrollResult.SetSizeRequest(360, 500);
	//数据接收区
	textResult =new TextView();
	textResult.Name = "textResult";
	textResult.Margin = 2;
	textResult.Editable = false;
	textResult.WrapMode = WrapMode.WordChar;    
	textResult.SizeAllocated += TextView_SizeAllocated;
	scrollResult.Child = textResult;
	frame4.Add(scrollResult);
	#endregion


	#region 数据发送区
	Frame frame5 = new Frame("<b>数据发送</b>");
	((Gtk.Label)frame5.LabelWidget).UseMarkup = true;
	rightBox.PackStart(frame5, true, true, 5);


	var scrollSend = new ScrolledWindow();
	scrollSend.SetSizeRequest(360, 100);
	textSend = new TextView();
	textSend.Margin = 2;
	textSend.WrapMode = WrapMode.WordChar;
	scrollSend.Child = textSend;
	frame5.Add(scrollSend);


	HBox hSendOpt=new HBox();
	hSendOpt.Margin = 2;
	btnSend = new Button { Label = "发送数据" };
	btnSend.Clicked += BtnSend_Clicked;
	btnSendClear = new Button { Label = "清除发送" };
	btnSendClear.Clicked += BtnSendClear_Clicked;
	btnSend.SetSizeRequest(100, 30);
	btnSendClear.SetSizeRequest(100, 30);
	hSendOpt.PackStart(btnSend, false, false, 5);
	hSendOpt.PackStart(btnSendClear, false, false, 5);
	rightBox.PackStart(hSendOpt, false, false, 5);


	#endregion            
}
#endregion


#region 加载上次配置
private void InitHistoryConfig()
{
	var config = LoadCache();
	FillCombo(config,comboCom, "com", SerialPort.GetPortNames());
	FillCombo(config, comboRate, "rate", Utils.baudRateList);
	FillCombo(config, comboStop, "stop", Utils.stopBitList);
	FillCombo(config, comboData, "data", Utils.dataBitList);
	FillCombo(config, comboJo, "pty", Utils.parityList);
}
private void InitCache(ComConfig config)
{
	try
	{
		if (File.Exists(cacheFile)) File.Delete(cacheFile);
		var cacheString = JsonSerializer.Serialize(config, options);
		byte[] bs = Encoding.Default.GetBytes(cacheString);
		using (Stream fileStream = new FileStream(cacheFile, FileMode.OpenOrCreate))
		{
			fileStream.Write(bs, 0, bs.Length);
		}
	}
	catch (Exception)
	{


	}
}
private ComConfig LoadCache()
{
	ComConfig config = null;
	if (File.Exists(cacheFile))
	{
		try
		{
			using (Stream fileStream = new FileStream(cacheFile, FileMode.Open))
			{
				var bs = new byte[fileStream.Length];
				fileStream.Read(bs, 0, bs.Length);
				var tmp = Encoding.Default.GetString(bs);
				config = JsonSerializer.Deserialize<ComConfig>(tmp);
			}
		}
		catch (Exception)
		{


		}
	}
	return config;
}
private void FillCombo(ComConfig config, Gtk.ComboBox cb, string type, string[] ctextList)
{
	cb.Clear();
	CellRendererText cell = new CellRendererText();
	cb.PackStart(cell, false);
	cb.AddAttribute(cell, "text", 0);
	ListStore store = new ListStore(typeof(string));
	cb.Model = store;


	int index = 0;
	for (int i = 0; i < ctextList.Length; i++)
	{
		store.AppendValues(ctextList[i]);
		if (config != null && type == "com" && ctextList[i] == config.PortName)
		{
			index = i;
		}
		if (config != null && type == "rate" && ctextList[i] == config.BaudRate)
		{
			index = i;
		}
		if (config != null && type == "data" && ctextList[i] == config.DataBit)
		{
			index = i;
		}
		if (config != null && type == "stop" && ctextList[i] == config.StopBit)
		{
			index = i;
		}
		if (config != null && type == "pty" && ctextList[i] == config.Parity)
		{
			index = i;
		}
	}
	cb.Active = index;
}


#endregion

收发事件

#region 事件处理
private void MainWindow_DeleteEvent(object o, DeleteEventArgs args)
{
	Application.Quit();
}
private void TextView_SizeAllocated(object o, SizeAllocatedArgs args)
{
	textResult.ScrollToIter(textResult.Buffer.EndIter, 0, false, 0, 0);
}
private void ckTimeToggled(object? sender, EventArgs e)
{
	if (ckTime.Active)
	{
		if (mSerialPort == null || !mSerialPort.IsOpen)
		{
			MessageBox.Show("尚未打开串口,请打开后再发送", "提示",parent:this);
			ckTime.Active = false;
			return;
		}
		if (!uint.TryParse(txtTime.Buffer.Text, out uint sp))
		{
			MessageBox.Show("周期格式不正确", "错误", parent: this);
			ckTime.Active = false;
			return;
		}
		txtTime.SetProperty("editable",new GLib.Value(false));
		timerSendId = GLib.Timeout.Add(sp, () =>
		{
			sendData();
			return true;
		});
	}
	else
	{
		if (timerSendId == 0)
			return;
		txtTime.SetProperty("editable", new GLib.Value(true));
		GLib.Timeout.Remove(timerSendId);
	}
}
private void BtnSendClear_Clicked(object? sender, EventArgs e)
{
	textSend.Buffer.Text = "";
	sendByteCount = 0;
	UpdateStatus();
}
private void BtnSend_Clicked(object? sender, EventArgs e)
{
	sendData();
}
private void sendData()
{
	if (mSerialPort != null && mSerialPort.IsOpen)
	{
		if ((ckNewline.Active) && (ckS16.Active))
		{
			//发送16进制新行  
			byte[] bytes = Utils.convertHexStringToBytes(textSend.Buffer.Text);
			if (bytes != null)
			{
				mSerialPort.Write(bytes, 0, bytes.Length);
				byte[] newLine = { 0xd, 0xa };
				mSerialPort.Write(newLine, 0, newLine.Length);
				sendByteCount += (bytes.Length + newLine.Length);
			}
		}
		else if ((ckNewline.Active) && (!ckS16.Active))
		{
			//发送普通字符串新行
			mSerialPort.Write(textSend.Buffer.Text + "\r\n");
			sendByteCount += (textSend.Buffer.Text.Length + 2);
		}
		else if ((!ckNewline.Active) && (!ckS16.Active))
		{
			//发送普通字符串
			mSerialPort.Write(textSend.Buffer.Text);
			sendByteCount += textSend.Buffer.Text.Length;
		}
		else if ((!ckNewline.Active) && (ckS16.Active))
		{
			//发送16进制
			byte[] bytes = Utils.convertHexStringToBytes(textSend.Buffer.Text);
			if (bytes != null)
			{
				mSerialPort.Write(bytes, 0, bytes.Length);
				sendByteCount += bytes.Length;
			}
		}
		else
		{
			MessageBox.Show("不支持的操作!","错误", parent: this);
		}
		UpdateStatus();
	}
	else
	{
		MessageBox.Show("尚未打开串口,请打开后再发送","提示", parent: this);
	}
}
private void BtnClear_Clicked(object? sender, EventArgs e)
{
	this.textResult.Buffer.Clear();
	recevieByteCount = 0;
	UpdateStatus();
}
private void BtnSp_Clicked(object? sender, EventArgs e)
{
	if (mSerialPort != null)
	{
		if (mSerialPort.IsOpen)
		{
			try
			{
				mSerialPort.Close();
				btnSp.Label = "打开串口";
				//定时发送取消勾选
				ckTime.Active = false;
				//更改按钮样式
				btnSp.StyleContext.RemoveClass("btnRed");
				btnSp.Image = Image.NewFromIconName("window-new", IconSize.Button);
				img = new Gdk.Pixbuf("2.png", 30, 30);
				this.imgRender.Pixbuf = img;
				//改为可用状态
				ChangeComConfigStatus(true);
			}
			catch (Exception)
			{
				MessageBox.Show("串口关闭失败", "错误", parent: this);
			}
		}
		else
		{
			OpenSerialPort();
		}
	}
	else
	{
		OpenSerialPort();
	}
	UpdateStatus();
}
private void OpenSerialPort()
{
	if (comboCom.Active == -1)
	{
		MessageBox.Show("请先选择串口", "提示", parent: this); return;
	}
	if (comboRate.Active == -1)
	{
		MessageBox.Show("请设置波特率", "提示", parent: this); return;
	}
	if (comboData.Active == -1)
	{
		MessageBox.Show("请设置数据位", "提示", parent: this); return;
	}
	if (comboJo.Active == -1)
	{
		MessageBox.Show("请设置奇偶校验位", "提示", parent: this); return;
	}
	if (comboStop.Active == -1)
	{
		MessageBox.Show("请设置停止位", "提示", parent: this); return;
	}
	string com = GetComboxText(comboCom);
	int rate = int.Parse(GetComboxText(comboRate));
	int dataBits = int.Parse(GetComboxText(comboData));
	Parity parity = (Parity)Enum.Parse(typeof(Parity), GetComboxText(comboJo));
	StopBits stopBits = (StopBits)Enum.Parse(typeof(StopBits), GetComboxText(comboStop), true);
	var config = new ComConfig
	{
		PortName = com,
		BaudRate = GetComboxText(comboRate),
		DataBit = GetComboxText(comboData),
		Parity = GetComboxText(comboJo),
		StopBit = GetComboxText(comboStop)
	};
	InitCache(config);
	if (stopBits != StopBits.None)
	{
		mSerialPort = new SerialPort(com, rate, parity, dataBits, stopBits);
	}
	else
	{
		mSerialPort = new SerialPort(com, rate, parity, dataBits);
	}
	if (!mSerialPort.IsOpen)
	{
		try
		{
			mSerialPort.Open();
			btnSp.Label = "关闭串口";
			//更改按钮样式
			btnSp.StyleContext.AddClass("btnRed");
			btnSp.Image = Image.NewFromIconName("window-close", IconSize.Button);
			img = new Gdk.Pixbuf("1.png", 30, 30);
			this.imgRender.Pixbuf = img;
			ChangeComConfigStatus(false);
		}
		catch (Exception ex)
		{
			MessageBox.Show(#34;打开串口失败:{ex.ToString()}", "提示", parent: this);
			return;
		}
	}
	mSerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
private void ChangeComConfigStatus(bool state)
{
	comboCom.Sensitive = state;
	comboData.Sensitive = state;
	comboJo.Sensitive = state;
	comboRate.Sensitive = state;
	comboStop.Sensitive = state;
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
	try
	{
		SerialPort sp = (SerialPort)sender;
		string indata = sp.ReadExisting();
		receiveThread = new Thread(new ParameterizedThreadStart(displayReceiveData));
		receiveThread.Start(indata);
	}
	catch
	{
	}
}
private void displayReceiveData(object? obj)
{
	try
	{
		if (ck16.Active)
		{
			AppendText(textResult, Utils.convertToHexString((string)obj));
			recevieByteCount = (textResult.Buffer.Text.Length + 1) / 3;
		}
		else
		{
			AppendText(textResult, obj.ToString());
			recevieByteCount = textResult.Buffer.Text.Length;
		}
		UpdateStatus();
	}
	catch {
		throw; 
	}
}
#endregion
#region 接收设置
private void ckDtrToggled(object? sender, EventArgs e)
{
	if (mSerialPort != null)
	{
		if (ckDtr.Active)
		{
			mSerialPort.DtrEnable = true;
		}
		else
		{
			mSerialPort.DtrEnable = false;
		}
	}
}
private void ckRtxToggled(object? sender, EventArgs e)
{
	if (mSerialPort != null)
	{
		if (ckRtx.Active)
		{
			mSerialPort.RtsEnable = true;
		}
		else
		{
			mSerialPort.RtsEnable = false;
		}
	}
}
private void ck16Toggled(object? sender, EventArgs e)
{
	if (ck16.Active)
	{
		textResult.Buffer.Text = Utils.convertToHexString(textResult.Buffer.Text);
	}
	else
	{
		textResult.Buffer.Text = Utils.convertHexStringToCommonString(textResult.Buffer.Text);
	}
}
#endregion
#region 辅助方法
private string GetComboxText(ComboBox comboBox)
{
	TreeIter tree;
	comboBox.GetActiveIter(out tree);
	String selectedText = (String)comboBox.Model.GetValue(tree, 0);
	return selectedText;
}
private void AppendText(TextView textView, string text)
{
	var buffer = textView.Buffer;
	TextIter insertIter = buffer.EndIter;
	buffer.Insert(ref insertIter, text);
}
#endregion

上位机李工公众号

一起学习编程,助力每一个梦想!

点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21