Event Handler Fires Twice When Plugging/Unplugging USB Serial Port - c#

I am writing a program which is supposed to detect when a USB serial device is plugged in and then log on to the new com port. The code below works wonderfully, but I have noticed in debugging the code and stepping through it that the event handler "DetectChange" fires twice. I'm not sure that this is normal, or an action of the debugger.
In any case, the code works, but I am new at event handling and I would like to make sure that I am not going to cause any issues as I add more code to actually read and write from the serial port.
(I got some of this code from stackoverflow, but I have misplaced my paper with names for attribution. If you see your code below, my heartfelt thanks.)
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Threading;
namespace SerialTest
{
public partial class Form1 : Form
{
SerialMethods serialMethods = new SerialMethods();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
loadCmdBox();
}
private void CmdBoxPort_SelectedIndexChanged(object sender, EventArgs e)
{
handleComPort();
}
private void handleComPort()
{
// Set the right port for the selected item.
// The portname is based on the "COMx" part of the string (SelectedItem)
string item = CmdBoxPort.SelectedItem.ToString();
// Search for the expression "(COM" in the "selectedItem" string
if (item.Contains("(COM"))
{
// Get the index number where "(COM" starts in the string
int indexOfCom = item.IndexOf("(COM");
// Set PortName to COMx based on the expression in the "selectedItem" string
// It automatically gets the correct length of the COMx expression to make sure
// that also a COM10, COM11 and so on is working properly.
string PortName = item.Substring(indexOfCom + 1, item.Length - indexOfCom - 2);
if (serialMethods._serialPort.IsOpen)
{
serialMethods._serialPort.Close();
serialMethods.Connect(PortName);
label5.Text = "Active Port: " + PortName;
}
else
{
serialMethods.Connect(PortName);
label5.Text = PortName;
}
}
else
return;
}
private void loadCmdBox()
{
// Get all serial (COM)-ports you can see in the devicemanager
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2",
"SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"");
// Sort the items in the combobox
CmdBoxPort.Sorted = true;
// Add all available (COM)-ports to the combobox
foreach (System.Management.ManagementObject queryObj in searcher.Get().Cast<ManagementObject>())
{
CmdBoxPort.Items.Add(queryObj["Caption"]);
}
SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
label2.Text = "";
label3.Text = "";
label4.Text = "";
}
protected Task<Task> getSerPorts()
{
CmdBoxPort.Text = "";
CmdBoxPort.Update();
if (!String.IsNullOrEmpty(CmdBoxPort.Text))
{
handleComPort();
return Task.FromResult(Task.CompletedTask);
}
else
{
loadCmdBox();
return Task.FromResult(Task.CompletedTask);
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
SerialPortService.CleanUp();
this.Close();
}
private void RefreshButton_Click(object sender, EventArgs e)
{
refresh();
}
protected Task<Task> refresh()
{
label2.Text = "";
label3.Text = "";
label4.Text = "";
CmdBoxPort.Items.Clear();
getSerPorts();
return Task.FromResult(Task.CompletedTask);
}
protected virtual void DetectChange(EventType changedArgs)
{
if (changedArgs == EventType.Insertion)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at insertion invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
else if (changedArgs == EventType.Removal)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at removal invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
return;
}
}
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
private static readonly SerialMethods SD = new SerialMethods();
static SerialPortService()
{
_serialPorts = SerialPort.GetPortNames();
MonitorDeviceChanges();
}
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = SerialPort.GetPortNames();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
handler?.Invoke(sender, args);
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public string[] SerialPorts => _serialPorts;
public EventType EventType => _eventType;
}
}

Just took a short look at this. It seems like getSerPorts() will always execute loadCmdBox() (CmdBoxPort.Text = ""; ... if (!String.IsNullOrEmpty(CmdBoxPort.Text))) that will attach a new event handler (previous attached event handlers will not be removed by attaching a new one).
You should either remove the existing event handler befor attaching a new one or only attach the event handler once.

Related

SQLite query blocking UI in BackgroundWorker

I've got a BackgroundWorker Queue for my Windows Forms app, which works fine with API calls, but querying a local SQLite database is blocking the UI. I realized a while ago that calling Invoke on an action runs it on the UI thread, so I've changed the DoWork function to be a simple function call on the BackgroundWorker's thread.
The problem is in the loadItems() function in my Form1 class. It queues a background worker to query the sqlite database.
What could be causing the UI block and what can I change to prevent it?
My custom Worker class:
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class Worker : BackgroundWorker
{
public Worker(object Sender)
{
this.Sender = Sender;
}
public object Sender { get; set; }
}
}
My Task Manager class (manages the queue)
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class TaskManager
{
public static Queue<Worker> taskQueue = new Queue<Worker>();
public static List<string> errors = new List<string>();
public static void QueueTask(
object item,
Action<object, Worker, DoWorkEventArgs> action,
Action<object, RunWorkerCompletedEventArgs> actionComplete,
Action<RunWorkerCompletedEventArgs> displayError,
Action<object, ProgressChangedEventArgs> progressChanged)
{
using (var worker = new Worker(item))
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.ProgressChanged += (sender, args) =>
{
progressChanged.Invoke(sender, args);
};
worker.DoWork += (sender, args) =>
{
action(sender, worker, args);
};
worker.RunWorkerCompleted += (sender, args) =>
{
if(args.Error != null)
{
displayError.Invoke(args);
} else
actionComplete.Invoke(sender, args);
taskQueue.Dequeue();
if (taskQueue.Count > 0)
{
startQueue();
}
};
taskQueue.Enqueue(worker);
if(taskQueue.Count == 1)
{
startQueue();
}
}
}
private static void startQueue()
{
var next = taskQueue.Peek();
next.ReportProgress(0, "");
next.RunWorkerAsync(next.Sender);
}
}
}
Database service class for Item:
using Microsoft.Data.Sqlite;
using Dapper;
namespace EveMarket.Database
{
public class Item
{
public Item() { }
public Item(int type_id, string name)
{
this.type_id = type_id;
this.name = name;
}
public string name { get; set; }
public int type_id { get; set; }
public static void insert(Item item)
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "insert into item (type_id, name) values (#type_id, #name)";
connection.ExecuteAsync(sql, item);
}
}
public static List<Item> getAll()
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "select * from item;";
List<Item> items = connection.Query<Item>(sql).ToList();
return items;
}
}
}
}
And the Form which is Queuing these tasks:
using EveMarket.Database;
using EveMarket.Tasks;
using EVEStandard;
namespace EveMarket
{
public partial class Form1 : Form
{
private EVEStandardAPI client;
private List<Item> items;
public Form1()
{
InitializeComponent();
this.items = new List<Item>();
client = new EVEStandardAPI("email#email.com", EVEStandard.Enumerations.DataSource.Tranquility, TimeSpan.FromSeconds(30));
}
private void Form1_Load(object sender, EventArgs e)
{
loadItems();
toolStripProgressBar1.Value = 50;
}
private void updateItemListToolStripMenuItem_Click(object sender, EventArgs e)
{
fetchItems();
}
private void fetchItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
List<long> ids = new List<long>();
for (int i = 1; i < 17; i++)
{
var task = client.Market.ListTypeIdsRelevantToMarketV1Async(10000002, page: i);
var page = task.Result.Model;
ids.AddRange(page);
w.ReportProgress((i * 100) / 16, "Fetching Market Item IDs Page: " + i);
}
List<List<long>> chunks = Utils.ChunkBy(ids, 1000);
int j = 1;
w.ReportProgress((j * 100) / chunks.Count, "Fetching Item Names ");
foreach (var chunk in chunks)
{
var task = client.Universe.GetNamesAndCategoriesFromIdsV3Async(chunk.ConvertAll(i => (int)i));
var names = task.Result.Model;
var types = names.FindAll((item) => item.Category == EVEStandard.Enumerations.CategoryEnum.inventory_type);
foreach (var type in types)
{
Item item = new Item(type.Id, type.Name);
Item.insert(item);
}
w.ReportProgress((j * 100) / chunks.Count, "Fetching Market Item Names Chunk: " + j);
j++;
}
},
// On Complete
(x, e) =>
{
loadItems();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.Result.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
private void loadItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
w.ReportProgress(50, "Loading Items");
List<Item> i = Item.getAll();
w.ReportProgress(100, "Items Loaded");
this.items = i;
},
// On Complete
(x, e) =>
{
foreach (var item in items)
{
itemBindingSource.Add(item);
}
itemBindingSource.EndEdit();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
}
}
Turns out it was the RunWorkerCompleted loop blocking the UI. I discovered that I could simply overwrite the datasource with the result of the Worker's execution, which works immediately.
itemBindingSource.DataSource = e.Result;

How to find items in a ListBox?

I am creating an application where I have a ListBox which has items that are read in from a text file using StreamReader. I have created a search form but I'm not sure what to do next. Can anyone give me some suggestions please? Here is my code:
My code for the ListBox (sorry it's so long)
public partial class frmSwitches : Form
{
public static ArrayList switches = new ArrayList();
public static frmSwitches frmkeepSwitches = null;
public static string inputDataFile = "LeckySafe.txt";
const int numSwitchItems = 6;
public frmSwitches()
{
InitializeComponent();
frmkeepSwitches = this;
}
private void btnDevices_Click(object sender, EventArgs e)
{
frmDevices tempDevices = new frmDevices();
tempDevices.Show();
frmkeepSwitches.Hide();
}
private bool fileOpenForReadOK(string readFile, ref StreamReader dataIn)
{
try
{
dataIn = new StreamReader(readFile);
return true;
}
catch (FileNotFoundException notFound)
{
MessageBox.Show("ERROR Opening file (when reading data in) - File could not be found.\n"
+ notFound.Message);
return false;
}
catch (Exception e)
{
MessageBox.Show("ERROR Opening File (when reading data in) - Operation failed.\n"
+ e.Message);
return false;
}
}
private bool getNextSwitch(StreamReader inNext, string[] nextSwitchData)
{
string nextLine;
int numDataItems = nextSwitchData.Count();
for (int i = 0; i < numDataItems; i++)
{
try
{
nextLine = inNext.ReadLine();
if (nextLine != null)
nextSwitchData[i] = nextLine;
else
{
return false;
}
}
catch (Exception e)
{
MessageBox.Show("ERROR Reading from file.\n" + e.Message);
return false;
}
}
return true;
}
private void readSwitches()
{
StreamReader inSwitches = null;
Switch tempSwitch;
bool anyMoreSwitches = false;
string[] switchData = new string[numSwitchItems];
if (fileOpenForReadOK(inputDataFile, ref inSwitches))
{
anyMoreSwitches = getNextSwitch(inSwitches, switchData);
while (anyMoreSwitches == true)
{
tempSwitch = new Switch(switchData[0], switchData[1], switchData[2], switchData[3], switchData[4], switchData[5]);
switches.Add(tempSwitch);
anyMoreSwitches = getNextSwitch(inSwitches, switchData);
}
}
if (inSwitches != null) inSwitches.Close();
}
public static bool fileOpenForWriteOK(string writeFile, ref StreamWriter dataOut)
{
try
{
dataOut = new StreamWriter(writeFile);
return true;
}
catch (FileNotFoundException notFound)
{
MessageBox.Show("ERROR Opening file (when writing data out)" +
"- File could not be found.\n" + notFound.Message);
return false;
}
catch (Exception e)
{
MessageBox.Show("ERROR Opening File (when writing data out)" +
"- Operation failed.\n" + e.Message);
return false;
}
}
public static void writeSwitches()
{
StreamWriter outputSwitches = null;
if (fileOpenForWriteOK(inputDataFile, ref outputSwitches))
{
foreach (Switch currSwitch in switches)
{
outputSwitches.WriteLine(currSwitch.getSerialNo());
outputSwitches.WriteLine(currSwitch.getType());
outputSwitches.WriteLine(currSwitch.getInsDate());
outputSwitches.WriteLine(currSwitch.getElecTest());
outputSwitches.WriteLine(currSwitch.getPatId());
outputSwitches.WriteLine(currSwitch.getNumDevice());
}
outputSwitches.Close();
}
if (outputSwitches != null) outputSwitches.Close();
}
private void showListOfSwitches()
{
lstSwitch.Items.Clear();
foreach (Switch b in switches)
lstSwitch.Items.Add(b.getSerialNo()
+ b.getType() + b.getInsDate()
+ b.getElecTest() + b.getPatId() + b.getNumDevice());
}
My code for the search form:
private void btnSearch_Click(object sender, EventArgs e)
{
frmSearchSwitch tempSearchSwitch = new frmSearchSwitch();
tempSearchSwitch.Show();
frmkeepSwitches.Hide();
}
If using a List<T> and lambda is not possible for you then go no farther.
Here I use a List<T> for the data source of a ListBox and mocked up data as where the data comes from is not important but here I am focusing on searching on a property within a class where a select is used to index the items then the where searches in this case) for a specific item, if found the index is used to move to the item. No code present for if not located as this is easy for you to do if the code here is something that is doable for you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace ListBoxSearch_cs
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
var results =
((List<Item>)ListBox1.DataSource)
.Select((data, index) => new
{ Text = data.SerialNumber, Index = index })
.Where((data) => data.Text == "BB1").FirstOrDefault();
if (results != null)
{
ListBox1.SelectedIndex = results.Index;
}
}
private void Form1_Load(object sender, EventArgs e)
{
var items = new List<Item>() {
new Item {Identifier = 1, SerialNumber = "AA1", Type = "A1"},
new Item {Identifier = 2, SerialNumber = "BB1", Type = "A1"},
new Item {Identifier = 3, SerialNumber = "CD12", Type = "XD1"}
};
ListBox1.DisplayMember = "DisplayText";
ListBox1.DataSource = items;
}
}
/// <summary>
/// Should be in it's own class file
/// but done here to keep code together
/// </summary>
public class Item
{
public string SerialNumber { get; set; }
public string Type { get; set; }
public int Identifier { get; set; }
public string DisplayText
{
get
{
return SerialNumber + " " + this.Type;
}
}
}
}

C# WPF - Modify a ComboBox from a different class

I'm writing a small WPF application in C# using Kinect and COMs to communicate to an arduino.
I have a combobox where the user can choose the port that he wants to use, I have a watcher on my COMs and everytime a device is Plugged/Unplugged I want to refresh my combobox.
My problem is that the calling to the function is in another class so I have a thread error
Here is the portion of code that doesn't work :
namespace Microsoft.Samples.Kinect.SkeletonBasics
{
using System.IO;
using System.IO.Ports;
using System;
using System.Management;
using System.Windows;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Controls;
using Microsoft.Kinect;
using Microsoft.Kinect.Toolkit;
public partial class MainWindow : Window
{
private WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
internal static ComboBox comboBox; //Internal static variable so it can be used in static method
public MainWindow()
{
InitializeComponent();
}
private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
using (receiveEvent) {}
}
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
comboBox = Com_ComboBox;
string[] ports = SerialPort.GetPortNames();
if (ports.Length == 0)
{
Default_Text.Content = "Aucun port détecté";
comboBox.IsHitTestVisible = false;
comboBox.Focusable = false;
}
else Default_Text.Content = "Arduino sur le port :";
comboBox.SelectedIndex = 0;
foreach (string port in ports)
{
comboBox.Items.Add(port);
}
}
internal static void Refresh_Coms() //I'm trying to call this function from the other class but I get a thread error
{
comboBox.Items.Clear();
ComboBoxItem Default_Text = (ComboBoxItem) comboBox.ItemContainerGenerator.ContainerFromIndex(0);
string[] ports = SerialPort.GetPortNames();
if (ports.Length == 0)
{
Default_Text.Content = "Aucun port détecté";
comboBox.IsHitTestVisible = false;
comboBox.Focusable = false;
comboBox.SelectedIndex = 0;
}
else Default_Text.Content = "Arduino sur le port :";
foreach (string port in ports)
{
comboBox.Items.Add(port);
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public class WMIReceiveEvent : IDisposable
{
private ManagementEventWatcher watcherAttach;
private ManagementEventWatcher watcherDetach;
public WMIReceiveEvent()
{
try
{
watcherAttach = new ManagementEventWatcher();
watcherAttach.EventArrived += Attaching;
watcherAttach.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
watcherAttach.Start();
watcherDetach = new ManagementEventWatcher();
watcherDetach.EventArrived += Detaching;
watcherDetach.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
watcherDetach.Start();
return;
}
catch (ManagementException err)
{
MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
}
}
public void Dispose()
{
try
{
watcherAttach.Stop();
watcherDetach.Stop();
watcherAttach.Dispose();
watcherDetach.Dispose();
}
catch
{
MessageBox.Show("An error occurred while trying to close COM event Handler");
}
}
void Attaching(object sender, EventArrivedEventArgs e)
{
if (sender != watcherAttach) return;
Console.WriteLine("Attaching");
MainWindow.Refresh_Coms(); //I can call the function but the thread can't modify the ComboBox
}
void Detaching(object sender, EventArrivedEventArgs e)
{
if (sender != watcherDetach) return;
Console.WriteLine("Detaching");
MainWindow.Refresh_Coms();
}
~WMIReceiveEvent()
{
this.Dispose();
}
}
}
I'm a little new to C#, I've searched on the web but I can't find an easy solution for doing that, can somebody help me please ?
Add SynchronizationContext to Your WMIReceiveEvent class, like:
public class WMIReceiveEvent : IDisposable
{
private readonly SynchronizationContext _syncContext;
public WMIReceiveEvent(SynchronizationContext syncContext)
{
_cyncContext = syncContext;
}
}
and wrap of calling MainWindow.Refresh_Coms(); with Send method of SynchronizationContext:
_syncContext.Send(o => MainWindow.Refresh_Coms(), null);
And last thing is creating Your WMIReceiveEvent class:
private WMIReceiveEvent receiveEvent = new WMIReceiveEvent(SynchronizationContext.Current);
More about SynchronizationContext.

Cross-thread InvalidOperationException when logging to MyLogger from a background worker

I have created a event driven logger that seems to have issues when called from another thread. How can I make it thread safe?
Here is the LoggerClass:
public static class MyLogger
{
private static List<string> log = new List<string>();
public static event EventHandler LogAdded;
private static string indent = "";
public static void Log(string message)
{
log.Add(indent + message);
if (LogAdded != null)
LogAdded(null, EventArgs.Empty);
}
}
In my form class I initiated it in the following two ways:
public frm_main()
{
InitializeComponent();
MyLogger.LogAdded += new EventHandler(MyLogger_LogAdded);
}
And here is the event progress where the crash occurs:
private void MyLogger_LogAdded(object sender, EventArgs e)
{
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
The crash occurs on the first line abobve commented with "at end of text" in the following call from the backgroundworker:
MyLogger.Log("Sending cmd: \"" + strCmdText + "\" to CMD.exe.");
How do I make MyLogger class thread-safe?
Edit: Going with SeeSharp solution, code changed is:
private void MyLogger_LogAdded(object sender, EventArgs e)
{
if (rtb_logger.InvokeRequired)
{
rtb_logger.BeginInvoke(new Action(delegate {
MyLogger_LogAdded(sender, e);
}));
return;
}
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
You must call body of MyLogger_LogAdded in UI thread.
Try to use Dispatcher.BeginInvoke() when you call methods or properties of rtb_logger.
See C#: Thread safe richtextbox event logging method?
private delegate void MYLOGGEREVENT(object sender, EventArgs e);
private void MyLogger_LogAdded(object sender, EventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new MYLOGGEREVENT(MyLogger_LogAdded), new object[]{sender, e});
}
else{
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
}
try this.

Calling a timer tick event from Windows Form Application from another class with no GUI

I'm trying to run the timer tick event which initially runs on my windows form,also when loaded on another class using a thread. I tried calling the timer event on another thread it didn't help. Am I supposed to use the same timer or create a new timer for that thread. This is my current implementation:
namespace XT_3_Sample_Application
{
public partial class Form1 : Form
{
Queue<string> receivedDataList = new Queue<string>();
System.Timers.Timer tmrTcpPolling = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
TM702_G2_Connection_Initialization();
}
static void threadcal()
{
Class1 c = new Class1();
c.timer_start();
c.test("192.168.1.188",9999);
}
public string Connection_Connect(string TCP_IP_Address, int TCP_Port_Number)
{
if (tcpClient.Connected)
{
Socket_Client = tcpClient.Client;
TcpStreamReader_Client = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
tmrTcpPolling.Start();
Status = "Connected\r\n";
}
else
{
Status = "Cannot Connect\r\n";
}
}
public string Connection_Disconnect()
{
tmrTcpPolling.Stop();
// do something
return "Disconnected\r\n";
}
void TM702_G2_Connection_Initialization()
{
tmrTcpPolling.Interval = 1;
tmrTcpPolling.Elapsed += new ElapsedEventHandler(tmrTcpPolling_Elapsed);
}
#region Timer Event
void tmrTcpPolling_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
if (tcpClient.Available > 0)
{
receivedDataList.Enqueue(TcpStreamReader_Client.ReadLine());
}
}
catch (Exception)
{
//throw;
}
}
private void tmrDisplay_Tick(object sender, EventArgs e)
{
Tick();
}
public void Tick()
{
Console.Write("tick" + Environment.NewLine);
if (receivedDataList.Count > 0)
{
string RAW_Str = receivedDataList.Dequeue();
//tbxConsoleOutput.AppendText(RAW_Str + Environment.NewLine);
tbxConsoleOutput.AppendText(Parser_Selection(RAW_Str) + Environment.NewLine);
}
}
#endregion
private void btnConnect_Click(object sender, EventArgs e)
{
tbxConsoleOutput.AppendText(Connection_Connect(tbxTCPIP.Text, Convert.ToInt32(tbxPort.Text, 10)));
Thread t = new Thread(threadcal);
t.Start();
}
}
}
But the timer tick event starts the moment the application is launched but not on button click - private void btnConnect_Click(object sender, EventArgs e).
I'm trying to call a separate thread for the class Class1's test method. I'm trying to use a similar timer event to receive output from the server, for this thread.
namespace XT_3_Sample_Application
{
class Class1
{
TcpClient tcpClient;
Socket Socket_Client;
StreamReader TcpStreamReader_Client; // Read in ASCII
Queue<string> receivedDataList = new Queue<string>();
System.Timers.Timer tmrTcpPolling = new System.Timers.Timer();
void TM702_G2_Connection_Initialization()
{
tmrTcpPolling.Interval = 1;
tmrTcpPolling.Elapsed += new ElapsedEventHandler(tmrTcpPolling_Elapsed);
}
public void test(string TCP_IP_Address, int TCP_Port_Number)
{
TM702_G2_Connection_Initialization();
try
{
string Status = "";
Ping pingSender = new Ping();
PingOptions options = new PingOptions();
PingReply reply = pingSender.Send(TCP_IP_Address, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
tcpClient = new TcpClient();
tcpClient.Connect(TCP_IP_Address, TCP_Port_Number);
if (tcpClient.Connected)
{
Socket_Client = tcpClient.Client;
TcpStreamReader_Client = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
tmrTcpPolling.Start();
Status = "Connected\r\n";
}
else
{
Status = "Cannot Connect\r\n";
}
}
else
{
Status = "Ping Fail\r\n";
}
MessageBox.Show(TCP_IP_Address + " :" + Status);
}
catch (System.Exception ex)
{
MessageBox.Show(TCP_IP_Address + " :" + ex.Message);
}
setFilterType();
setButtonRadioLvl();
heloCmd();
}
public void timer_Start()
{
Form1 f = new Form1();
f.Tick();
}
}
}
When tried the above method the timer is not fired on the new thread. Any suggestions on this?
Without any blocking code or loop your thread will not live long. the following calls your test method every one second and doesn't use timer
static void threadcal()
{
while (true)
{
Thread.Sleep(1000);
Class1 c = new Class1();
c.test("192.168.1.188", 9999);
}
}

Categories