Reputation: 499
In WPF, one could use something like:
Application.Current.Dispatcher.BeginInvoke(new Action(() => Form1.grid.Items.Refresh()));
to access UI functions outside of the main thread. In Winforms however, the same functionality isn't there. What would be the easiest way to access a BindingList that exists inside of my Form1 class from my "working" thread? Currently I getting the following error when trying to access "Form1.record_list":
System.InvalidOperationException: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
edit: I appreciate the help so far, but I'm lost on "this.Invoke". My method in the separate thread has no "Invoke".
Here's an example of my code so far.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
Form1.record_list.Add(record); //This is where I assume the problem spawns
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public partial class Form1 : Form
{
public static BindingList<Record> record_list = new BindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
Thread thread = new Thread(SCMClient.connect);
thread.IsBackground = true;
thread.Start();
FillData();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
foreach (DataGridViewRow row in dataGridView.Rows)
{
for (var i = 0; i < row.Cells.Count; i++)
{
Console.WriteLine(row.Cells[i].Value);
if (row.Cells[i].Value as string == "OK")
{
row.Cells[i].Style.BackColor = Color.Red;
Console.WriteLine("IF WAS TRUE");
}
}
}
}
I think the specific problem here is when I add Records to the Forms1.record_list. I'm not sure how to add items to that list without causing the Cross-thread error...
Upvotes: 2
Views: 8139
Reputation: 136
You MUST access your UI from your UI Thread only. But you can use the Control.Invoke
method - a Form
IS a Control
- to ensure your code is run from the UI Thread.
Also, you have a static BindingList
, but I assume you want to display the contents of that list in an specific form. You should make the BindingList
an instance member instead... or get a reference to a valid Form. The Control.Invoke
method is not static.
There are several ways to do so. I would do it like so:
First, create a method in your Form class that adds the record to the list.
public void AddRecord(Record r) {
if(this.InvokeRequired) {
this.Invoke(new MethodInvoker(() => this.AddRecord(r)));
} else {
this.record_list.Add(r);
}
}
Second, you need to have a reference to the form (in the next step, that is the theForm
variable).
Then, in your listener method, invoke AddRecord
method instead of adding the record in your BindingList
directly.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
theForm.AddRecord(record); // You need a Form instance.
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
Upvotes: 7
Reputation: 1371
The below will work out in winforms. Have tried once. The below code helps to update the label in UI thread from another thread.
string _string = "Call from another thread";
this.Invoke((MethodInvoker)delegate {
label1.Text = _string;
});
Upvotes: 1
Reputation: 888107
You're looking for SynchronizationContext.Current
, which works with both WinForms and WPF.
Note that you'll need to grab its value from the UI thread, since it's per-thread.
Upvotes: 0