C# GUI 刷新与异步串口通信 [英] C# GUI refresh and async serial port communication

查看:42
本文介绍了C# GUI 刷新与异步串口通信的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个应用程序,该应用程序通过串行端口与硬件通信并将结果报告给 gui.

I'm trying to create an application which communicates with hardware via serial port and reports the results to the gui.

当前在 GUI 中的移动是由 KeyEvents 完成的,它触发 GUI 的下一个页面"的绘制.但是在一个步骤中(按下键后)我需要绘制新页面并通过串口发送一些命令.

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 工作完成和属性更改.数据再次发送,但图像没有重新加载.知道我如何实现这一目标吗?

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);
    }
}

您可以使用 invoke 从后台工作线程更新 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 刷新与异步串口通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆