Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

RS232 using thread-safe calls to Windows Forms controls

4.58/5 (28 votes)
23 Jan 20072 min read 1   7.8K  
An article on RS232 using thread-safe calls to Windows Forms controls.

Introduction

As RS-232 is still used often in industrial applications, Microsoft has added the SerialPort class to its .NET 2.0 framework.

When communicating with a Windows Forms application and displaying the received data, for instance in a TextBox, the problem occurs, that the serial port and the textbox are using different threads. MSDN describes a way to solve this problem.

As an example of how to apply all this, using Visual C#, a small application has been built:

First of all, you have to place, from the Toolbox=>Components, a SerialPort onto the Form.

Then, in the Properties, set the serial port to 9600,8N1 and the port name to X (don't be alarmed; after the application has started, you will get the opportunity to choose a proper COM-name!)

Now, place the top-textbox (txtIn), showing received data, and the lower textbox (txtOut), showing the data to be send after clicking the Send button. Optionally, you could also place a SttusStrip (as I did).

Add a ComboBox (cmbComSelect) with DropDownStyle set to DropDown and Sorted set to true.

The Namespace

The System.IO.Ports namespace contains classes for controlling serial ports. The most important class is the SerialPort class.

The Code

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;


namespace RS232
{
    public partial class fclsRS232Tester : Form
    {
        string InputData = String.Empty;
        
        // This delegate enables asynchronous calls for setting
        // the text property on a TextBox control:
        delegate void SetTextCallback(string text);
 
        public fclsRS232Tester()
        {
            InitializeComponent();

            // Nice methods to browse all available ports:
            string[] ports = SerialPort.GetPortNames();

            // Add all port names to the combo box:
            foreach (string port in ports)
            {
                cmbComSelect.Items.Add(port);
            }
        }

        private void cmbComSelect_SelectionChangeCommitted(object sender, 
                                                           EventArgs e)
        {
            if (port.IsOpen) port.Close();
            port.PortName = cmbComSelect.SelectedItem.ToString();
            stsStatus.Text = port.PortName + ": 9600,8N1";
            
            // try to open the selected port:
            try
            {
                port.Open();
            }
            // give a message, if the port is not available:
            catch
            {
                MessageBox.Show("Serial port " + port.PortName + 
                   " cannot be opened!", "RS232 tester", 
                   MessageBoxButtons.OK, MessageBoxIcon.Warning);
                cmbComSelect.SelectedText = "";
                stsStatus.Text = "Select serial port!";
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (port.IsOpen) port.WriteLine(txtOut.Text);
            else MessageBox.Show("Serial port is closed!", 
                                 "RS232 tester", 
                                 MessageBoxButtons.OK, 
                                 MessageBoxIcon.Error);
            txtOut.Clear();
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            txtIn.Clear();
        }

        private void port_DataReceived_1(object sender, 
                SerialDataReceivedEventArgs e)
        {
            InputData = port.ReadExisting();
            if (InputData != String.Empty)
            {
 //             txtIn.Text = InputData;
                // because of different threads this
                // does not work properly !!

                SetText(InputData);
            }
        }

        /*
        To make a thread-safe call a Windows Forms control:

        1.  Query the control's InvokeRequired property.
        2.  If InvokeRequired returns true,  call Invoke with 
            a delegate that makes the actual call to the control.
        3.  If InvokeRequired returns false, call the control directly.

        In the following code example, this logic is 
        implemented in a utility method called SetText. 
        A delegate type named SetTextDelegate encapsulates the SetText method. 
        When the TextBox control's InvokeRequired 
        returns true, the SetText method creates an instance
        of SetTextDelegate and calls the form's Invoke method. 
        This causes the SetText method to be called 
        on the thread that created the TextBox control,
        and in this thread context the Text property is set directly

        also see: http://msdn2.microsoft.com/en-us/library/ms171728(VS.80).aspx
        */

        // This method demonstrates a pattern for making thread-safe
        // calls on a Windows Forms control. 
        //
        // If the calling thread is different from the thread that
        // created the TextBox control, this method creates a
        // SetTextCallback and calls itself asynchronously using the
        // Invoke method.
        //
        // If the calling thread is the same as the thread that created
        // the TextBox control, the Text property is set directly. 
       
        private void SetText(string text)
        {
            // InvokeRequired required compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.txtIn.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else this.txtIn.Text += text;
        }

    }
}

Getting the COM-port

The SerialPort gives us a nice method to find all COM-ports available in the computer:

C#
string[] ports = SerialPort.GetPortNames();

Now that we have all ports available, we fill the Items list of the ComboBox:

C#
foreach (string port in ports)
{
    cmbComSelect.Items.Add(port);
}

and, through the SelectionChangeCommitted event from the ComboBox, we can choose a particular COM-port.

Because the selected COM-port may be in use, we try to open it, and if an exception is thrown, we catch it and display a proper message.

Achieving thread-safety

The statement txtIn.Text = InputData; will cause problems on receiving data, because the Receive thread is different from the thread which started the textbox!! 

To solve this problem in a thread-safe way, we use the following statement instead:

C#
SetText(InputData);

First, we have to declare a delegate method:

C#
delegate void SetTextCallback(string text);

and then this method can be defined as:

C#
private void SetText(string text)
{
    // InvokeRequired required compares the thread ID of the
    // calling thread to the thread ID of the creating thread.
    // If these threads are different, it returns true.
    if (this.txtIn.InvokeRequired)
    {
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text });
    }
    else this.txtIn.Text += text;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here