Search code examples
c#winformsbackgroundworkerinvokeinvokerequired

Invoking a control from backgroundworker stops with no error


I'm running a BackgroundWorker which suppose to update my UserControl. I tried Invoking after checking InvokeRequired property:

private void bgwHighlightText_DoWork(object sender, DoWorkEventArgs e)
{
   tmpRich.SelectedRtf = myRtf;
   if (_ucResultRich.InvokeRequired && _ucResultRich.rchResult.InvokeRequired)
     _ucResultRich.Invoke(new Action(() => _ucResultRich.rchResult.Rtf = tmpRich.Rtf)); // Debug pointer stops here

   //Rest of the code
}

I also tried to invoke the RichTextBox inside my UserControl directly:

_ucResultRich.rchResult.Invoke(new Action(() => _ucResultRich.rchResult.Rtf = tmpRich.Rtf));

But when I debug the code, it just stops running the rest of the code with no error.

Both _ucResultRich.InvokeRequired and _ucResultRich.rchResult.InvokeRequired return true.

Am I doing something wrong here?

Update

I put the Invoke part in try catch and now I can get the following error from the exception message:

Cross-thread operation not valid: Control '' accessed from a thread 
      other than the thread it was created on.

Is it because it cant determine the control? Cause it shows it like Control ''.


Solution

  • You need to invoke a delegate not an Action when using other threads to update controls on the UI thread.

    You can use my general purpose method to achieve this:

    public delegate void SetControlPropertyDelegateHandler(Control control, string propertyName, object value);
    
    public static void SetControlProperty(Control control, string propertyName, object value)
    {
     if (control == null) 
         return;
     if (control.InvokeRequired)
     {
        SetControlPropertyDelegateHandler dlg = new SetControlPropertyDelegateHandler(SetControlProperty);
        control.Invoke(dlg, control, propertyName, value);
     }
     else
     {
        PropertyInfo property = control.GetType().GetProperty(propertyName);
        if (property != null)
            property.SetValue(control, value, null);
     }
    }
    

    And you can use this method like this:

    SetControlProperty(_ucResultRich.rchResult, "Rtf", tmpRich.Rtf); 
    

    Update

    You can use this method to call parameterless methods on the control:

    public static void CallMethodUsingInvoke(Control control, Action methodOnControl)
        {
            if (control == null)
                return;
            var dlg = new MethodInvoker(methodOnControl);
            control.Invoke(dlg, control, methodOnControl);
        }
    

    Example:

    CallMethodUsingInvoke(richTextBox1, () => _ucResultRich.rchResult.SelectAll());
    

    To call more complex methods you must create an appropriate delegate for the method you need to call.

    Update 2

    To get values from properties from other threads you can use this method:

    public delegate object GetControlPropertyValueDelegate(Control controlToModify, string propertyName);
    
    public static T GetControlPropertyValue<T>(Control controlToModify, string propertyName)
    {
        if (controlToModify.InvokeRequired)
        {
            var dlg = new GetControlPropertyValueDelegate(GetControlPropertyValue);
            return (T)controlToModify.Invoke(dlg, controlToModify, propertyName);
        }
        else
        {
            var prop = controlToModify.GetType().GetProperty(propertyName);
            if (prop != null)
            {
                return (T)Convert.ChangeType(prop.GetValue(controlToModify, null), typeof(T));
            }
        }
        return default (T);
     }
    

    Example:

    var textLength = GetControlPropertyValue<int>(_ucResultRich.rchResult, "Length");