0

I have an application where I send an instruction to a micro via the PC serial port by a button click. The micro then streams back the data which fires the data received event handler. This is captured into a string.

At this point I wish to use the string's data and populate my listview box. I can do this using invoke, delegate because I am still in the data received thread.

Is there any way I can call an event handler or simple routine to do this after the thread has exited, so I don't need to use invoke, delegate? Building the routine works ok if it's triggered by a button, but I would like it to be called programmatically to complete the task.

Hope it's clear enough, it's my first post.

Edit: Here is some sample code --

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

    //use 28591 or("ISO-8859-1") to cover all hex bytes from 0 - 255
    serialPort1.Encoding = Encoding.GetEncoding(28591);

    //wait for download to complete by monitoring cts line
    if (HoldData == true)
    {
        while (serialPort1.CtsHolding == true) ;
        HoldData = false;
    }

    else
        Thread.Sleep(50);

    string text = serialPort1.ReadExisting();

    switch (text[0])
    {
        case '?': MemLabelUpdate(); break;
        case '>': WriteConfig(text); break;
        case '=': SealTest(text); break;
        case '<': CurrentNumber(text); break;

        default: DataDownload(text); break;

    }
}

The first byte of string text is an identifier as to what has come in. This in turn calls a function which populates lables on the main form using the invoke delegate method as its running within the data received thread. The default call to the download data function passes text and sorts it out as this is a mass of events. The results are then passed to my listview box into relevant columns. I want to get away from using the invoke delegate method. I need to exit the port_datareceived thread to do this and upon exit, enter my function to just update the list as below. How can i trigger this kind of event programatically.

private void btnDisplayData_Click(object sender, EventArgs e)
{
    int SectionStart = 10;
    int SectionEnd = 8;
    listView1.Items.Clear();
    listView1.View = View.Details;
    listView1.GridLines = true;

    //Add columns to listview
    listView1.Columns.Add("Event", 80, HorizontalAlignment.Center);
    listView1.Columns.Add("Time", 80, HorizontalAlignment.Center);
    listView1.Columns.Add("Date", 80, HorizontalAlignment.Center);

    //Print results to listview box
    ListViewItem ListItem;
    for (int i = 0; i < 10; i++)
    {
        ListItem = listView1.Items.Add(DownloadedData.Substring(SectionStart,     SectionEnd));
        SectionStart += 8;
        ListItem.SubItems.Add(DownloadedData.Substring(SectionStart, SectionEnd));
        SectionStart += 8;
        ListItem.SubItems.Add(DownloadedData.Substring(SectionStart, SectionEnd));
        SectionStart += 8;
    }

    foreach (ColumnHeader column in listView1.Columns)
    {
        column.Width = -2;
    }

}
Ryan
  • 7,835
  • 2
  • 29
  • 36
  • You need to clarify the question: what threads are you using, are you using the C# SerialPort class, on which thread do you create the SerialPort object. – Ryan May 01 '13 at 16:21
  • Show us the relevant code and explain a bit how you want it to work differently. It's hard to tell what you're really asking for here. – Jim Mischel May 01 '13 at 18:39

2 Answers2

0

It looks like the event you're really interested in is PinChange. So handle that, check CTS, and then use BeginInvoke to send one message back to your UI thread, which can empty the serial port buffer (without waiting), parse the data, and update the controls.


As a further note, the DataReceived event is actually useless on the Microsoft-provided System.IO.Ports.SerialPort class. If you're interested in doing something with the data as soon as it arrives, use BeginRead with a callback.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

You still haven't given any detail on the threading issue. Is the SerialPort created on the UI thread, a worker thread, or a BackgroundWorker? The SynchronizationContext is important and if the SerialPort is created on a different thread, then you have to use some kind of mechanism to Invoke or raise the data back to the UI thread to display.

The simplest solution might be just to Invoke the DownloadData method (assuming that method updates the UI or calls another method to update the UI):

        switch (text[0])
        {
            case '?': MemLabelUpdate(); break;
            case '>': WriteConfig(text); break;
            case '=': SealTest(text); break;
            case '<': CurrentNumber(text); break;

            default:
                if (InvokeRequired)
                    Invoke((MethodInvoker)delegate { DataDownload(text); });
                else
                    DataDownload(text);
                break;
        }

Another technique would be to define your own events for the data received cases. YouOne can define an event for one or all of the cases. You may only need to define an event for the code that will access the UI, but it might be a good idea to add events for all the cases to allow you to componentize the protocol better and move the serial code into a separate class:

// Create an EventArgs based class for the data
public class DataDownloadEventArgs : EventArgs
{
    public string Data { get; set; }

    public DataDownloadEventArgs(string data)
    {
        Data = data;
    }
}

public partial class Form1 : Form
{

    // Event handler when data is downloaded
    public event EventHandler<DataDownloadEventArgs> DataDownloaded;

    // Virtual method to raise event to observers
    protected virtual void OnDataDownloaded(DataDownloadEventArgs e)
    {
        var handler = DataDownloaded;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    // Form constructor
    public Form1()
    {
        InitializeComponent();
        // Add handler for the download event
        DataDownloaded += new EventHandler<DataDownloadEventArgs>(DisplayData);
    }

    // Serial port receive event
    private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        // snip...
        switch (text[0])
        {
            case '?': MemLabelUpdate(); break;
            case '>': WriteConfig(text); break;
            case '=': SealTest(text); break;
            case '<': CurrentNumber(text); break;

            default:
                DataDownload(text);
                OnDataDownloaded(new DataDownloadEventArgs(text));
                break;

        }
        // snip...
    }

    // Change btnDisplayData_Click to the following:
    private void DisplayData(object sender, DataDownloadEventArgs e)
    {
        // insert remaining code from btnDisplayData_Click
    }
}

If you are still having issues with the UI thread and the events, you might use an extension to raise the event to the UI thread like this. Here is an article that might help as well, Cross-Thread Events.

Community
  • 1
  • 1
Ryan
  • 7,835
  • 2
  • 29
  • 36