0

I have a problem similar to the topic "Display serial in to textbox - Cross thread operation" but I don't understand the answers given for that.

I created a very simple C# form to send/receive serial data, based on a tutorial video example I found. Works great, but the example requires you to click a button to receive data, whereas I want to have it now automatically display whatever is received by updating the "Received" textbox.

image of Form1

Program.cs is generated by VSE2015:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace spcontrol
{
  static class Program
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new Form1());
    }
  }
}

Form1.cs shows working code based on the tutorial example (i.e. "Receive" button must be clicked):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;

namespace spcontrol
{
  public partial class Form1 : Form
  {

    public Form1()
    {
      InitializeComponent();
      GetAvailablePorts();
      //serialPort1.DataReceived += new SerialDataReceivedEventHandler(port_OnReceiveData);
    }

    void GetAvailablePorts()
    {
      string[] ports = SerialPort.GetPortNames();
      comboBox_portnames.Items.AddRange(ports);
    }

    private void button_openport_Click(object sender, EventArgs e)
    {
      try
      {
        if (comboBox_portnames.Text == "" || comboBox_baudrate.Text == "")
        {
          textBox_received.Text = "Please select port settings.";
        }
        else
        {
          textBox_received.Text = "";
          serialPort1.PortName = comboBox_portnames.Text;
          serialPort1.BaudRate = Convert.ToInt32(comboBox_baudrate.Text);
          serialPort1.Open();
          button_send.Enabled = true;
          button_receive.Enabled = true;
          textBox_sent.Enabled = true;
          button_openport.Enabled = false;
          button_closeport.Enabled = true;
          comboBox_portnames.Enabled = false;
          comboBox_baudrate.Enabled = false;
          serialPort1.DataBits = 8;
          serialPort1.Parity = Parity.None;
          serialPort1.StopBits = StopBits.One;
          serialPort1.Handshake = Handshake.None;
          label_config.Text = serialPort1.PortName + " " + serialPort1.BaudRate + " 8N1 None";
          progressBar_status.Value = progressBar_status.Maximum;
        }
      }
      catch (UnauthorizedAccessException)
      {
        textBox_received.Text = "Unauthorized access.";
      }
    }

    private void button_closeport_Click(object sender, EventArgs e)
    {
      serialPort1.Close();
      button_send.Enabled = false;
      button_receive.Enabled = false;
      textBox_sent.Enabled = false;
      button_openport.Enabled = true;
      button_closeport.Enabled = false;
      comboBox_portnames.Enabled = true;
      comboBox_baudrate.Enabled = true;
      progressBar_status.Value = progressBar_status.Minimum;
    }

    private void button_send_Click(object sender, EventArgs e)
    {
      serialPort1.WriteLine(textBox_sent.Text);
      textBox_sent.Text = "";
      textBox_sent.Focus();
    }

    private void button_receive_Click(object sender, EventArgs e)
    {
      try
      {
        textBox_received.Text = serialPort1.ReadLine();
      }
      catch (TimeoutException)
      {
        textBox_received.Text = "Timeout exception.";
      }
    }

    //private void port_OnReceiveData(object sender, SerialDataReceivedEventArgs e)
    //{
    //  SerialPort sp = (SerialPort)sender;
    //  textBox_received.Text += sp.ReadExisting();
    //}

  }
}

The commented-out code is my attempt at automating the received data textbox display (uncomment this code and comment out the "try" statement within "button_receive_Click" function.)

But doing so gives me the cross-thread error:

Exception thrown: 'System.InvalidOperationException' in System.Windows.Forms.dll An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll Additional information: Cross-thread operation not valid: Control 'textBox_received' accessed from a thread other than the thread it was created on.

I've read things about using Invoke or BeginInvoke but there's never enough context in the answers for me (a non-programmer) to figure out how to implement, plus usually the answers are all over the map and there's no consensus for which is correct/best. Can someone please provide a simple solution for what I need to add to my code to make this work? Thanks.

jpc2112
  • 23
  • 1
  • 5

2 Answers2

0
private void port_OnReceiveData(object sender, SerialDataReceivedEventArgs e) {
    SerialPort sp = (SerialPort)sender;
    UpdateTextBox (sp.ReadExisting());
}

public void UpdateTextBox(string value) {
    if (InvokeRequired)
    {
        this.Invoke(new Action<string>(UpdateTextBox), new object[] {value});
        return;
    }
    textBox_received.Text += value;
}
Ihdina
  • 950
  • 6
  • 21
0

To understand the problem, UI form has own thread, UI specific thread and to make things safe and easy, everything in the UI is handled in message loop of its thread. Events like event from serial port or other communication channels are scheduled usually on thread pool or specific threads outside UI thread. What you need is to cross boundary between other threads and UI thread. On windows forms there are methods of Form instances, Invoke and BeginInvoke, they will schedule some work to UI message loop and the UI thread in own time will execute it. Invoke synchronously waits until the execution is completed, BeginInvoke does not, unless you play with returning values. Take my answer as intro to big and vast world of multi-thread synchronization, communication, interaction. Good luck.

ipavlu
  • 1,617
  • 14
  • 24
  • ipavlu, I appreciate the explanation. And the code shared by lhdina solved my issue. I am not doing any other processing in this UI so I suppose the Invoke method is adequate for me. – jpc2112 Jul 25 '17 at 20:18