views:

40

answers:

2

I'm coding a WinForm component where I start a Task to do the actual processing and trap the exception on a continuation. From there I want to show the exception message on a UI element.

Task myTask = Task.Factory.StartNew (() => SomeMethod(someArgs));
myTask.ContinueWith (antecedant => uiTextBox.Text = antecedant.Exception.Message,
                     TaskContinuationOptions.OnlyOnFaulted);

Now I get a cross-thread exception because the task is trying to update a UI element from a, obviously, non UI thread.

However, there is no Invoke or BeginInvoke defined in the Component class.

How to proceed from here?


UPDATE

Also, please note that Invoke/BeginInvoke/InvokeRequired are not available from my Component-derived class since Component doesn't provide them.

+1  A: 

You can use delegates to do this.

    delegate void UpdateStatusDelegate (string value);


    void UpdateStatus(string value)
    {
        if (InvokeRequired)
        {
            // We're not in the UI thread, so we need to call BeginInvoke
            BeginInvoke(new UpdateStatusDelegate(UpdateStatus), new object[]{value});
            return;
        }
        // Must be on the UI thread if we've got this far
        statusIndicator.Text = value;
    }
jimplode
So, creating a delegate and invoking it will use the UI thread?
Stecy
should do, not tested this code, but this is how I do in standard winforms
jimplode
Unfortunately, this will not work as the InvokeRequired method is not available inside a Component.
Stecy
You typically do not need to create a delegate; use `Action<T>` instead.
Fredrik Mörk
Could you create an event on the component and then subscribe to it from further up?
jimplode
Tried it and same error.
Stecy
`BeginInvoke(new MethodInvoker(() => UpdateStatus(value)))` should work, probably
Sam
@Sam BeginInvoke is not available under Component class and derived.
Stecy
@Stecy but the textbox does
Sam
@Sam You're right. I've successfully used this solution before using the one from Hans which, in my opinion, is optimal since it may happen that I am not using a textbox but some other component later on.
Stecy
+1  A: 

You could just add a property to your component, allows the client to set a form reference that you can use to call its BeginInvoke() method.

That can be done automatically as well, preferable so nobody can forget. It requires a bit of design time magic that's fairly impenetrable. I didn't come up with this by myself, I got it from the ErrorProvider component. Trusted source and all that. Paste this into your component source code:

using System.Windows.Forms;
using System.ComponentModel.Design;
...
    [Browsable(false)]
    public Form ParentForm { get; set; }

    public override ISite Site {
        set {
            // Runs at design time, ensures designer initializes ParentForm
            base.Site = value;
            if (value != null) {
                IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
                if (service != null) this.ParentForm = service.RootComponent as Form;
            }
        }
    }

The designer automatically sets the ParentForm property when the user drops your component on a form. Use ParentForm.BeginInvoke().

Hans Passant
Brilliant! It works perfectly! Thank you very much!
Stecy