Populating listview after running serial port thread - c#

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;
}
}

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.

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.

Related

Check multiple checkbox.checked state inside a do while cycle

I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.

How to check output from the serial port match a message to continue write a command in C#?

I use this code to initialize a serial port in C# :
serialPort.PortName = cboCOMPort.Text;
serialPort.BaudRate = Convert.ToInt32(cboBaudRate.Text);
serialPort.Parity = Parity.None;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Handshake = Handshake.None;
serialPort.WriteTimeout = 500;
serialPort.ReadTimeout = 500;
serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceived);
serialPort.Open();
then use this code to read data from serial port :
private void SerialDataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort = (SerialPort)sender;
strDataReceived = serialPort.ReadExisting();
ShowSerialOutput(strDataReceived);
}
and write :
private void SerialDataSend(string strCommand)
{
serialPort.WriteLine(strCommand);
}
My problem : when a device (example a switch) connects to the serial port and executes a command I have written, my program has to wait for the switch finishes executing this command before writing a new command to the serial port. How to check if the switch is finished? (when finished, the switch will send some message contain the keywords like 'completed', 'finished',...). I have tried to use this code but not work :
while(true)
{
if(serialPort.ReadLine().Contains("completed"))
serialDataSend(nextCommand);
}
Sorry for not clearly explaining my problem.
**Example my problem : **
I connect to the switch using serial port and use my program to read/write data. I want to copy a large file from server to the switch using this command : cp tftp://10.0.0.1/file.tgz /var/tmp/file.tgz. I use my program to write this command to the serial port and the switch executes this command. The file is very large so the program need to wait the file copied completely before sending the next command. When finished, the switch show message "Completed". That is my problem : how to check the copy process completed to write the new command.
I will try to approach your problem. Probably will need to rephrase as more information might come from you :)
As I understand it you have more than 1 command that you are sending to the device. Something like a command-list.
As you already have the event for data reception you could also use it to verify whether the completed keyword has been send and set a flag.
EDIT: The flag you can use for the while-wait-loop as you already do.
Your program will wait there until your device confirms the completion and jump out of the loop. Then you need to reset the flag for the next waiting loop.
public class DeviceCommunication
{
bool FlagToProceed = false;
private void MainJob()
{
// send command 1...
SerialDataSend("cp tftp://10.0.0.1/file.tgz /var/tmp/file.tgz.");
// wait
while(!FlagToProceed)
{}
// reset the flag
FlagToProceed = false;
// send command 2 ...
SerialDataSend("WhatEverComesNext");
// wait
while(!FlagToProceed)
{}
// reset the flag
FlagToProceed = false;
}
private void SerialDataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort = (SerialPort)sender;
strDataReceived = serialPort.ReadExisting();
ShowSerialOutput(strDataReceived);
// check whether affirmation has been received and open the gates
if(strDataReceived.Contains("completed"))
{
FlagToProceed = true;
}
}
private void SerialDataSend(string strCommand)
{
serialPort.WriteLine(strCommand);
}
}
This waiting technique is not a very good one! It is just the closest to your posted code. If you want to use it, I would suggest to have a timer running and break out of the while - loop if timeout is reached. Otherwise you might keep stuck in the while loop if the device decides not to confirm with "complete" or data just gets lost during transmission.
Another possibility to solve your problem could be to apply an asynch / await approach.
EDIT: be ware of case sensitivity! "Complete" & "complete" is not the same for the Contains method!
You need to use a state machine and delegates to achieve what you are trying to do. See the code below, I recommend doing all this in a separate thread other then Main. You keep track of the state you're in, and when you get a response you parse it with the correct callback function and if it is what you are expecting you move onto the next send command state.
private delegate void CallbackFunction(String Response); //our generic Delegate
private CallbackFunction CallbackResponse; //instantiate our delegate
private StateMachine currentState = ATRHBPCalStateMachine.Waiting;
SerialPort sp; //our serial port
private enum StateMachine
{
Waiting,
SendCmd1,
Cmd1Response,
SendCmd2,
Cmd2Response,
Error
}
private void do_State_Machine()
{
switch (StateMachine)
{
case StateMachine.Waiting:
//do nothing
break;
case StateMachine.SendCmd1:
CallbackResponse = Cmd1Response; //set our delegate to the first response
sp.Write("Send first command1"); //send our command through the serial port
currentState = StateMachine.Cmd1Response; //change to cmd1 response state
break;
case StateMachine.Cmd1Response:
//waiting for a response....you can put a timeout here
break;
case StateMachine.SendCmd2:
CallbackResponse = Cmd2Response; //set our delegate to the second response
sp.Write("Send command2"); //send our command through the serial port
currentState = StateMachine.Cmd2Response; //change to cmd1 response state
break;
case StateMachine.Cmd2Response:
//waiting for a response....you can put a timeout here
break;
case StateMachine.Error:
//error occurred do something
break;
}
}
private void Cmd1Response(string s)
{
//Parse the string, make sure its what you expect
//if it is, then set the next state to run the next command
if(s.contains("expected"))
{
currentState = StateMachine.SendCmd2;
}
else
{
currentState = StateMachine.Error;
}
}
private void Cmd2Response(string s)
{
//Parse the string, make sure its what you expect
//if it is, then set the next state to run the next command
if(s.contains("expected"))
{
currentState = StateMachine.Waiting;
backgroundWorker1.CancelAsync();
}
else
{
currentState = StateMachine.Error;
}
}
//In my case, I build a string builder until I get a carriage return or a colon character. This tells me
//I got all the characters I want for the response. Now we call my delegate which calls the correct response
//function. The datareceived event can fire mid response, so you need someway to know when you have the whole
//message.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string CurrentLine = "";
string Data = serialPortSensor.ReadExisting();
Data.Replace("\n", "");
foreach (char c in Data)
{
if (c == '\r' || c == ':')
{
sb.Append(c);
CurrentLine = sb.ToString();
sb.Clear();
CallbackResponse(CurrentLine); //calls our correct response function depending on the current delegate assigned
}
else
{
sb.Append(c);
}
}
}
I would put this in a background worker, and when you press a button or something you can set the current state to SendCmd1.
Button press
private void buttonStart_Click(object sender, EventArgs e)
{
if(!backgroundWorker1.IsBusy)
{
currentState = StateMachine.SendCmd1;
backgroundWorker1.RunWorkerAsync();
}
}
Background worker do work event
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (backgroundWorker1.CancellationPending)
break;
do_State_Machine();
Thread.Sleep(100);
}
}

Timer says it's enabled, but never executes

I have a sub which starts one of two timers (depending on 'zone' condition). This sub called 'CheckAndActivateRelays' is itself called by a Serial Port _DataReceived event. I am inserting break points to help me troubleshoot and am seeing that the tmrSoundSirensAfterDelay.Start() line is being executed successfully with the status of the timer even changing to enabled. However the associated Tick event never executes any of the code contained within it.
If I do the same thing by calling the sub from within button24's click event, it works perfectly. Everything is on the same Form with no threaded processes.
Anyone? Thanks
private void checkAndActivateRelays(int zoneNumber)
{
if (globalFullAlarmSet || globalNightAlarmSet || globalDoorsAlarmSet)
{
if (zoneNumber == 1) //Entry zone
{
//kick off a timer after delay specified in Settings1 file,
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//activates the relays if global alarm flags are still set to true
//(i.e. user has not entered code in time)
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Start();
}
}
else //If any other zone is activated during alarm set condition
{
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//Output to relays 1 & 2
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
//then close after duration from Settings1 file
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Start();
}
}
}
}
private void tmrSoundSirensAfterDelay_Tick(object sender, EventArgs e)
{
globalAlarmEntryDurationTicks = globalAlarmEntryDurationTicks + 1;
if (globalAlarmEntryDurationTicks == Settings1.Default.alarmEntryDelayInSeconds) //Value from Settings1 file
{
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Stop();
tmrSoundSirens.Start();
}
}
private void tmrSoundSirens_Tick(object sender, EventArgs e)
{
globalAlarmSirenDurationTicks = globalAlarmSirenDurationTicks + 1;
if (globalAlarmSirenDurationTicks == (Settings1.Default.alarmSirenDurationInMinutes * 5)) //*60 Value from Settings1 file
{
spIOCard.Write("~out10=0~");
spIOCard.Write("~out11=0~");
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Stop();
}
}
private void button24_Click(object sender, EventArgs e)
{
globalFullAlarmSet = true;
checkAndActivateRelays(1);
}
Serial Port Data Received Code:
private void spIO_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
RxString = spIOCard.ReadExisting();
if (RxString == "~in00=1~")
{
checkAndActivateRelays(1);
button10.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in00=0~")
{
button10.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in01=1~")
{
checkAndActivateRelays(2);
button11.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in01=0~")
{
button11.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in02=1~")
{
button12.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in02=0~")
{
button12.BackColor = System.Drawing.Color.LightGray;
}
}
Something to think about since you are using the DataReceivedEvent. According to MSDN it is raised on a secondary thread. This is probably causing your issue.
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object. Because this event is raised on a
secondary thread, and not the main thread, attempting to modify some
elements in the main thread, such as UI elements, could raise a
threading exception. If it is necessary to modify elements in the main
Form or Control, post change requests back using Invoke, which will do
the work on the proper thread.
Since calling Start() is not the problem the timer setup is where you need to look. Make sure you handle the tick event AND set an interval.
myTimer.Tick += new EventHandler(TimerEventProcessor);
// Sets the timer interval to 5 seconds.
myTimer.Interval = 5000;
myTimer.Start();
The key here is that you are doing this in the SerialPort DataReceived event. This event is fired on a separate thread. Thats important because you probably registered for the Tick event on the main thread, but you start the timer on a different one. You'll need to register the Tick event in the checkAndActivateRelays function. Then it should be happy.
The DataReceived event is raised on a secondary thread when data is received from the SerialPort object. Because this event is raised on a secondary thread, and not the main thread, attempting to modify some elements in the main thread, such as UI elements, could raise a threading exception. If it is necessary to modify elements in the main Form or Control, post change requests back using Invoke, which will do the work on the proper thread.

Question to Auto logout C# desktop application

I need to implement an auto logout feature in C#. Previously i have asked a similiar question before and i managed to implement it using the System.Windows.Forms.Timer . But right now i have a additional requirement apart from resetting the timer when the user move the mouse or enters a key i also need to reset the timer when a new message is received via the serial port ( DataReceived event handler ).
serialPort.DataReceived += port_DataRecieved;
I need to include the reset function in a portion of the port_DataRecieved function. I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will received a lot of other messages that i am not interested in. I want to perform a reset when the message that i am interested in arrives. And i know where to put the reset feature. The issue is that the timer does not reset in port_DataRecieved method. And i cannot achieve the desired result using the System.Threading.Timer. Anyone can guide me or provide some suggestion on this issue ? Any help provided will be greatly apperciated.
public partial class Form1: Form
{
private System.Windows.Forms.Timer sessionTimer = new System.Windows.Forms.Timer();
public Form1()
{
initialiseTimer();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
try
{
serialPort= (SerialPort)sender;
str = serialPort.ReadExisting();
string[] split = str.Split(Convert.ToChar(10));
for (int i = 1; i < split.Length; i++)
{
str = split[i];
if (split[i].StartsWith("+CMTI:"))
{
sessionTimer.Stop();
sessionTimer.Start();
//Other codes
}
}
}
catch (Exception)
{
MessageBox.Show("Error processing received commands !", "CONNECTION ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
sendRecPort.Close();
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void initialiseTimer()
{
sessionTimer.Interval = (5 * 60 * 1000);
sessionTimer.Tick += new EventHandler(logOutUser);
sessionTimer.Stop();
sessionTimer.Start();
}
private void logOutUser(object sender, EventArgs e)
{
// logout the user
this.Hide();
//Open up the login Form
login.Show();
}
}
Your problem is that the the DataReceived event is being executed on a thread other than the UI thread. You're trying to modify the timer (a UI object) from a non-UI thread. This typically throws an exception, but it's possible that the method that issues the DataReceived event is swallowing that exception.
From the documentation for the DataReceived event:
The DataReceived event is raised on a
secondary thread when data is received
from the SerialPort object. Because
this event is raised on a secondary
thread, and not the main thread,
attempting to modify some elements in
the main thread, such as UI elements,
could raise a threading exception. If
it is necessary to modify elements in
the main Form or Control, post change
requests back using Invoke, which will
do the work on the proper thread.
You need to synchronize with the UI thread to set the timer.
void ResetTimer()
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
//Other codes
this.Invoke((MethodInvoker)delegate { ResetTimer(); });
//Other codes
}
I need to include the reset function in a portion of the port_DataReceived function.
Ok. Gotcha.
I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will receive a lot of other messages that I am not interested in.
Ok, but I thought you said:
I want to perform a reset when the message that I am interested in arrives.
So you either have to listen to that DataReceived method, or you won't know when that message arrives.
I'm confused. What is it you want to do? Magic?
if (dataReceived == "someValue1")
{
//action if matches "someValue1"
}
else if (dataReceived.Contains("someValue2"))
{
// action if contains "someValue2"
}
else if (dataReceived.IndexOf("someValue3") != -1 )
{
// action if contains "someValue3"
}
else if (dataReceived == "someValue4")
{
// action if matches "someValue4"
}
else
{
// default action
}

c# cross-thread help

I've got some backgroundworker threads that are working on calculations or saving data. Every time a thread is done working I'm first adding a message to a static messagelist and then I'm showing this in a richtextbox.
The message contains the hour, a label, the message and a message type. This message type I'm using to show the message in a specific color.
Now I've got the problem that some times a 2 threads are done at the same time and try to set a message. So I got the cross-thread exception.
I know that I can solve this by using a delegate. But I'm kinda stuck here.
this is how I set my message currently:
private void SetMessages()
{
rtxtMessage.Text = "";
foreach (var message in GlobalVariables.MessageList)
{
var text = message.Date.ToShortTimeString() + " " + message.Label + ": " +
message.TheMessage;
switch (message.Type)
{
case GlobalVariables.MessageType.normal:
rtxtMessage.SelectionColor = Color.Black;
break;
case GlobalVariables.MessageType.calculation:
rtxtMessage.SelectionColor = Color.Green;
break;
case GlobalVariables.MessageType.error:
rtxtMessage.SelectionColor = Color.Red;
break;
case GlobalVariables.MessageType.warning:
rtxtMessage.SelectionColor = Color.Orange;
break;
default:
break;
}
rtxtMessage.SelectedText = text + Environment.NewLine;
rtxtMessage.ScrollToCaret();
}
pnlMessage.Visible = true;
}
So main question is how can I rewrite this to get it working with a delegate?
I interpret your question as if you are using the BackgroundWorker class for the threaded work. Then then question is how the messages come into the GlobalVariables.MessageList collection. If it is done in the RunWorkerCompleted event, you should not have any cross-threading problems, since it executes on the UI thread (this is the way I would recommend you to do). If it is updated directly from the worker process you will need to take care of synchronization and threading issues yourself (using one of the available locking mechanisms).
Update (after the comment response in the original question):
The RunWorkerCompleted should typically run on the UI thread (or perhaps rather on the thread on which RunWorkerAsync was invoked, I guess), so you should typically not need to bother about threading issues when updating the UI from that thread. However, to be really sure, you can use the following approach:
private void BackgroundWorker_RunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(
new Action<object, RunWorkerCompletedEventArgs>(
BackgroundWorker_RunWorkerCompleted), sender, e);
}
else
{
// update the message list, and then call SetMessages()
SetMessages();
}
}
That said, I would urge you to pinpoint under exactly which circumstances the threading exception occurs, since it should really not happen using the RunWorkerCompleted event. I ran a test where I started a large (a couple of hundred) number of backgroundworkers simultaneously, and failed to provoke any sort of collision. It also did not use the Invoke-path in the above code sample once.
As a side note I think that you would not need to first store the messages in a list, just to clear and re-populate the textbox with all messages each time. I think you could instead change the SetMessages method into a method that takes a message as paramter and that simply adds the message to the textbox:
private void SetMessage(MyMessage message)
{
var text = message.Date.ToShortTimeString() + " " + message.Label + ": " + message.TheMessage;
switch (message.Type)
{
case GlobalVariables.MessageType.normal:
rtxtMessage.SelectionColor = Color.Black;
break;
case GlobalVariables.MessageType.calculation:
rtxtMessage.SelectionColor = Color.Green;
break;
case GlobalVariables.MessageType.error:
rtxtMessage.SelectionColor = Color.Red;
break;
case GlobalVariables.MessageType.warning:
rtxtMessage.SelectionColor = Color.Orange;
break;
default:
break;
}
rtxtMessage.SelectedText = text + Environment.NewLine;
rtxtMessage.ScrollToCaret();
pnlMessage.Visible = true;
}
You should be able to call this method straight from the RunWorkerCompleted event handler and just pass the message into the method.
You should be able to get this working by locking your static Message List like:
private void SetMessages()
{
rtxtMessage.Text = "";
lock(GlobalVariables.MessageList)
{
foreach (var message in GlobalVariables.MessageList)
{
//Rest of your code
}
pnlMessage.Visible = true;
}
}

Categories