Click here to Skip to main content
15,881,687 members
Articles / Programming Languages / C#
Article

RS232 using thread-safe calls to Windows Forms controls

Rate me:
Please Sign up or sign in to vote.
4.58/5 (28 votes)
23 Jan 20072 min read 211.6K   7.8K   149   19
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


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 3 Pin
kvicky28-Nov-10 20:41
kvicky28-Nov-10 20:41 
GeneralMy Vote of 5 Pin
bigbro_19853-Nov-10 1:50
professionalbigbro_19853-Nov-10 1:50 
GeneralMy vote of 5 Pin
Member 372944630-Oct-10 21:53
Member 372944630-Oct-10 21:53 
Is working. god job.
GeneralGood article Pin
Donsw2-Dec-08 8:18
Donsw2-Dec-08 8:18 
General“Access to the port 'COM1' is denied” Pin
techmind78610-Aug-08 23:47
techmind78610-Aug-08 23:47 
GeneralRe: “Access to the port 'COM1' is denied” Pin
Ninou9118-May-09 5:14
Ninou9118-May-09 5:14 
Generalfailed receiving data Pin
kabola29-Mar-08 4:36
kabola29-Mar-08 4:36 
GeneralRe: failed receiving data Pin
j.v.d1-Jul-08 2:09
j.v.d1-Jul-08 2:09 
QuestionHow to update form easliy after received data ? Pin
lanshenghai26-Nov-07 7:11
lanshenghai26-Nov-07 7:11 
AnswerRe: How to update form easliy after received data ? Pin
j.v.d1-Jul-08 2:00
j.v.d1-Jul-08 2:00 
Generalrs232 Pin
Rcbuck21-Nov-07 1:06
Rcbuck21-Nov-07 1:06 
GeneralSerial .NET issue or not an issue please comment Pin
sanong1-Jul-07 17:19
sanong1-Jul-07 17:19 
GeneralClosing Form Pin
bbivans29-Apr-07 5:28
bbivans29-Apr-07 5:28 
Generalwrong data Pin
krepos30-Jan-07 3:24
krepos30-Jan-07 3:24 
GeneralSpecific case of a general problem Pin
mayafit24-Jan-07 9:18
mayafit24-Jan-07 9:18 
GeneralSame result using Invoke..delegate{} Pin
Cees Meijer22-Jan-07 22:01
Cees Meijer22-Jan-07 22:01 
Generaldoes WPF windows (Forms) has the same problem Pin
elwolv22-Jan-07 11:26
elwolv22-Jan-07 11:26 
QuestionConversion to VB.net? Pin
mmansf21-Jan-07 2:44
mmansf21-Jan-07 2:44 
AnswerRe: Conversion to VB.net? Pin
j.v.d22-Jan-07 2:52
j.v.d22-Jan-07 2:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.