I am developing a application with C# and Arduino.I reading values from Arduino and set the value on the textbox.I want always read value from Arduino and set this on the interface.However this code doesn't working.
Shoul I run while statement in a different thread?
This is my code
SerialPort port = new SerialPort("COM4", 9600);
port.Open();
while (true)
{
String s=port.ReadLine();
txtValue.Text=s;
}
port.Close();
}
I want show value which I read from Arduino to show in the textbox simultaneously.
This is code to start (put it in constructor, window Loaded or button click event):
Task.Run(() =>
{
_stop = false; // define as private field of window class
var port = new SerialPort("COM4", 9600);
port.Open();
while (!_stop)
{
var s=port.ReadLine();
Dispatcher.Invoke(() => txtValue.Text = s); // InvokeAsync?
}
port.Close();
});
This is code to stop (put it into window Closing event):
_stop = true;
This doesn't uses any bindings, but should give you an idea of how to organize port operation (with or without bindings).
I can't speak to the arduino side, but if you are using .net 4.0 or 4.5 you could do something like below:
Task myTask = Task.Factory.StartNew(() => {
while (true)
{
String s=port.ReadLine();
txtValue.Text=s;
}
});
as mentioned by Sinatr be sure to have a way to stop execution. You could do this by setting a vairable instead of using "true". As for where to put the code it is really dependent on what your final program will be.
With MVVM Light and SolidSoils4Arduino you should be able to quickly create an observable binding with Arduino serial messages.
Here is a basic viewmodel class:
public class MainViewModel : GalaSoft.MvvmLight.ViewModelBase, IObserver<string>
{
#region Fields
private string _lastSerialMessageReceived;
private readonly ObservableCollection<string> _serialMessages = new ObservableCollection<string>();
#endregion
#region Constructors
public MainViewModel()
{
var connection = new Solid.Arduino.SerialConnection("COM3", Solid.Arduino.SerialBaudRate.Bps_115200);
var session = new Solid.Arduino.ArduinoSession(connection);
session.CreateReceivedStringMonitor().Subscribe(this);
}
#endregion
#region Public Interface
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(string value)
{
_serialMessages.Add(value);
LastSerialMessageReceived = value;
}
public ObservableCollection<string> SerialMessages
{
get { return _serialMessages; }
}
public string LastSerialMessageReceived
{
get { return _lastSerialMessageReceived; }
private set { Set(() => LastSerialMessageReceived, ref _lastSerialMessageReceived, value); }
}
#endregion
}
You can bind your textbox to property LastSerialMessageReceived. Property SerialMessages could be bound to a listbox.
Related
When I execute the updateScreen() function, an exception is thrown when the new value is set in the TextLabel string. This exception is demonstrated in the figure right after the code.
This error occurs when I invoke the screen update through the INotifyPropertyChanged interface or through the method of the ObservableObject class, after creating a new Thread.
My code:
public class PageInicialViewModel : ObservableObject
{
private int cont = 0;
private string _textLabel = 0.ToString();
public string TextLabel
{
get => _textLabel;
set => SetProperty(ref _textLabel, value);
}
public void updateScreen()
{
Task.Factory.StartNew(updateTextLabel);
}
public void updateTextLabel()
{
while (true)
{
cont++;
TextLabel = cont.ToString();
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
The Error:
System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))'
It's the age-old problem that the UI can only be updated from the main-thread (it was created with). (By varying degree of tolerance in one or other ui framework.)
Solution for WinUI would look something like this.
public class PageInicialViewModel : ObservableObject
{
private int cont = 0;
private string _textLabel = 0.ToString();
private Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue;
public PageInicialViewModel()
{
// must be called on ui thread
_dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
}
public string TextLabel
{
get => _textLabel;
set => SetProperty(ref _textLabel, value);
}
public void updateScreen()
{
Task.Factory.StartNew(updateTextLabel);
}
public void updateTextLabel()
{
while (true)
{
cont++;
_dispatcherQueue.TryEnqueue(() =>
{
TextLabel = cont.ToString();
});
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
Just for reference, in WPF the same is achieved with:
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
});
You can find a lot of explanation and articles for the general technical principle if you search for these term.
I am modifying to first question attempt.
I need help passing the data from a listbox and pass it to another method so every time the listbox gets data add it from the tread, it should also send that data to my new method and it to the my list because in that method I will be doing some parsing because the data from the listbox is a long string barcode but I don't need help on parsing the data because I can do that, the part I need help only is to pass the data from thread that is passing it to the listbox it should also be send to my method ReadAllData() so in that method I will received the data and then I will do the parsing the make a return.
Here is the method where the listbox is stored and receives the data from a thread from telnet port 23
Here is the code I need to send the data from the lst_BarcodeScan to the method ReadAllData method and store to my list.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.Remoting.Messaging;
using System.Security.Authentication.ExtendedProtection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BarcodeReceivingApp
{
public class TelnetConnection
{
private Thread _readWriteThread;
private TcpClient _client;
private NetworkStream _networkStream;
private string _hostname;
private int _port;
private BarcodeReceivingForm _form;
private bool _isExiting = false;
public TelnetConnection(string hostname, int port)
{
this._hostname = hostname;
this._port = port;
}
public TelnetConnection()
{
}
public void ServerSocket(string ip, int port, BarcodeReceivingForm f)
{
this._form = f;
try
{
_client = new TcpClient(ip, port);
}
catch (SocketException)
{
MessageBox.Show(#"Failed to connect to server");
return;
}
_networkStream = _client.GetStream();
_readWriteThread = new Thread(ReadWrite);
//_readWriteThread = new Thread(() => ReadWrite(f));
_readWriteThread.Start();
}
public void Exit()
{
_isExiting = true;
}
public void ReadWrite()
{
var received = "";
do
{
received = Read();
if (received == null)
break;
if (_form.lst_BarcodeScan.InvokeRequired)
{
_form.lst_BarcodeScan.Invoke(new MethodInvoker(delegate
{
_form.lst_BarcodeScan.Items.Add(received + Environment.NewLine);
}));
}
} while (!_isExiting);
CloseConnection();
}
public List<string> ReadAllData()
{
var obtainData = new List<string>();
return obtainData;
}
public string Read()
{
var data = new byte[1024];
var received = "";
var size = _networkStream.Read(data, 0, data.Length);
if (size == 0)
return null;
received = Encoding.ASCII.GetString(data, 0, size);
return received;
}
public void CloseConnection()
{
MessageBox.Show(#"Closed Connection",#"Important Message");
_networkStream.Close();
_client.Close();
}
}
}
Main class that will call the methods from the telnetconnection class or any other classes I will add.
using System;
using System.Windows.Forms;
namespace BarcodeReceivingApp
{
public partial class BarcodeReceivingForm : Form
{
//GLOBAL VARIABLES
private const string Hostname = "myip";
private const int Port = 23;
private TelnetConnection _connection;
public BarcodeReceivingForm()
{
InitializeComponent();
//FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
}
private void btn_ConnectT_Click(object sender, EventArgs e)
{
_connection = new TelnetConnection(Hostname, Port);
_connection.ServerSocket(Hostname, Port, this);
}
private void btn_StopConnection_Click(object sender, EventArgs e)
{
//_connection = new TelnetConnection(Hostname, Port);
//_connection.ServerSocket(Hostname, Port, this);
_connection.Exit();
}
private void btn_RemoveItemFromListAt_Click(object sender, EventArgs e)
{
for (var i = lst_BarcodeScan.SelectedIndices.Count - 1; i >= 0; i--)
{
lst_BarcodeScan.Items.RemoveAt(lst_BarcodeScan.SelectedIndices[i]);
}
}
private void BarcodeReceivingForm_Load(object sender, EventArgs e)
{
lst_BarcodeScan.SelectionMode = SelectionMode.MultiSimple;
}
private void btn_ApplicationSettings_Click(object sender, EventArgs e)
{
var bcSettingsForm = new BarcodeReceivingSettingsForm();
bcSettingsForm.Show();
}
private void btn_ClearBarcodeList_Click(object sender, EventArgs e)
{
lst_BarcodeScan.Items.Clear();
}
}
}
The easiest way to implement without adding more complexity to the thread is to simply raise an event on the listbox when and item is added to it. The problem is that the listbox do no have any events that allow to do that but you can create your own version with a dozen lines of code.
The goal is to create a derived control of the listbox then you add to it a method to trigger a custom event when an item is added and bingo.
Here's the custom listbox class with the custom EventArgs.
// custom override class over the list box so we can create an event when items are added
public class ListBoxWithEvents : ListBox
{
// the event you need to bind to know when items are added
public event EventHandler<ListBoxItemEventArgs> ItemAdded;
// method to call to add items instead of lst.Items.Add(x);
public void AddItem(object data)
{
// add the item normally to the internal list
var index = Items.Add(data);
// invoke the event to notify the binded handlers
InvokeItemAdded(index);
}
public void InvokeItemAdded(int index)
{
// invoke the event if binded anywhere
ItemAdded?.Invoke(this, new ListBoxItemEventArgs(index));
}
}
// basic event handler that will hold the index of the item added
public class ListBoxItemEventArgs : EventArgs
{
public int Index { get; set; } = -1;
public ListBoxItemEventArgs(int index)
{
Index = index;
}
}
Now you need to change your listbox on your form with the ListBoxWithEvents instead. You have 2 ways to do this but i'll give you the easiest. Compile your code and go in the design window for the form. In your toolbox you should have the ListBoxWithEvents control now and you can simply drag and drop in your form and replace the one you have. Since it derive from the listbox all it has is extra features. It didn't lose anything it had before.
Now you need to change 2 things. In your form select the new ListBoxWithEvents control and go in the properties events and you will find the new event called ItemAdded you can double click that and it should create an event like the following. I have also thrown in a sample MessageBox that display all you will need.
private void ListBox1_ItemAdded(object sender, ListBoxItemEventArgs e)
{
MessageBox.Show("Item was added at index " + e.Index + " and the value is " + listBox1.Items[e.Index].ToString());
}
Finally in order to trigger that event you need to use the new method lst.AddItem(object); instead of lst.Items.Add(object); so according to your sample code you need to change this :
_form.lst_BarcodeScan.Invoke(new MethodInvoker(delegate
{
_form.lst_BarcodeScan.Items.Add(received + Environment.NewLine);
}));
to this :
_form.lst_BarcodeScan.Invoke(new MethodInvoker(delegate
{
_form.lst_BarcodeScan.AddItem(received + Environment.NewLine);
}));
Try it and now you should have that event fire every time something is added to the list.
Since you are pretty new to programming i find it important to mention that this event will trigger on the UI thread and not the thread you created. This mean it behave normally like clicking on a button triggers a button_click(object sender, EventArgs e) event. No special thread involved whatsoever.
I would monitor data received on a Serial port with my pc and a Arduino.
On the arduino, the sketch send thorugt the USB the string "aabb" evry 300ms.
With pc I want listen, and in real time print the string in a control (Textbox). To do that, I create a new thread which listen in a Loop what arrives in Serial port, and when it happens it write by a Invoke the string in textbox. The procedures works if I deploy in the form's class but if I use a external class it doesn't. To explain better the matter, I paste the code of the class
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
public TextBox textLog;
public string LastString;
public bool thrIsAlive;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[IODescriptionAttribute("ControlInvokeRequiredDescr")]
public bool InvokeRequired { get; private set; }
//DISPOSE
public void Dispose()
{
this.Dispose();
}
//SET Textobox LOG
public void SetLogTxtB (TextBox txt)
{
textLog = txt;
}
//PORTE DISPONIBILI
public string[] Available_Ports()
{
return SerialPort.GetPortNames();
}
//COSTRUTTORI
public SerialPortManager(string portname, int baudrate,bool InitializeConn)
{
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (InitializeConn == true) Serial.Open();
}
public SerialPortManager()
{
}
//SETTA I PARAMETRI E INIZIALIZZA LA CONNESSIONE
public void SetConnectionParam(string portname, int baudrate, bool initializeConn)
{
Serial.Close();
Serial.Dispose();
Serial = new SerialPort();
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (initializeConn == true) Serial.Open();
}
//ASYNC LISTENER
public void AsyncListener()
{
thrIsAlive = true;
thr = new Thread(ThreadReader);
thr.Start();
}
//PROCEDURA PER APPEND
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
textLog.Text += value;
}
private void Invoke(Action<string> action, params object[] v)
{
throw new NotImplementedException();
}
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
AppendTextBox(LastString + "\n");
}
}
}
In the form I write three rows
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB(textBox1);
PortMan.AsyncListener();
If I try to run program it returns the error " cross-thread operation not allowed". Now, while I posting this ask, I decide to do a last try and change the method AppendTextBox to :
public void AppendTextBox(string value)
{
if (textLog.InvokeRequired)
{
try
{
textLog.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textLog.Text += value;
}
And It Finally works. Now ascertained the power of Stackoverflow that solved the problem before posting, I would know why my code works. Thank you
In SerialPortManager you must use delegate instead windows control.
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
//public TextBox textLog;
public Action<string> textLog;
.....
Crete in you form simply method:
public void SetTextBoxText(string value)
{
if (textBox1.InvokeRequired)
{
try
{
textBox1.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textBox1.Text += value;
}
Set delegate to PortMan:
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB=new Action<string>(SetTextBoxText);
PortMan.AsyncListener();
If need output log to TextBox of PortMan call textLog delegate.
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
//AppendTextBox(LastString + "\n");
textLog(LastString + "\n");
}
}
Apart from that your Invoke method in SerialPortManager should throw NotImplementedException the problem is that you define your own InvokeRequired/Invoke.
You need to use these methods provided by a WinForms control such that it knows whether your code is running inside the thread (UI thread) that created the control and how it can change context to this thread.
Actually it seems you may use your SerialPortManager but make use of InvokeRequired/Invoke of textLog like you're already doing in AppendTextBox.
BTW, if (initializeConn == true) is rather useless - if (initializeConn) is sufficient.
Making my Bluetooth application I need access to some broadcast actions on the Android side of my code.
All this is run in my Core, so I have a ViewModel that will call through my interface
public interface IConnectionService
{
//Properties
string IntentName { get; }
//Events
event EventHandler<SearchConnectionItemEventArgs> SearchItemFoundEvent;
//Methods
void RunIntent();
void SearchConnection();
void Connect(string macAddress);
}
RunIntent prompts the user to turn on Bluetooth (Could be another technology) and I would then like to have an event trigger when Bluetooth state is changed
Android.Bluetooth.BluetoothAdapter.ActionStateChanged
And also when I search for new devices I need
Android.Bluetooth.BluetoothDevice.ActionFound
But I cant put the [Android.Runtime.Register("ACTION_FOUND")] and [Android.Runtime.Register("ACTION_STATE_CHANGED")] on my class, this only works if I try to put it on my View, but if I do that I need logic in my view?
Is it possible to put it in the core somehow?
My class implementing my interface
using System;
using Android.Bluetooth;
using Android.Content;
using Cirrious.MvvmCross.Android.Platform.Tasks;
using Test.Core.Interfaces;
using Test.Core.Models;
namespace Test.Core.Android
{
public class AndroidBluetooth : MvxAndroidTask, IConnectionService
{
#region Private Fields
private readonly BluetoothAdapter _bluetoothAdapter;
#endregion
#region Public Fields
#endregion
#region Properties
public string IntentName { get { return "Turn on Bluetooth"; }}
#endregion
#region Constructor
public AndroidBluetooth()
{
_bluetoothAdapter = BluetoothAdapter.DefaultAdapter;
}
#endregion
#region Events
public event EventHandler<SearchConnectionItemEventArgs> SearchItemFoundEvent;
private void ItemFound(SearchConnectionItemEventArgs e)
{
if(SearchItemFoundEvent != null)
{
SearchItemFoundEvent(this, e);
}
}
#endregion
#region Methods
public void RunIntent()
{
if (_bluetoothAdapter == null)
{
//No bluetooth support on phone
}
else if(!_bluetoothAdapter.IsEnabled)
{
var intent = new Intent(BluetoothAdapter.ActionRequestEnable);
StartActivity(intent);
}
}
public void SearchConnection()
{
if (_bluetoothAdapter == null)
{
//No bluetooth support on phone
}
else if (!_bluetoothAdapter.IsEnabled)
{
//Bluetooth not turned on
RunIntent();
}
else
{
FindBondedDevices();
}
}
private void FindBondedDevices()
{
var pairedDevices = _bluetoothAdapter.BondedDevices;
if (pairedDevices.Count <= 0) return;
foreach (var item in pairedDevices)
{
ItemFound(new SearchConnectionItemEventArgs {Name = item.Name, MacAddress = item.Address});
}
}
private void FindNewDevices()
{
if (_bluetoothAdapter == null)
{
}
else if (!_bluetoothAdapter.IsEnabled)
{
}
else
{
_bluetoothAdapter.StartDiscovery();
//Bind event for new devices
}
}
public void Connect(string macAddress)
{
}
#endregion
}
}
Disclaimer: I'm not familiar with this part of Android - I've never used the Bluetooth stack.
From your description it sounds like you already know the answer - these Attributes need to go on methods within the Activity/View.
Of course, once they've been added to the Activity/View then it is easy to route these method calls through to the ViewModel - just use the ViewModel property within the View.
It's probably easier to try to get this part of your working as a standalone sample first - and then work out how to make it cross-platform and/or mvvm.
Please can you help me with advice or demo code for the following task:
I had a program in WPF which constantly listen on a serial port, If it received a specific signal it should change a property in a ViewModel. The listener is start on another thread so I had wonder how can I change a ViewModel property from another thread, I try to pass a property by reference but that was not possible.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//My property in the view model
private Boolean _Lock;
public Boolean Lock
{
get { return _Lock; }
set
{
_Lock = value;
OnPropertyChanged("Lock");
}
}
//Start the listener thread
Thread ComListenThread = new Thread(delegate()
{
Com cm = new Com(Path,Lock);
cm.Start();
});
ComListenThread.Start();
class Com
{
private Uri web { get; set; }
private Boolean Lock { get; set; }
public Com(Uri Path,Boolean _Lock)
{
web = Path;
Lock = _Lock;
}
public void Start()
{
try
{
port = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
}
catch (Exception e)
{
MessageBox.Show("Reason {0}:", e.Message);
}
port.ReadTimeout = 500;
port.Open();
int position = 0;
while (true)
{
try
{
int len = port.Read(data, position, data.Length - position);
position += len;
}
catch (TimeoutException)
{
//How to change Lock property
Lock = (data[2]==5)?true:false;
position = 0;
}
}
}
}
So my question is how can I pass the property which will be changed on another thread in constant loop.
By passing the parent object you should have access to the property to change it; however, you may want to switch back to the UI thread (Dispatcher.Invoke) before doing this, as cross-threaded mutation of "observer" models rarely ends well.
Another approach is for your code to simply raise an event (nothing to do with this property), and have your UI code switch to the UI tread and update the view-model. This approach separates the UI code cleanly from the "doing" code (since the "doing" code knows nothing of the view-model or threading), and is particularly useful if you need to support arbitrary UI models.