LocEngineer
LocEngineer

Reputation: 2917

Cross-thread update UI trouble

I am creating a plugin for a third-party software. the plugin consists of a ribbon button and a Winform. I have no trouble interacting with the SW from the ribbon. Trouble is with the WinForm because I need to start it on a separate thread. Why? To keep it top-most without blocking access to the UI below it.

I run the form (named SetCategory) like this:

var thread = new System.Threading.Thread(() => {
                        var sc= new SetCategory();
                        sc.ShowDialog();
});
thread.Start();

Then, triggered by a button on that "SetCategory" form, I need to access the software hosting the plugin and set a value. I try it like this:

SendOrPostCallback updateSegment = delegate(object state)
{
    theApplication.theProprietaryControl = myValue
};

System.Threading.SynchronizationContext.Current.Post(updateSegment, null);

Note: I have not included the entire application control shebang as it is irrelevant. If I start the Form on the UI thread and close it afterwards so the user can continue working in the application, everything works fine, so there is no problem with that action itself.

I have also tried using a MethodInvoker and BeginInvoke but the result is in both cases the same albeit a very weird one:

==> The action is performed, I can see my value being set - and yet I get an error saying:

This method/property must be called on the UI thread. Fix the problem by testing the InvokeRequired property on the control and call Invoke() or BeginInvoke() on the control instead of calling directly when InvokeRequired is true.

If I choose to Continue, I can actually continue, the value is set as if everything had worked normally.

I tried passing the ThreadId of the UI thread like this before running the form from the button:

var uid = Thread.CurrentThread.ManagedThreadId;
var thread = new System.Threading.Thread(() => {
                        var sc= new SetCategory();
                        sc.Controls["lblUiThreadId"].Text = uid.ToString();
                        sc.ShowDialog();
});
thread.Start();

Alas, I cannot use the ThreadID to make sure the action is performed on the correct thread, can I?

Thanks!

Upvotes: 0

Views: 258

Answers (2)

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

There should only be one thread that does all the user interface stuff.

You want to let a separate thread create and show the dialog box. Because: "I need to start it on a separate thread. Why? To keep it top-most without blocking access to the UI below it"

Are you familiar with the term modal and modeless dialog boxes?

  • Modal dialog boxes require the user to respond before continuing the program
  • Modeless dialog boxes stay on the screen and are available for use at any time but permit other user activities

MSDN about modal and modeless dialog boxes

It seems you need a modeless dialog box. You use the function ShowDialog to show the form. ShowDialog is used to show the form as a Modal Dialog Box. If you want to shot is as a Modeless dialog box use Form.Show()

The following would do what you want while keeping the main form responsive

private SetCategory formSetCategory = null;
private void Button1_clicked(object sender, ...)
{
    if (this.formSetCategory == null)
    {   // form not showing yet, create + show
        this.formSetCategory = new SetCategory();
        this.formSetCategory.ShowDialog(this);
    }
    // else: form already showing. Do nothing
}

Now both this form and the formSetCategory are responsive, they both use the same UI thread. Normally the formSetCategory has functionality to close itself (something like a Close or OK button), if the form is closed, you need to get notified, to make sure that you clear your reference to formSetCategory:

 private SetCategory formSetCategory = null;
private void Button1_clicked(object sender, ...)
{
    if (this.formSetCategory == null)
    {   // form not showing yet
        this.formSetCategory = new SetCategory();
        // subscribe to FormClosed event
        this.formSetCategory.FormClose += this.OnSetCategoryFormClosed;
        this.formSetCategory.ShowDialog(this);
    }
}
private void OnSetCategoryFormClosed(object sender, FormClosedEventArgs e)
{   // formSetCategory is closed.        
    this.formSetCategory.Dispose();
    this.formSetCategory = null;
}

Upvotes: 0

Radin Gospodinov
Radin Gospodinov

Reputation: 2323

You cannot update the UI thread from backgound thread. If you want to show the form on top of all forms set TopMost = true; then call Show() method.

Upvotes: 2

Related Questions