MrDysprosium
MrDysprosium

Reputation: 499

Accessing UI from outside thread in Winforms

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

Answers (3)

Augusto Ruiz
Augusto Ruiz

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

Uthistran Selvaraj
Uthistran Selvaraj

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

SLaks
SLaks

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

Related Questions