Actualización de GUI C#y comunicación de puerto serie asíncrono
winforms serial-port (1)
Debe usar una máquina de estado y delegates para lograr lo que está tratando de hacer. Vea el código a continuación, le recomiendo hacer todo esto en un hilo separado que no sea Main. Realiza un seguimiento del estado en el que se encuentra y, cuando obtiene una respuesta, la analiza con la función de devolución de llamada correcta y, si es lo que espera, pasa al siguiente estado de comando de envío.
private delegate void CallbackFunction(String Response); //our generic Delegate
private CallbackFunction CallbackResponse; //instantiate our delegate
private StateMachine currentState = ATRHBPCalStateMachine.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("/n", "");
foreach (char c in Data)
{
if (c == ''/r'' || 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
esto en un trabajador en segundo plano, y cuando presiona un botón o algo, puede establecer el estado actual en
SendCmd1
.
Presione el botón
private void buttonStart_Click(object sender, EventArgs e)
{
if(!backgroundWorker1.IsBusy)
{
currentState = StateMachine.SendCmd1;
backgroundWorker1.RunWorkerAsync();
}
}
Trabajador de fondo hacer evento de trabajo
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (backgroundWorker1.CancellationPending)
break;
do_State_Machine();
Thread.Sleep(100);
}
}
editar: puede usar invoke para actualizar la GUI desde su hilo de trabajo en segundo plano.
this.Invoke((MethodInvoker)delegate
{
image = Image.FromFile(path);
//do some stuff on image using Graphics, adding texts etc.
picturebox1.Image = image;
});
Estoy tratando de crear una aplicación que se comunique con el hardware a través del puerto serie e informe los resultados a la interfaz gráfica de usuario.
Actualmente, KeyEvents realiza el movimiento a través de la GUI que activa el dibujo de la próxima "página" de la GUI. Sin embargo, en un paso (después de presionar la tecla) necesito dibujar una nueva página y enviar algunos comandos a través del puerto serie.
El envío del comando se realiza a través de:
port.Write(data, 0, data.Length);
Luego espero la respuesta esperando que se active DataReceivedHandler: solo señala que hay datos en espera y los datos se están procesando en otro método.
Al principio, simplemente puse el comando de envío y recepción en la función de dibujo de la página después de las "partes de dibujo", sin embargo, se quedó atascado: los datos se estaban transfiriendo, pero la página no se dibujó, estaba congelada.
Luego hice un método asíncrono:
private async void SendData()
{
await Task.Run(() => serialClass.SendAndReceive(command));
// process reply etc.
}
Que se usa así:
public void LoadPage()
{
image = Image.FromFile(path);
//do some stuff on image using Graphics, adding texts etc.
picturebox1.Image = image;
SendData();
}
Funciona bien, sin embargo, necesito "volver a cargar" la página (para volver a llamar a LoadPage). Si lo hago dentro del método asíncrono como este:
private async void SendData()
{
await Task.Run(() => serialClass.SendAndReceive(command));
// process reply etc.
LoadPage();
}
Entonces, obviamente, la imagen no se actualizará, aunque los datos se enviarán a través del puerto serie. ¿Es posible verificar de alguna manera si la función asincrónica se terminó y desencadenar un evento en el que pueda volver a cargar la página?
Hasta ahora he intentado usar BackGroundWorker Work Complete y Property Change. Los datos se enviaron nuevamente, pero la imagen no se volvió a cargar. ¿Alguna idea de cómo puedo lograr eso?
Gracias de antemano por la ayuda, Saludos cordiales