0

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 ''.

Ghasem
  • 14,455
  • 21
  • 138
  • 171
  • 1
    The whole point of backgroundworker is easily creating 2 threads: one for the GUI and another one for something else. It includes all what is required to perfectly communicate between both threads, but you have to use it properly. The `DoWork` event starts the backgroundworker thread and, consequently, you cannot modify what belongs to the GUI thread (e.g., any control) from there. To perform any modification in the controls, you should either wait for the backgroundworker thread to finish (via `RunWorkerCompleted` event) or call `WorkerReportsProgress` (+ know how to use this)... – varocarbas Dec 08 '15 at 09:06
  • 1
    ... In summary: you should either learn to use backgroundworker properly or not use it at all (there are other multithreading approaches). What you are trying (`Invoke`) doesn't make any sense: you are mixing up different approaches (and not maximising the main reason to use the backgroundworker, the simplicity of its in-built functionalities) what usually drives to errors which shouldn't ever happen (= the ones derived of not using the given variable properly). – varocarbas Dec 08 '15 at 09:14
  • You get a good diagnostic, the control with the problem does in fact not have a name. It is `tmpRich`. No matter what you do you'll get this exception, _ucResultRich is owned by your UI thread and tmpRich is owned by your worker thread. That happened when you assigned its Rtf property. You'll have to rethink this, very high odds of course that you'll discover that there is no point at all anymore to using a worker thread because all the real work *must* be done by the UI thread. You can tinker and assign _ucResultRich.Rtf in the RunWorkerCompleted event handler with e.Result. – Hans Passant Dec 08 '15 at 09:14
  • @HansPassant The reason is I'm showing an animated `gif` image inside a `PictureBox` which will freeze if I do all the work on the main threat. That's why I'm using backgroundworker. – Ghasem Dec 08 '15 at 09:19
  • 1
    Are you trying to justify doing things wrong?! As said: you were using the backgroundworker wrongly; and your attempt to solve the error (+ the proposed solution) doesn't make too much sense. The backgroundworker has in-built events to allow the comunication between both threads, you should use them properly (= understand what `DoWork` means/implies). If you don't use the in-built functionalities of the backgroundworker what is the point of using it at all?! This is a very limited multi-threading approach (= just 2 threads). – varocarbas Dec 08 '15 at 09:24
  • @varocarbas The reason is there's no point using `ProgressChanged` or `RunWorkerCompleted`. cause they cause freeze the UI threat in my case. – Ghasem Dec 08 '15 at 09:29
  • 1
    What are you saying!??? If you don't know about something, why don't you plainly ask rather than saying things which do not making any sense?! The whole point of the backgroundworker is precisely avoiding the GUI to freeze. These events allow a proper communication between the GUI and the backgroundworker thread. In other multithreading approach, you would have to create all this by your own; the backgroundwer has everything in-built just to be used right away. – varocarbas Dec 08 '15 at 09:30
  • @varocarbas I'm really aware of this. I've used it many times for things like updating a progressbar. But it doesn't work on a PictureBox with an animated gif picture – Ghasem Dec 08 '15 at 09:32
  • 1
    Seriously?! So you have found a bug in the multithreading approach of the backgroundworker? It doesn't work with a picturebox? Don't you think that perhaps you are using it wrongly? Additionally, if you think that, the solution would be easy: don't use the backgroundworker; use a different multithreading approach. Anyway... do whatever you want. I have written clear enough comments for future readers to understand this situation properly. – varocarbas Dec 08 '15 at 09:33
  • The human eye is *very* sensitive to changes in motion. An evolutionary trait, it stopped your great, great ... father from being eaten by a lion in the tall savanna grass. Any work done by your UI thread that takes more than ~20 milliseconds is noticeable. Just assigning that Rtf property can be enough to interrupt the animation. That same trait is also the reason that most users really dislike gifs, it is too distracting from the pretty static work they have to do to enter data. Make both your life and the user's life more enjoyable by removing that gif. – Hans Passant Dec 08 '15 at 09:42
  • I'm joining to all that @varocarbas said. If you use `BackgroundWorker`, the do it properly - no `Invoke` is needed, you **must not** access the UI from `DoWork`. If you want manually accessing the UI from another thread via `Invoke/BeginInvoke`, then use a plain `Thread`. – Ivan Stoev Dec 08 '15 at 10:08
  • @varocarbas How about this question. Can you help me out? http://stackoverflow.com/questions/34153497/how-to-call-a-control-method-from-another-thread – Ghasem Dec 08 '15 at 10:30
  • @AlexJolig you didn't get my point: I do think that for just 2 threads (GUI + anything else), backgroundworker does deliver the best approach; but you have to use it properly. I will take a look at your other question (although please don't do this kind of things in the future; I am not a per-request helper :)). – varocarbas Dec 08 '15 at 10:38
  • @AlexJolig I have written an answer with a descriptive-enough sample code showing how to use the backgroundworker properly to perform the highlighting actions you want. Please don't expect anything like this in the future. – varocarbas Dec 08 '15 at 11:25

1 Answers1

1

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");
Mihail Stancescu
  • 4,088
  • 1
  • 16
  • 21
  • Just a side note: I would recommend you to additionally write a PropertyNameResolver (Expression to String) to get rid of that hardcoded property string parameter – Jannik Dec 08 '15 at 08:47
  • That's a cool solution. But can you call something like `_ucResultRich.rchResult.SelectAll()` using this as well? – Ghasem Dec 08 '15 at 08:52
  • @AlexJolig Seriously are you finding problems by using something against its purpose (and without even understanding how it works)?! Is that weird? – varocarbas Dec 08 '15 at 09:28
  • @varocarbas Cause I really have no idea how else I can resolve my problem. – Ghasem Dec 08 '15 at 09:30