Access control element from static thread function - c#

I ve read lots of information about this stuff and i tried this:
class Server
{
...
public Server(int Port, ListBox ex_lb, PictureBox ex_pb)
{
ServerWork = new Thread(() => ServerFunction(Listener, ex_lb, ex_pb));
ServerWork.Start();
}
static void ServerFunction(TcpListener ex_listener, ListBox ex_lb, PictureBox ex_pb)
{
//and any access to ex_lb throws exception, didnt debug to access to ex_pb
}
}
this:
private static IncomingDataClass g_IDC = new IncomingDataClass();
private class IncomingDataClass
{
static string data = "";
public string Data
{
get { return data; }
set {
data = value;
SomeEvent(this,null,data);
}
}
}
void IncomingDataClass_SomeEvent(object sender, EventArgs e, string ex_data)
{
if (ex_data.Contains("listbox"))
{
ex_data = ex_data.Remove(ex_data.IndexOf("listbox"), "listbox".Length);
listBox1.Items.Add(ex_data);
}
}
delegate void MyEventHandler(object sender, EventArgs e, string ex_data);
static event MyEventHandler SomeEvent;
// in form load event
SomeEvent += IncomingDataClass_SomeEvent;
class Server
{
...
public Server(int Port, ListBox ex_lb, PictureBox ex_pb)
{
ServerWork = new Thread(() => ServerFunction(Listener));
ServerWork.Start();
}
static void ServerFunction(TcpListener ex_listener)
{
//and any change of g_IDC.Data throws exception here
}
}
this:
private static ListBox listBox1 = new ListBox();
private void Form1_Load(object sender, EventArgs e)
{
...
listBox1.FormattingEnabled = true;
listBox1.Location = new System.Drawing.Point(12, 256);
listBox1.Name = "listBox1";
listBox1.Size = new System.Drawing.Size(258, 108);
listBox1.TabIndex = 6;
Controls.Add(listBox1);
}
//anyways, even if i create new ListBox lb = listBox1 in ServerFunction(..), it throws System.InvalidOperationException => Access attempt to listBox1 not from thread where is was created.
What do i do wrong? I thought that creating static control is an ultimate problem solve for this, but even this doesnt work...

If I remember correctly in order to update the UI from another thread you need to use this invocation:
this.Invoke((MethodInvoker)delegate
{
MethodForUpdatingUI();
});
This will start MethodForUpdatingUI() from the UI thread, enabling you to access the controls.
So, for use this in your code I'll try to change IncomingDataClass_SomeEvent:
void IncomingDataClass_SomeEvent(object sender, EventArgs e, string ex_data)
{
this.Invoke((MethodInvoker)delegate
{
UpdateListBox(ex_data);
});
}
UpdateListBox(string ex_data)
{
if (ex_data.Contains("listbox"))
{
ex_data = ex_data.Remove(ex_data.IndexOf("listbox"), "listbox".Length);
listBox1.Items.Add(ex_data);
}
}
Please feel free to correct me if this is not the case you are asking for or this is a bad pratice. I've not tested the code.

You can only manipulate UI controls from UI thread. It doesn't matter if those controls are static or not, afaik. Use Dispatcher object or SynchronizationContext object to modify controls from different thread.

Related

Logic issue on reading ALL data from the from the listbox that is coming from thread in c#

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.

Multiple class instances raising same event

I'm having trouble figuring out what is wrong with my C# code.
I'm trying to learn how to use ConcurrentQueue class in System.Collections.Concurrent namespace.
In order to do this, I'm creating 2 instances of the same class in different threads, passing to the constructors a different Listbox control.
I am expecting each class instance of EventGenerator to raise events at random intervals, updating the Listbox the were passed with randomly generated number, and adding that number to a ConcurrentQueue which is also passed to the constructor.
In my main thread, is the method to DeQueue the ConcurrentQueue of objects EnQueued to it by both spawned threads.
But what I'm getting is the 2 EnQueue Listboxes displaying the same data and the DeQueue Listbox seeming reporting to have deQueued them both.
I apologize if my description is not good enough, and my code follows, along with a link to an image of my form in case it might better help visualize what I'm trying to do...
Form
public partial class Form1 : Form
{
ConcurrentQueue<int> CQ;
EventGenerator eventGenerator1;
EventGenerator eventGenerator2;
public Form1()
{
InitializeComponent();
CQ = new ConcurrentQueue<int>();
eventGenerator1 = new EventGenerator(CQ, listBox1);
eventGenerator1.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
eventGenerator2 = new EventGenerator(CQ, listBox2);
eventGenerator2.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
}
private void RandomEvent(object sender, IncomingConnectionEventArgs e)
{
string s = e.Property_Int.ToString()
+ " "
+ e.Property_String;
UpdateListbox(s, e.LB);
}
private void UpdateListbox(string argstring, ListBox argListBox)
{
if (InvokeRequired)
{
Invoke(new Action<string, ListBox>(UpdateListbox), new object[] { argstring, argListBox });
return;
}
int n;
bool b = false;
//do
//{
b = CQ.TryDequeue(out n);
//} while (!b);
argListBox.Items.Add(argstring);
argListBox.SelectedIndex = argListBox.Items.Count -1;
listBoxDeQueue.Items.Add(n.ToString());
listBoxDeQueue.SelectedIndex = listBoxDeQueue.Items.Count - 1;
}
private void button_Start_Click(object sender, EventArgs e)
{
Thread methodThread1 = new Thread(new ThreadStart(TheThread1));
methodThread1.Start();
Thread methodThread2 = new Thread(new ThreadStart(TheThread2));
methodThread2.Start();
}
private void TheThread2()
{
eventGenerator2.Start();
}
private void TheThread1()
{
eventGenerator1.Start();
}
private void button_Stop_Click(object sender, EventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
}
IncomingConnectionEventArgs
class IncomingConnectionEventArgs : EventArgs
{
public System.Windows.Forms.ListBox LB;
public int Property_Int { get; set; }
public string Property_String { get; set; }
public IncomingConnectionEventArgs(int argInt, string argString,
System.Windows.Forms.ListBox lb)
{
LB = lb;
Property_Int = argInt;
Property_String = argString;
}
}
EventGenerator
class EventGenerator
{
public delegate void RandomEventHandler(
object sender,
IncomingConnectionEventArgs e);
public event RandomEventHandler OnRandomEvent;
public Random r = new Random();
public ListBox listBox;
public bool Generate = true;
public ConcurrentQueue<int> CQ;
public EventGenerator(ConcurrentQueue<int> argCQ, ListBox argListBox)
{
CQ = argCQ;
listBox = argListBox;
}
public void Start()
{
Generate = true;
while (Generate)
{
Thread.Sleep(r.Next(100, 2000));
RandomEvent();
}
}
public void Stop()
{
Generate = false; ;
}
public void RandomEvent()
{
if (OnRandomEvent == null)
{
return;
}
int n = r.Next(1000, 10000);
CQ.Enqueue(n);
IncomingConnectionEventArgs Args =
new IncomingConnectionEventArgs(n, "", listBox);
OnRandomEvent(this, Args);
}
}
The problem is with your use of Random. Unless you use a single instance of Random or explicitly seed each instance differently, the two threads calling Random.Next(...) will typically generate the same values.
A better way to seed your instance is this:
Random r = new Random(Guid.NewGuid().GetHashCode());

C# Form Controls Won't Update with Multithreading

I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.
I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:
Here's the 'main' program:
class Program
{
static MyForm form;
static void Main(string[] args)
{
form = new MyForm();
form.Show();
doWork();
form.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
while (form.Value < 100000)
{
form.ChangeVal();
Thread.Sleep(1);
}
return;
}
}
...And here's the Form. I'm not including the auto-generated stuff from VS.
public partial class MyForm : Form
{
private int val;
public int Value
{
get { return val; }
set { val = value; }
}
public Thread GUIupdater;
public MyForm()
{
InitializeComponent();
this.Refresh();
}
private void MyForm_Load(object sender, EventArgs e)
{
GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
GUIupdater.Start();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
}
public void ChangeVal()
{
val++;
}
private void changeLabel(string s)
{
label.Text = s;
label.Refresh();
}
private delegate void labelChanger(string s);
private void GUIupdaterThread()
{
while (true)
{
Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
Thread.Sleep(100); //100 ms
}
}
private void killThreadOnClose(object sender, FormClosingEventArgs e)
{
GUIupdater.Abort();
}
}
So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!
Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here
Edits:
When I add Console.WriteLine Statements, I discovered that the GUIupdaterThread (the thing that's meant to update the GUI) loop always 'breaks' on the Invoke statement, never reaching 'Thread.Sleep'. I changed it to 'BeginInvoke', which causes the loop to function properly, but this hasn't changed the fact that the GUI doesn't update.
CLARIFICATIONS:
About my 'actual' project:
The main thread here in 'Program' simulates my software, which is a plugin implementing an interface. My decision to alter val / value in that thread, not in the thread created by the window, was deliberate.
I'm constrained to using .NET 4.0 . any more recent features can't help me
Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.
There are several approaches how to update controls from other threads.
I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).
I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.
Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:
This form has a single label on it.
public partial class ProgressForm : Form
{
private int currentValue = 0;
public ProgressForm()
{
InitializeComponent();
}
public void ChangeValue(int newValue)
{
currentValue = newValue;
lblValue.Text = string.Format("Current value: {0}", currentValue);
lblValue.Refresh(); //Call Refresh to make the label update itself
}
}
static class Program
{
private static ProgressForm progressForm = null;
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
progressForm = new ProgressForm();
progressForm.Show();
doWork();
progressForm.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
int i = 0;
while (i < 100000)
{
progressForm.ChangeValue(i);
Thread.Sleep(1);
i++;
}
return;
}
}
You may use the following instead as you are trying to access UI control other than main thread (from which it is created).
while ( true )
{
Invoke ( ( Action ) (() =>
{
label.Text = val.ToString();
label.Refresh()
Application.DoEvents();
}));
Thread.Sleep( 100 );
}
I recommend you to use "backgroundworker".
First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.
private void btnDoIt_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Foo();
}
int total = 0;
private void Foo()
{
for (int i = 0; i <= 100000; i++)
{
total += i;
this.Text = i.ToString();
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run next process
}

Changing label in GUI from thread in another class

I have a label in GUI in my main Form. From the Form when I press a button, a new Thread is created with a parameter, some things happen and in the end I want to update the label saying it is done. But I am getting a NullRefferenceException. How can I update it? I basically used the same code in a different project, I just did not start the Thread with a parameter. Here is my code:
GUI_logic.cs:
private void button_upload_Click(object sender, EventArgs e) {
UploadFile upload = new UploadFile();
t_upload = new Thread(() => upload.startUpload(file));
t_upload.Start();
}
public static GUI_logic _GUI_l;
delegate void updateLabelStatusCallback(string text);
public void updateLabelStatus(string message) {
if (this.label_status.InvokeRequired) {
updateLabelStatusCallback d = new updateLabelStatusCallback(updateLabelStatus);
this.Invoke(d, new object[] { message });
} else {
this.label_status.Text = message;
}
}
UploadFile.cs:
public void startUpload(OpenFileDialog file) {
string ext = Path.GetExtension(file.FileName);
switch (ext) {
case ".xml":
parseXMLFile(file.FileName);
break;
}
}
private void parseXMLFile(string file) {
I do stuff here
...
...
//And now I want to update the label
GUI_logic._GUI_l.updateLabelStatus("Done");
}
Ok found the answer. Like the comments suggested, I did not instantiate the variable so I added _GUI_l=this in a code
public GUI_logic() {
_GUI_l = this;
InitializeComponent();
}

Can't control Threads from different Methods WPF C#

I'm writing a small Keylogger for some statistics about my typing.
The Keylogger works fine, but now i want to implement it to a wpf to have a better control.
public MainWindow()
{
InitializeComponent();
Thread ThreadLog = new Thread(Log);
Thread ThreadRefreshForm = new Thread(refreshForm);
Thread ThreadAutoSave = new Thread(AutoSave);
ThreadLog.Start();
ThreadRefreshForm.Start();
ThreadAutoSave.Start();
}
private void btn_ThreadLogStop_Click(object sender, RoutedEventArgs e)
{
if (ThreadLog.IsAlive == true)
{
ThreadLog.Abort();
}
This gives me an Error # ThreadLog.IsAlive. How can i solve the Problem?
Thanks for your help!!!!
You should declare your ThreadLog somewhere else and initialize it in the constructor, such that the method can access the ThreadLog:
private Thread ThreadLog;
public MainWindow()
{
InitializeComponent();
ThreadLog = new Thread(Log);
...
}
private void btn_ThreadLogStop_Click(object sender, RoutedEventArgs e)
{
if (ThreadLog.IsAlive == true)
{
ThreadLog.Abort();
}
Generally speaking the correct way how to end threads is like this
private volatile bool m_Stop;
public void ThreadLoop()
{
while(!m_Stop) {
// do some work
}
}
// starting
new Thread(ThreadLoop).Start();
// "force" end
m_Stop = true;
Or if you prefer tasks over threads (which I do):
public void ThreadLoop(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
// do some work
}
}
var cancelation = new CancellationTokenSource();
// starting
new Task(() => ThreadLoop(cancelation.Token), cancelation.Token).Start();
// "force" end
cancelation.Cancel();

Categories