How To Update A Controls Properties From Another Task In C#

Preamble :

In this blog, I am going to cover some code failures and two different ways you can reach a controls properties from within another task.

Getting started :

You would do something like this, but not quite this because this is bad code :

 public partial class Form1 : Form
 {
 public Form1()
 {
 InitializeComponent();
 }
 private void button2_Click(object sender, EventArgs e)
 {
 ControlAction action = new ControlAction();
 Task colorChange_Task = new Task(() => action.LabelBG_ColorChange(label1, Color.Red)); 
colorChange_Task.Start();
 }

 private void label1_BackColorChanged(object sender, EventArgs e)
 {
 Debug.Write("Color was changed");
 }
 }
 public class ControlAction
 {
 public Color LabelBG_ColorChange(Control cnl, Color clr)
 {
 if (cnl.BackColor == Color.Transparent)
 Debug.WriteLine("I am Transparant");
 else
 cnl.BackColor = clr;
 return clr;
 }
 }

In the else section. If you where to access the text property of that control, cnl.Text you would be met with an exception of type :

System.InvalidOperationException: ‘Cross-thread operation not valid: Control ‘label1′ accessed from a thread other than the thread it was created on.’

And I will explain why this exception happens later on in the post. By passing the control into the new task, you are able to update the color of the label at ease, and while this works, but it’s not technically correct. Let me show you an example and explain about how you should access controls from another thread. If you don’t use the control you passed into LabelBG_ColorChange, you would be forced to do it like this :

 public partial class Form1 : Form
 {
 public Form1()
 {
 InitializeComponent();

 }
 private void button2_Click(object sender, EventArgs e)
 {
 ControlAction action = new ControlAction();
 Task colorChange_Task = new Task(() => action.Color = action.LabelBG_ColorChange(label1, Color.Red)); colorChange_Task.Start();
 Debug.WriteLine($"Your color is : { action.Color }");
 }

 private void label1_BackColorChanged(object sender, EventArgs e)
 {
 Debug.Write(string.Concat("Color was changed", Environment.NewLine));
 }
 }
 public class ControlAction
 {
 public Color Color { get; set; } = Color.Transparent;
 public Color LabelBG_ColorChange(Control ctl, Color clr)
 {
 if (ctl.BackColor == Color.Black)
 Debug.WriteLine("I am already Black");
 else if (ctl.BackColor != clr)
 {
 Form.ActiveForm.Controls.Find("label1", false)[0].Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), clr, ctl);
 Color = clr;
 }
 return clr;
 }
 public delegate void Delegate_Callback_To(Color ctl_Color, Control control);

 public void UpdateUI_With_NewItem(Color ctl_Color, Control control)
 {
 control.BackColor = ctl_Color;
 }
 }

Notice on line 29, we are forced to search the active form for the control using the find method because we are not using the control we originally passed in to this method. And assuming the control is not null, (I should have added some null checks there ?) and we invoke the control we want to update if its found. We do this because our calling method : button2_Click set the executed method to run on another thread, other than the UI thread where the control was created and belongs to. And so, if we don’t invoke the control; we are accessing it incorrectly since it does not belong to the thread that is changing its properties. Line 29 above may also require your access modifier for your label be set to public.

Also note : Find(“label1”, false) assumes the control is on the main form and not on another container/control ie a panel or group box. And it will not search child controls while set to false. Changing false to true will allow the search pattern to find child controls for the name of the control you specified. This is worth noting if the control you want to find is a child control of another parent object.

Let me elaborate some more bad code which will cause the same error above.. This : Form.ActiveForm.Controls.Find(“label1″, false)[0].Text =””; is also bad code and that’s because the control is being accessed illegally in the same manner as above. So a better way to write line 29 more appropriately would look like this :

ctl.Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), clr, ctl);

The same malpractice applies if we try to access the control we passed into the method with the following snipped : ctl.Text = “”; as this will also result in an illegal cross thread operation.

On a more advisable note to the above approach. This example I’ve demonstrated to you is fine to use in Winforms, since winforms is old and tasteless. This is how some developers used to write code for Winforms.

They’d execute threads to carry out work, and from those threads, they would delegate back to their UI with any updates their threading method was required to carry back to the user interface. It is bad practice to use your UI to carry out work or to use your UI as a model for anything other than an interactive interface for the end-users experience.

Running any code on your UI thread, which takes a long time to execute may also lockup your UI until that work has finished. This is also a common reason to add multi-threading events to your projects. And if you are looking for multi-threading examples, you will find my blog has no shortage of them.

Since WPF was released, most modern developers use Model View Controllers, Model View Presentation, (These two are my favourite) or even the Model View View Model design. Windows forms is very old, and it’s also EOL which means you really shouldn’t be using it, unless in some legacy type applications where you need meet a specific target. WPF is where it’s at today, and Microsoft encourage users to use WPF over old and dated Winforms.

I would highly advise any readers to start learning Windows Presentation Foundation and all it has to offer, so that you can begin to learn modern day programming models and better programming practices. Doing so will allow you to build better, more stable and robust applications.

In the end; here is what you should end up using. This is the finished code with the icing on the top. 😉

 public partial class Form1 : Form
 {
 public Form1()
 {
 InitializeComponent();
 }
 private void button2_Click(object sender, EventArgs e)
 {
 ControlAction action = new ControlAction();
 Task colorChange_Task = new Task(() => action.Color = action.LabelBG_ColorChange(label1, Color.Red)); colorChange_Task.Start();
 Debug.WriteLine($"Your color is : { action.Color }");
 }
 private void label1_BackColorChanged(object sender, EventArgs e)
 {
 Debug.Write(string.Concat("Color was changed", Environment.NewLine));
 }
 }
 public class ControlAction
 {
 public Color Color { get; set; } = Color.Transparent;
 public Color LabelBG_ColorChange(Control ctl, Color clr)
 {
 if (ctl.BackColor == Color.Black)
 Debug.WriteLine("I am already Black");
 else if (ctl.BackColor != clr)
 {
 ctl.Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), clr, ctl);
 Color = clr;
 }
 return clr;
 }
 public delegate void Delegate_Callback_To(Color ctl_Color, Control control);

 public void UpdateUI_With_NewItem(Color ctl_Color, Control control)
 {
 control.BackColor = ctl_Color;
 }