I am making a program that reads values on NFC cards.
I have a class that reads values from Serial comms sent by an EMBED hooked up to an NFC IC. Once data has been received, the program (in the main form) must change 'page' and process the data.
I have used User Controls as different 'pages'.
I understand that there are multiple threads and i cannot change the GUI from a different thread without using some sort of Invoke/Deleagate?. However, i am unsure on how to implement this on my code.
Here is the code i have (with omissions of irrelevant code):
SerialData Class
public class SerialData
{
public SerialPort ComPort;
public String savedText;
public SerialData()
{
ComPort = new SerialPort();
// set port data
ComPort.PortName = "COM5";
ComPort.BaudRate = 9600;
ComPort.DataBits = 8;
ComPort.RtsEnable = true;
ComPort.DtrEnable = true;
}
public void readCard()
{
try
{
savedText = "";
ComPort.Open();
ComPort.DataReceived += ComPort_DataReceived;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
}
}
private void ComPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
savedText += data;
}
}
Form1 Code
public partial class Form1 : Form
{
List<UserControl> Pages = new List<UserControl>();
public int CurrentPage = 0;
SerialData SerialDataDriver = new SerialData();
public Form1()
{
InitializeComponent();
Pages.Add(page1);
Pages.Add(page2);
Pages.Add(page3);
// change to first page
ChangeToPage(0);
}
private void Page1ButtonClick(object sender, EventArgs e)
{
ChangeToPage(1);
SerialDataDriver.readCard();
}
void ComPort_DataReceived(string data)
{
SerialDataDriver.ComPort.Close(); /// close ComPort
ChangeToPage(2);
}
}
Here's an example of a custom event:
public class SerialData
{
public SerialPort ComPort;
public String savedText;
public delegate void Data(string message);
public event Data NewData;
private void ComPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
savedText += data;
if (NewData != null)
{
NewData(savedText);
}
}
}
...and how to wire up and receive it in the form:
public Form1()
{
InitializeComponent();
Pages.Add(page1);
Pages.Add(page2);
Pages.Add(page3);
// change to first page
ChangeToPage(0);
SerialDataDriver.NewData += SerialDataDriver_NewData;
}
private void SerialDataDriver_NewData(string message)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate() {
SerialDataDriver_NewData(message);
});
}
else
{
// do something with "message"
this.label1.Text = message;
}
}
You can make a wrapper that implements SerialPort events using custom add and remove accessors:
using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private readonly SerialData _data;
public Form1()
{
InitializeComponent();
Closing += (sender, args) => { _data.Dispose(); };
_data = new SerialData(new SerialPort("COM5", 9600));
_data.DataReceived += Serial_DataReceived;
}
private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// do something
}
}
public class SerialData : IDisposable
{
private readonly object _locker = new object();
private readonly SerialPort _port;
public SerialData(SerialPort port)
{
_port = port ?? throw new ArgumentNullException(nameof(port));
}
#region IDisposable Members
public void Dispose()
{
_port.Dispose();
}
#endregion
public event SerialDataReceivedEventHandler DataReceived
{
add
{
lock (_locker)
{
_port.DataReceived += value;
}
}
remove
{
lock (_locker)
{
_port.DataReceived -= value;
}
}
}
}
}
Related
I have Multiple class that need to subscribe to a delegate.
How is that possible? If I do declare delegate in my mainForm is that well coded?
Example code:
internal delegate void DEL_SetSingleVal(Single value);
public partial class Form1 : Form
{
Single Data;
ClasswithDel ClassDelegate;
TryToAccess AccessDelegate;
public Form1()
{
InitializeComponent();
ClassDelegate = new ClasswithDel();
AccessDelegate = new TryToAccess();
ClassDelegate.SetValCbk += new DEL_SetSingleVal(SetValCbkFn);
ClassDelegate.SetValCbk += new DEL_SetSingleVal(AccessDelegate.SetValCbkObj2Fn);
}
internal void SetValCbkFn(Single value)
{
Data = value;
}
private void SetValueLabel(String value)
{
label1.Text = value;
}
private void button1_Click(object sender, EventArgs e)
{
ClassDelegate.SetValue(Convert.ToSingle(textBox1.Text));
}
}
public class TryToAccess
{
private Single Data2;
internal void SetValCbkObj2Fn(Single value)
{
Data2 = value;//value come from From1
}
}
public class ClasswithDel
{
internal DEL_SetSingleVal SetValCbk;
public void SetValue(Single valuesent)//value to dispatch to Form1 and TryToAccess
{
SetValCbk(valuesent);
}
}
Is this looking good? Thanks for help!
I'm trying to understand the lock mechanism.
if I have multiple events to lock on different value should I use an object lock for each?
More serious code added:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
namespace ValueChangeOnEventForm
{
public partial class Form1 : Form
{
private Test_Onchange DataSource;
Thread Task1;
private bool Flag_Stop_Task1;
public Form1()
{
InitializeComponent();
graph1.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
graph1.ChartAreas[0].AxisX.IsLabelAutoFit = true;
graph1.ChartAreas[0].AxisX.ScaleView.Size = 100;
graph2.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
graph2.ChartAreas[0].AxisX.IsLabelAutoFit = true;
graph2.ChartAreas[0].AxisX.ScaleView.Size = 100;
DataSource = new Test_Onchange();
DataSource.ValueChanged += new EventHandler(EventValueChange);//Value input info
DataSource.SecondValueChange += new EventHandler(EventSecondValueChange);//second value
Task1 = new Thread(new ThreadStart(Task_1));//create the thread
Task1.Start();//start the thread
}
protected virtual void EventSecondValueChange(object sender, EventArgs e)
{
double valueMAX = 0, size = 0;
if (graph1.InvokeRequired)
{
graph1.Invoke(new MethodInvoker(delegate { graph1.Series["ValueOnGraph"].Points.AddY(DataSource.Value); }));
graph1.Invoke(new MethodInvoker(delegate { valueMAX = graph1.ChartAreas[0].AxisX.Maximum; }));
graph1.Invoke(new MethodInvoker(delegate { size = graph1.ChartAreas[0].AxisX.ScaleView.Size; }));
if (valueMAX - 10 > size)
{
graph1.Invoke(new MethodInvoker(delegate { graph1.ChartAreas[0].AxisX.ScaleView.Scroll(graph1.ChartAreas[0].AxisX.Maximum); }));
graph1.Invoke(new MethodInvoker(delegate { graph1.Series["ValueOnGraph"].Points.RemoveAt(0); }));
}
}
}
protected virtual void EventValueChange(object sender, EventArgs e)
{
double valueMAX=0,size=0;
if (graph2.InvokeRequired)
{
graph2.Invoke(new MethodInvoker(delegate { graph2.Series["ValueOnGraph2"].Points.AddY(DataSource.Secondvalue); }));
graph2.Invoke(new MethodInvoker(delegate { valueMAX = graph2.ChartAreas[0].AxisX.Maximum; }));
graph2.Invoke(new MethodInvoker(delegate { size = graph2.ChartAreas[0].AxisX.ScaleView.Size; }));
if (valueMAX - 10 > size)
{
graph2.Invoke(new MethodInvoker(delegate { graph2.ChartAreas[0].AxisX.ScaleView.Scroll(graph2.ChartAreas[0].AxisX.Maximum); }));
graph2.Invoke(new MethodInvoker(delegate { graph2.Series["ValueOnGraph2"].Points.RemoveAt(0); }));
}
}
}
private void Task_1()
{
while (!Flag_Stop_Task1)
{
Random RandVal = new Random();
Random RandVal2 = new Random();
int Value = RandVal.Next(0, 100);
int SecondValue = RandVal2.Next(50, 200);
DataSource.Value = Value;
DataSource.Secondvalue = SecondValue;
Thread.Sleep(100);
}
Flag_Stop_Task1 = false;
}
private void btn_StopTask_1_Click(object sender, EventArgs e)
{
Flag_Stop_Task1 = true;
}
}
}
And then
namespace ValueChangeOnEventForm
{
class Test_Onchange
{
private int value;
private int secondvalue;
protected object _lock = new object();
public event System.EventHandler ValueChanged;
public event System.EventHandler SecondValueChange;
protected virtual void OnValueChange()
{
lock (this._lock)
{
EventHandler eventvaluechange = ValueChanged;
if (eventvaluechange != null)
eventvaluechange(this, EventArgs.Empty);
}
}
protected virtual void OnSecondValueChange()
{
lock (this._lock)
{
EventHandler eventvaluechange = SecondValueChange;
if (eventvaluechange != null)
eventvaluechange(this, EventArgs.Empty);
}
}
public int Value
{
get { return this.value; }
set
{
if (value != this.value)
{//if value changed enter
this.value = value;
OnValueChange();
}
}
}
public int Secondvalue
{
get { return this.secondvalue; }
set
{
if (value != this.secondvalue)
{//if value changed enter
this.secondvalue = value;
OnSecondValueChange();
}
}
}
}
}
Do I need two lock (lock1 and lock2 object or only one for both value and secondvalue....?
Thanks a lot.
Update
Ok let's do it so.
I'm using beckhoff PLC which are real time Task PLC. and I'm reading two value on it when the value change. like this:
Form1 Class:
namespace RealTimeLock
{
using Beckhoff.App.Ads.Core;
using Beckhoff.App.Ads.Core.Plc;
using TwinCAT.Ads;
using System.IO;
public partial class Form1 : Form
{
private PLC PLCData;
public Form1()
{
InitializeComponent();
}
public Form1(IBAAdsServer _adsServer)
: this()
{
PLCData = new PLC(_adsServer);
PLCData.ErrorBoolChanged += new EventHandler(EventErrorChanged);//error info
PLCData.ForceValChanged += new EventHandler(EventForceChanged);//Force input info
}
protected virtual void EventErrorChanged(object sender, EventArgs e)
{
//state of error PLC
lv_ErrorInfo.Text = "PLC Error num : " + PLCData.i_ErrorID.ToString();
}
protected virtual void EventForceChanged(object sender, EventArgs e)
{//modify graphical data PLC Force data
lv_ForceInfo.Text = PLCData.i_ForceVal.ToString();
c_graphForceIN.Series["ForceData"].Points.AddY(PLCData.i_ForceVal);
if (c_graphForceIN.ChartAreas[0].AxisX.Maximum - 10 > c_graphForceIN.ChartAreas[0].AxisX.ScaleView.Size)
{
c_graphForceIN.ChartAreas[0].AxisX.ScaleView.Scroll(c_graphForceIN.ChartAreas[0].AxisX.Maximum);
c_graphForceIN.Series["ForceData"].Points.RemoveAt(0);
}
}
}
}
Error ID and Force change showed in Form1 label lv_ErrorID and lv_Force and graphForceIN add point.
The events handler on the other side (PLC class) looks like this:
PLC Class:
namespace RealTimeLock
{
using Beckhoff.App.Ads.Core;
using Beckhoff.App.Ads.Core.Plc;
using TwinCAT.Ads;
using System.IO;
public partial class Form1 : Form
{
private PLC PLCData;
public Form1()
{
InitializeComponent();
}
public Form1(IBAAdsServer _adsServer)
: this()
{
PLCData = new PLC(_adsServer);
PLCData.ErrorBoolChanged += new EventHandler(EventErrorChanged);//error info
PLCData.ForceValChanged += new EventHandler(EventForceChanged);//Force input info
}
protected virtual void EventErrorChanged(object sender, EventArgs e)
{
//state of error PLC
lv_ErrorInfo.Text = "PLC Error num : " + PLCData.i_ErrorID.ToString();
}
protected virtual void EventForceChanged(object sender, EventArgs e)
{//modify graphical data PLC Force data
lv_ForceInfo.Text = PLCData.i_ForceVal.ToString();
c_graphForceIN.Series["ForceData"].Points.AddY(PLCData.i_ForceVal);
if (c_graphForceIN.ChartAreas[0].AxisX.Maximum - 10 > c_graphForceIN.ChartAreas[0].AxisX.ScaleView.Size)
{
c_graphForceIN.ChartAreas[0].AxisX.ScaleView.Scroll(c_graphForceIN.ChartAreas[0].AxisX.Maximum);
c_graphForceIN.Series["ForceData"].Points.RemoveAt(0);
}
}
}
}
Does it seem to be correct coding for you guys? and while I have a real time task running there do I need to lock variables and if so, do I need two lock or only one??
Thanks for your remark on this!!
I'm writing a fairly basic application, where one of the tasks is communicating with an Arduino Uno card.
I would like to write the serial communication as a separate module, so the forms only calls for the input from Arduino, and the module will handle the creating and opening of the serialPort and reading data from it.
For testing purposes I wrote a program for the Arduino which prints the elapsed milliseconds every half second to the serialPort.
I would like to populate the textBox of my Form with the output from Arduino after I press a button.
I create the serialPort in the SerialComm class, and although I attach a DataReceived event handler to it, it never seems to fire.
Here is the code for the SerialComm class:
class SerialComm
{
private List<String> availablePorts;
private SerialPort arduino;
private string receivedText;
public String[] portList
{ get
{
EnumPorts();
return availablePorts.ToArray();
}
}
public string receivedData
{
get
{
return receivedText;
}
}
public void InitialiseSerial()
{
arduino = new SerialPort();
arduino.BaudRate = 9600;
arduino.DtrEnable = true;
// Add event handler
arduino.DataReceived += new SerialDataReceivedEventHandler(arduino_DataReceived);
}
public void EnumPorts()
{
availablePorts = new List<string>();
foreach (string s in SerialPort.GetPortNames())
{
availablePorts.Add(s);
}
}
public void StartMC(SerialPort serialPort, String portName)
{
arduino = serialPort;
if (arduino.IsOpen)
{
arduino.Close();
}
else
{
//Initialise Serial Port
arduino.PortName = portName;
arduino.Open();
}
}
//This never fires==================
private void arduino_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
receivedText = arduino.ReadExisting();
}
public void CloseMC()
{
if (arduino.IsOpen)
{
arduino.Close();
arduino.Dispose();
}
}
}
In the Form I call the text from Arduino like this: (I have a button (Button1), a combobox for selecting the COM port (comboBox1) and a textBox on the Form)
public partial class Form1 : Form
{
SerialComm arduino = new SerialComm();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Arduino
arduino.InitialiseSerial();
String[] portList = arduino.portList;
foreach (String COM in portList)
{
comboBox1.Items.Add(COM);
}
if (portList.Length > 0)
{
comboBox1.SelectedItem = comboBox1.Items[0];
}
else
{
comboBox1.Text = "No microcontroller found!";
}
}
private void button1_Click(object sender, EventArgs e)
{
arduino.StartMC(serialPort1, comboBox1.SelectedItem.ToString());
//as the DataReceived never fires arduino.receivedData stays null
if (arduino.receivedData != null)
{
for (int i = 0; i < 101; i++)
{
textBox1.AppendText(arduino.receivedData);
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
arduino.CloseMC();
}
}
Do you see any reason why the event handler doesn't trigger?
Thank you very much for your help in advance.
Best Regards,
Peter
I have a serial port class, and I would like to control send/receive via my GUI, and have the GUI update based on receipt of data from the serial port (or other events). So the two relevant classes are the serial class and the main window class.
I have the code below which compiles, but I get an exception when I try to run.
public class MySerThread
{
public SerialPort serport;
public event SerialDataReceivedEventHandler newSerData;
public MySerThread()
{
serport = new SerialPort("COM1", 115200);
serport.Open();
serport.DataReceived += DataReceivedHandler;
}
public void DataReceivedHandler(object s, SerialDataReceivedEventArgs e)
{
byte[] data = new byte[serport.BytesToRead];
serport.Read(data, 0, data.Length);
// here's where I think I'm going wrong?
if(newSerData != null)
newSerData(s,e);
}
}
And then in my GUI class...
public partial class MainWindow : Window
{
MySerThread myPort;
public MainWindow()
{
// Exception triggers here
myPort.newSerData += DisplaySerDataHandler;
}
private void DisplaySerDataHandler(object sender, SerialDataReceivedEventArgs e)
{
this.ReceivedCallback(e);
}
private void ReceivedCallback(SerialDataReceivedEventArgs e)
{
if(this.someTextBlock.Dispatcher.CheckAccess())
{
this.UpdateTextBlock(e);
}
else
{
this.someTextBlock.Dispatcher.BeginInvoke(new Action<SerialDataReceivedEventArgs>(this.UpdateTextBlock), e);
}
}
private void UpdateTextBlock(SerialDataReceivedEventArgs e)
{
someTextBlock.Text = "got new data";
}
}
So, what am I doing wrong here? What is the best way to do this?
You can't access myPort without creating an instance.
MySerThread myPort = new MySerThread();
I have a MySerialPort class/object accessed from FormGUI. After creating a MySerialPort object, I want to open it, and keep receiving data continuously. The data will be stored and managed in the object's data buffer. FormGUI's memoEdit will show the received codes from the MySerialPort object's buffer.
How can I use "new Thread()" with the [port].ReadExisting method?
using System;
using System.IO.Ports;
using System.Threading;
class MySerialPort
{
public SerialPort CreatePort(string portName, int portSpeed, int portParity, int portDataSize, int portStopBits)
{
// fixed values while testing
var port = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One);
return port;
}
public void OpenPort(SerialPort port)
{
port.Open();
new Thread(() => port.ReadExisting).Start();
while (true)
{
// Send to buffer
// Maybe some break condition
}
}
The Observer pattern is working now, I got the data I was expecting from the serial port, thanks Henk.
public partial class MyGUI : Form
{
private MySerialPort MyPort = new MySerialPort();
public MyGUI()
{
InitializeComponent();
MyPort.OnDataBufferUpdate += DisplayNewData;
}
public void DisplayNewData(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { DisplayNewData(sender, e); }));
}
else
{
memoEdit.Text = MyPort.DataBuffer;
}
}
}
public class MySerialPort
{
public MySerialPort()
{
// Initialize serial port
_Port.DataReceived += _Port_DataReceived;
}
public delegate void HandleDataBufferUpdate(object sender, EventArgs e);
public event HandleDataBufferUpdate OnDataBufferUpdate = delegate {};
private void _Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// Set _Port.DataBuffer from serial port buffer, then activate event below to signal form
OnDataBufferUpdate(this, EventArgs.Empty);
}
}