问题描述
我正在尝试创建一个通过串行端口与硬件通信并将结果报告给 gui 的应用程序.
I'm trying to create an application which communicates with hardware via serial port and reports the results to the gui.
目前在 GUI 中的移动是由触发 GUI 下一个页面"的绘制的 KeyEvents 完成的.但是一步(按下键后)我需要绘制新页面并通过串口发送一些命令.
Currently moving through GUI is made by KeyEvents which trigger the drawing of the next "page" of GUI. However at one step (after the key is pressed) I need to draw new page and send few commands via serial port.
命令发送是通过:
port.Write(data, 0, data.Length);
然后我通过等待触发 DataReceivedHandler 来等待答案 - 它只是指出有数据在等待并且数据正在以另一种方法处理.
I then wait for the answer by waiting for DataReceivedHandler to trigger - it just pins out that there is data awaiting and data is being processed in another method.
起初我只是把发送 &在绘制部件"之后在绘制页面的函数中接收命令,但是它使它卡住了 - 数据正在传输,但页面没有绘制 - 它被冻结了.
At first I just put sending & receiving command in the function drawing the page after the "draw parts" however it made it stuck - the data was being transfered, but the page wasn't drawn - it was frozen.
然后我做了一个异步方法:
Then I made an async method :
private async void SendData()
{
await Task.Run(() => serialClass.SendAndReceive(command));
// process reply etc.
}
这样使用:
public void LoadPage()
{
image = Image.FromFile(path);
//do some stuff on image using Graphics, adding texts etc.
picturebox1.Image = image;
SendData();
}
它工作正常,但是我需要重新加载"页面(再次调用 LoadPage).如果我在这样的异步方法中这样做:
It works fine, however I need to "reload" the page (to call again LoadPage). If I do it inside the async method like this :
private async void SendData()
{
await Task.Run(() => serialClass.SendAndReceive(command));
// process reply etc.
LoadPage();
}
那么显然图像不会被刷新,虽然数据会通过串口发送.是否有可能以某种方式检查异步功能是否完成并触发我可以重新加载页面的事件?
Then obviously the image won't be refreshed, though the data will be send via serial port. Is it possible to somehow check if async function was finished and trigger an event where I could reload the page?
到目前为止,我已经尝试使用 BackGroundWorker Work Complete 和 Property Change.数据再次发送,但图像没有重新加载.知道如何实现吗?
So far I've tried using the BackGroundWorker Work Complete and Property Change. The data was send again, but the image wasn't reloaded. Any idea how I can achieve that?
提前感谢您的帮助,最好的问候
Thanks in advance for the help, Best regards
推荐答案
你需要使用状态机和委托来实现你想要做的事情.请参阅下面的代码,我建议在除 Main 之外的单独线程中执行所有这些操作.您跟踪您所处的状态,当您收到响应时,您使用正确的回调函数对其进行解析,如果它是您所期望的,您将进入下一个发送命令状态.
You need to use a state machine and delegates to achieve what you are trying to do. See the code below, I recommend doing all this in a separate thread other then Main. You keep track of the state you're in, and when you get a response you parse it with the correct callback function and if it is what you are expecting you move onto the next send command state.
private delegate void CallbackFunction(String Response); //our generic Delegate
private CallbackFunction CallbackResponse; //instantiate our delegate
private StateMachine currentState = StateMachine.Waiting;
SerialPort sp; //our serial port
private enum StateMachine
{
Waiting,
SendCmd1,
Cmd1Response,
SendCmd2,
Cmd2Response,
Error
}
private void do_State_Machine()
{
switch (StateMachine)
{
case StateMachine.Waiting:
//do nothing
break;
case StateMachine.SendCmd1:
CallbackResponse = Cmd1Response; //set our delegate to the first response
sp.Write("Send first command1"); //send our command through the serial port
currentState = StateMachine.Cmd1Response; //change to cmd1 response state
break;
case StateMachine.Cmd1Response:
//waiting for a response....you can put a timeout here
break;
case StateMachine.SendCmd2:
CallbackResponse = Cmd2Response; //set our delegate to the second response
sp.Write("Send command2"); //send our command through the serial port
currentState = StateMachine.Cmd2Response; //change to cmd1 response state
break;
case StateMachine.Cmd2Response:
//waiting for a response....you can put a timeout here
break;
case StateMachine.Error:
//error occurred do something
break;
}
}
private void Cmd1Response(string s)
{
//Parse the string, make sure its what you expect
//if it is, then set the next state to run the next command
if(s.contains("expected"))
{
currentState = StateMachine.SendCmd2;
}
else
{
currentState = StateMachine.Error;
}
}
private void Cmd2Response(string s)
{
//Parse the string, make sure its what you expect
//if it is, then set the next state to run the next command
if(s.contains("expected"))
{
currentState = StateMachine.Waiting;
backgroundWorker1.CancelAsync();
}
else
{
currentState = StateMachine.Error;
}
}
//In my case, I build a string builder until I get a carriage return or a colon character. This tells me
//I got all the characters I want for the response. Now we call my delegate which calls the correct response
//function. The datareceived event can fire mid response, so you need someway to know when you have the whole
//message.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string CurrentLine = "";
string Data = serialPortSensor.ReadExisting();
Data.Replace("
", "");
foreach (char c in Data)
{
if (c == '
' || c == ':')
{
sb.Append(c);
CurrentLine = sb.ToString();
sb.Clear();
CallbackResponse(CurrentLine); //calls our correct response function depending on the current delegate assigned
}
else
{
sb.Append(c);
}
}
}
我会把它放在后台工作人员中,当你按下按钮或其他东西时,你可以将当前状态设置为 SendCmd1
.
I would put this in a background worker, and when you press a button or something you can set the current state to SendCmd1
.
按键
private void buttonStart_Click(object sender, EventArgs e)
{
if(!backgroundWorker1.IsBusy)
{
currentState = StateMachine.SendCmd1;
backgroundWorker1.RunWorkerAsync();
}
}
后台工作人员做工作事件
Background worker do work event
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (backgroundWorker1.CancellationPending)
break;
do_State_Machine();
Thread.Sleep(100);
}
}
您可以使用调用从后台工作线程更新 GUI.
edit: you can use invoke to update the GUI from your background worker thread.
this.Invoke((MethodInvoker)delegate
{
image = Image.FromFile(path);
//do some stuff on image using Graphics, adding texts etc.
picturebox1.Image = image;
});
这篇关于C# GUI 刷新和异步串口通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!