none
How can a thread update a textbox in the windows form that is part of the main thread

    Question

  • I have created a windows forms application in C# that gets traffic from several com ports. I have created separate threads for each com port since they need to get com traffic in the background. This works great but I can't figure out an elegant way to update the controls on the form which are part of the main thread when I get new com traffic on one of the background stings. I have looked at synchronization techniques like locking, monitor class, mutex class, readerwriterlock, semaphore, signaling, wait handles, interlocked operations, etc. but I can't seem to find a simple and elegant way to do this. Surely there must be a way for a separate string to update a textbox in another string?

    Chuck

    Friday, November 07, 2008 4:51 PM

Answers

  • I would recommend adding a method to the form to update each control.  In there you can use Control.Invoke.  For example:

            public void SetTextBox(String text)  
            {  
                if(InvokeRequired)  
                {  
                    this.Invoke((MethodInvoker)delegate() { SetTextBox(text); });  
                    return;  
                }  
                textBox1.Text = text;  
            }  
     

    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 4:59 PM
    Moderator
  • You need an instance of the form somewhere.  You could change your thread to be parameterized.  For example:
     
    public static void ReadThread1(Object parameter)  
    {  
       Form1 form = parameter as Form1;  
       if(form == null) return;  
       while(true) {  
          form.SetTextBox(SerialPort2.ReadLine);  
          //...  
       }  
    Then start the thread like this:
    readThread.Start(this);  
     


    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 5:57 PM
    Moderator
  • Yes, Invoke uses a FIFO message queue to process calls to Invoke with only one being processed at a time.
    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 8:10 PM
    Moderator

All replies

  • I would recommend adding a method to the form to update each control.  In there you can use Control.Invoke.  For example:

            public void SetTextBox(String text)  
            {  
                if(InvokeRequired)  
                {  
                    this.Invoke((MethodInvoker)delegate() { SetTextBox(text); });  
                    return;  
                }  
                textBox1.Text = text;  
            }  
     

    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 4:59 PM
    Moderator
  • Peter,

    I tried that but the com thread can't see the method which is in the Form thread:

    public partial class Form1 : Form
        {
            static SerialPort SerialPort1 = new SerialPort();
            static SerialPort SerialPort2 = new SerialPort();
            Thread readThread1 = new Thread(ReadThread1);
            Thread readThread2 = new Thread(ReadThread2);
            Thread mainThread = new Thread(MainThread);

            private void Form1_Load(object sender, EventArgs e)
            {
                SerialPort1.Open();
                SerialPort2.Open();
                mainThread.Priority = ThreadPriority.Highest;
                mainThread.Start();
                readThread1.Priority = ThreadPriority.Normal;
                readThread1.Start();
                etc...
            }

            public static void ReadThread1()
            {
                while (true)
                {
                    try
                    {
                        string tmp = SerialPort1.ReadLine();
                        SetTextBox(tmp);
                    etc...
                
    }
                }
            }


    This code generates the follow error:

    Error 2 An object reference is required for the non-static field, method, or property 'SerialPortThreadTest.Form1.SetTextBox(string)' C:\Documents and Settings\admin\My Documents\Visual Studio 2008\Projects\SerialPortThreadTest\SerialPortThreadTest\Form1.cs 134 25 SerialPortThreadTest

    Thanks,
    Chuck

    • Edited by chuck33 Friday, November 07, 2008 5:56 PM
    Friday, November 07, 2008 5:33 PM
  • You need an instance of the form somewhere.  You could change your thread to be parameterized.  For example:
     
    public static void ReadThread1(Object parameter)  
    {  
       Form1 form = parameter as Form1;  
       if(form == null) return;  
       while(true) {  
          form.SetTextBox(SerialPort2.ReadLine);  
          //...  
       }  
    Then start the thread like this:
    readThread.Start(this);  
     


    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 5:57 PM
    Moderator
  • Peter,

    Of course, that seems so obvious now that you have pointed it out and it works great!  Much easier than using wait handles and locks to try to synchronize multiple threads, and I assume that the InvokeRequired will keep deadlocks from occurring if multiple threads try to access the SetTextBox() method at the same time. 

    Thanks,
    Chuck
    • Edited by chuck33 Friday, November 07, 2008 6:17 PM
    Friday, November 07, 2008 6:17 PM
  • Yes, Invoke uses a FIFO message queue to process calls to Invoke with only one being processed at a time.
    http://www.peterRitchie.com/blog
    Friday, November 07, 2008 8:10 PM
    Moderator
  • I implemented a similar application that monitors up to 16 COM ports and received data is displayed in a DataGridView as: Timestamp | Source Port | RecvData | ParsedData.
    My serial devices as 2.4GHz radios with a RF sniffing firmware and I'm using them to sniff all 16 RF channels of 802.15.4 networks and parse each packet received.
    The maximum amount of data is 100 packets per second and a packet is max 127 bytes

    How is implemented:

    public class SerialPortLoggerFrm : Form
    {
            private SerialPortLogger serialPortLogger;
            private Thread serialPortLoggerThread;

            // This delegates enables asynchronous calls for setting the text property on a TextBox, RichTextBox and DataGridView controls.
            delegate void SetRTBTextLoggerCallback(RichTextBox target, DateTime timeStamp, string source, Color color, Color bkColor, string msg, Color msgColor, Color msgBkColor);
    ....
            public void SetRTBTextLogger(RichTextBox target, DateTime timeStamp, string source, Color color, Color bkColor, string msg, Color msgColor, Color msgBkColor)
            {
                // InvokeRequired compares the thread ID of the calling thread to the thread ID of the creating thread and returns true if different.
                if (target.InvokeRequired)
                {
                    SetRTBTextLoggerCallback d = new SetRTBTextLoggerCallback(SetRTBTextLogger);
                    //BeginInvoke(d, new object[] { target, source, color, bkColor, msg, msgColor, msgBkColor });
                    Invoke(d, new object[] { target, timeStamp, source, color, bkColor, msg, msgColor, msgBkColor });
                }
                else
                {
                    lock (target)
                    {
                        target.SelectionColor = color;
                        target.SelectionBackColor = bkColor;
                        target.SelectedText = timeStamp.ToString(TIME_FORMAT) + " " + source;
                        target.SelectedText = "";
                        target.SelectionColor = msgColor;
                        target.SelectionBackColor = target.BackColor;
                        target.SelectedText = " " + msg;
                    }
                }
            }
    ........
            private void btnConnectDisconnect_Click(object sender, EventArgs e)
            {
                    ..........
                    serialPortLogger = new SerialPortLogger(serialPortsList);
                    serialPortLogger.SerialLoggerDataReceived += new SerialPortWorkerEventHandler(OnDataReceivedFromSerialPortLogger);
                    ..........
            }
    .........
            public void OnDataReceivedFromSerialPortLogger(object sender, SerialPortWorkerEventArgs e)
            {
                SetRTBTextLogger(rtbConsoleWindowConsole,
                    e.Timestamp,
                    //e.PortName + ":" +
                    ((SerialPortLogger)sender).serialPortsList[((SerialPortLogger)sender).GetIndexByPortName(e.PortName)].label + ": ",
                    ((SerialPortLogger)sender).serialPortsList[((SerialPortLogger)sender).GetIndexByPortName(e.PortName)].forecolor,
                    ((SerialPortLogger)sender).serialPortsList[((SerialPortLogger)sender).GetIndexByPortName(e.PortName)].backcolor,
                    e.Data,
                    rtbConsoleWindowConsole.ForeColor,
                    rtbConsoleWindowConsole.BackColor
                    );
            }
     .....
    }
    class SerialPortLogger
    {
         public SerialPort[] serialPortsList;
    ......
         public SerialPortLogger(SerialPortItem[] aSerialPortsList)
         {
               // initialize my array of SerialPorts: portName, BaudRate, etc. SerialPortItem class contains all the standard settings needed to configure a serial port
               // also for each SerialPort item add:
               // serialPortsList[i].DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(this.OnDataReceived);
               .......
         }
    ......
             // Note: In my project I defined my own SerialPortWorkerEventArgs but the SerialDataReceivedEventArgs will work fine as well
            private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                if (SerialLoggerDataReceived != null)
                {
                    SerialLoggerDataReceived(this, e);
                }
            }
    ......
    }

    I hope this is usefull for who ever writes something similar.
    Btw, since the amount of data I'm receiving is huge, I had to store the data outside of the control object and do a just-in-time data loading to save user's ram memory.

    Sunday, March 29, 2009 3:19 PM