Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / threads

How To: Manage cross-thread WinForms controls access

4.40/5 (9 votes)
29 Jan 2014CPOL2 min read 46.4K   376  
Using Generics and Extension Methods to provide easy-to-use helper for cross thread operations over WinForms controls.

Introduction   

When developing rich business applications, developers often have to deal with manipulating data and UI on separate threads and so have to manage cross-thread controls access.

In WinForms, we do that by using callback methods and invoke them on the main thread.

For example, consider a heavy load worker that searches some files into the computer and sends back log information during the process. We should implement a method that handles the event:

C#
private void AppendLogText(string logText)
{
    if (tbLog.InvokeRequired)
    {
        AppendLogTextCallBack callBack = new AppendLogTextCallBack(AppendLogText);
        Invoke(callBack, new object[] { logText });
    }
    else
    {
        if (!string.IsNullOrEmpty(tbLog.Text))
            tbLog.AppendText(Environment.NewLine);
        tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
    }
}

This article will show how to implement methods to simplify cross thread controls access implementation.

Background

In this article, we use the TPL (Task Parallel Library) to run worker async. The TPL is a set of APIs included in the framework 4, please visit this link for more information: here.

We also use the class Action, that encapsulates a method. It's very useful in this case to encapsulate the callback methods without having to declare them as class fields. To learn more about this class, please visit this link: here.

As the worker is running async, the worker implements some events to send back information. 

First step: Creating a helper managing the invoke method

The basis of all guidelines is to not develop a process twice: when you have to manage cross threads controls access for multiple controls in your code, you should have the pattern implemented once.

To do so, we use a helper providing a method that manages control access and callback method firing:

C#
public static class ControlThreadingHelper
{
    public static void InvokeControlAction<t>(t control, Action action) where t : Control
    {
        if (control.InvokeRequired)
            control.Invoke(new Action<t, Action>(InvokeControlAction), new object[] { control, action });
        else
            action();
    }
}

This helper is fully reusable for all controls thanks to generics: it checks the InvokeRequired property and fires the callback method embedded in an Action. The class Action can be assigned by a lambda expression, so it's very simple to write code.  

Thanks to this helper, we can now manage all controls access, cross threads or not, by a unique way:

C#
private void AppendLogText(string logText)
{
    ControlThreadingHelper.InvokeControlAction(tbLog, () =>
    {
        if (!string.IsNullOrEmpty(tbLog.Text))
            tbLog.AppendText(Environment.NewLine);
        tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
    });
}

Second step: Simplifying code writing 

To have simplified code writing, we should implement the control access method directly in the class Control.

We can do this thanks to an extension method: 

C#
public static class ControlThreadingExtensions
{
    public static void Invoke<t>(this t control, Action action) where t : Control
    {
        ControlThreadingHelper.InvokeControlAction<t>(control, action);
    }
}

This extension method allows us to implement control access directly onto the controls of our WinForm:

C#
private void AppendLogText(string logText)
{
    tbLog.Invoke(() =>
    {
        if (!string.IsNullOrEmpty(tbLog.Text))
            tbLog.AppendText(Environment.NewLine);
        tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
    });
}

We have separated the concerns: cross threads accesses are managed by a unique method in the helper while specific form behaviour is assigned into the form behind code.

Points of interest  

In this article, we have seen how coupling the Action class, helpers, and extension methods have helpes us write methods that provide easy-to-use cross-threads controls access methods.

History 

  • 19/06/2012: Bugfix : use an empty Action instead of an Action<t> for callback method 
  • 19/06/2012: Original version. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)