How to display data received from serial port in a

2019-09-03 08:55发布

问题:

So, I'm trying to develop a simple application in visual C# which gets data from serial port and displays it in a textbox (to monitor temperature). I'm acquiring and displaying the data successfully, using the DataReceived event to update a global string variable and a timer to update the text field on my text box, as shown:

    private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
    {
        try
        {
            globalVar.updateTemp = port.ReadLine(); //This is my global string
        }
        catch (IOException)
        {
        }
        catch (InvalidOperationException)
        {
        }
        catch (TimeoutException)
        {
        }
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        tempDisplayBox.Text = globalVar.updateTemp; //This is my textbox updating
    }

The only issue I have is that the value shown in the textbox keeps flashing, making it hard to read. My timer is set to trigger every 10 ms (which should be fast enough, right?). Is there any way to make it more stable? I realize this may be a newb question, but to be fair I am a newb :) Any help is appreciated! Thanks!

回答1:

Do you really need it updating every 10ms? What about every 500 ms or if not that then 100ms. 100ms will require your update method run 10 times less and therefore update 10 times less. The flickering you are expiriencing is due to the refresh speed. You could create custom method which will only update the temp only when target Label or textBox value is different than source port. But that will only sort the flickering when temp is steady, when temp will start vary it will bring back the flickering. Good luck ;-)

UPDATE

Hi I tried to reproduce the conditions and could not make my textbox nor Label flash. The way I tested it was by assigning int ntick = 0; and then increment the ++ntick; inside of the timer_tick method. The results didn't make any of the controls flash and were updated even every milisecond at some point. I also tried string.Format to put some load on the method. Is your app responsive?



回答2:

The trick is to use double buffering. This way the operating system will redraw the Control off-screen, and only show the control when it is fully redrawn.

I have had the same problem, and solved it by extending the TextBox control like this:

public FastLogBox()
    {
        InitializeComponent();
        _logBoxText = new StringBuilder(150000);

        timer1.Interval = 20;
        timer1.Tick += timer1_Tick;
        timer1.Start();
        SetStyle(ControlStyles.DoubleBuffer, true);
    }

    void timer1_Tick(object sender, EventArgs e)
    {
        if (_timeToClear)
        {
            _logBoxText.Clear();
            _timeToClear = false;
        }

        if (_logQueue.Count <= 0) return;

        while (!_logQueue.IsEmpty)
        {
            string element;
            if (!_logQueue.TryDequeue(out element)) continue;
            {
                _logBoxText.Insert(0, element + "\r\n");
            }
        }
        if (_logBoxText.Length > 150000)
        {
            _logBoxText.Remove(150000, _logBoxText.Length - 150001);
        }
        Text = _logBoxText.ToString();
    }
    public new void Clear()
    {
        _timeToClear = true;
        while (!_logQueue.IsEmpty)
        {
            string element;
            _logQueue.TryDequeue(out element);
        }
    }
    public void AddToQueue(string message)
    {
        _logQueue.Enqueue(message);
    }
}

I also use a timer and a concurrentQueue to avoid using Invoke to update the control from another thread. I also use a StringBuilder to prepare the string before putting it into the TextBox. StringBuilder is faster when building larger strings.



回答3:

You can use ReadExisting() to read the whole data at a time. You need to handle DataReceived Event of SerialPort

serialPort1.ReadExisting();

Sample:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
           String myData=serialPort1.ReadExisting();
        }

Example Code: Here i would like to show you the code to Read Data(RFID Tag Code which is basically of length 12)

        String macid  = "";
        private void DoWork()
        {
            Invoke(
                  new SetTextDeleg(machineExe ),
                  new object[] { macid  });
            macid  = "";
        }
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {

            string str1;
            macid += serialPort1.ReadExisting();

            if (macid.Length == 12)
            {
                macid = macid.Substring(0, 10);
                Thread t = new Thread(new ThreadStart(DoWork));
                t.Start();
            }
        }

         public void machineExe(string text)
          {
            TextBox1.Text=text;
          }


回答4:

Thank you so much for the answers! I found a way to work around this issue: Instead of replacing the contents of my textbox by rewriting the TextBox.Text property - which, as HenningNT implied, refreshes the control and causes the flickering - I'm now using the TextBox.AppendText method. Though, as I want to display only one line of data at a time, I use the textbox in multiline mode and the Environment.NewLine to jump to a new line before appending the text. As for the method of updating, I've gone back to using the timer because with the invoke method was crashing my application when I close the form, for some reason. Also, enabling double buffering didn't do me much good, although I guess I was doing it wrong... It still flickers a bit, but it's much better now :) I know this is not really a perfect solution (much more of a workaround), so I'll keep looking for it. If I find it, I'll be sure to update it here ;) My code:

    private void timer1_Tick(object sender, EventArgs e) //Timer to update textbox
    {
        if (tempDisplayBox.Text != globalVar.updateTemp) //Only update if temperature is different
        {
            try
            {
                tempDisplayBox.AppendText(Environment.NewLine);
                tempDisplayBox.AppendText(globalVar.updateTemp);
            }
            catch (NullReferenceException)
            { 
            }
        }
    }