views:

272

answers:

7

Hi,

I encounter a problem with a Windows Forms application.

A form must be displayed from another thread. So in the form class, I have the following code:

private delegate void DisplayDialogCallback();

public void DisplayDialog()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DisplayDialogCallback(DisplayDialog));
    }
    else
    {
        this.ShowDialog();
    }
}

Now, every time I run this, an InvalidOperationException is thrown on the line this.ShowDialog();:

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

What's wrong with this piece of code? Isn't it a valid way to make cross-thread calls? Is there something special with ShowDialog()?

+7  A: 

You're probably executing this code before the form has been shown.
Therefore, InvokeRequired is returning false.

SLaks
+4  A: 

I believe what is happening here is that this code is being run before the Form is ever shown.

When a Form is created in .Net it does not immediately gain affinity for a particular thread. Only when certain operations are performed like showing it or grabbing the handle does it gain affinity. Before that happens it's hard for InvokeRequired to function correctly.

In this particular case no affinity is established and no parent control exists so InvokeRequired returns false since it can't determine the original thread.

The way to fix this is to establish affinity for your control when it's created on the UI thread. The best way to do this is just to ask the control for it's handle property.

var notUsed = control.Handle;
JaredPar
Strange, but getting `control.Handle` doesn't change anything: the exception is still thrown. Instead, Lorenzo suggestion works well. IMHO, this is caused by the fact that the compiler just removes `var notUsed = control.Handle;` line, while `notUsed` variable is... not used.
MainMa
A: 

You could always try testing against a different control.

For example, you could access Application.Forms collections

public Control GetControlToInvokeAgainst()
{
    if(Application.Forms.Count > 0)
    {
        return Application.Forms[0];
    }
    return null;
}

Then in your DisplayDialog() method, call the GetControlToInvokeAgainst() and test for null before trying to perform an invokerequired call.

Ben Cawley
A: 

Most likely the handle of the control is not created yet, in which case Control.InvokeRequired returns false.

Check the Control.IsHandleCreated property to see if this is the case.

Willem van Rumpt
A: 

I also think SLaks is correct. From msdn (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx):

If no appropriate handle can be found, the InvokeRequired method returns false.

If its possible in your case, I would try to combine the creating and showing the control in a single method, i.e.:

public DisplayDialog static Show()
{
  var result = new DisplayDialog; //possibly cache instance of the dialog if needed, but this could be tricky
  result.ShowDialog(); 
  return result;
}

you can call Show from a different thread

Grzenio
+2  A: 

Try this one:

private delegate void DisplayDialogCallback();

public void DisplayDialog()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DisplayDialogCallback(DisplayDialog));
    }
    else
    {
        if (this.Handle != (IntPtr)0) // you can also use: this.IsHandleCreated
        {
            this.ShowDialog();

            if (this.CanFocus)
            {
                this.Focus();
            }
        }
        else
        {
            // Handle the error
        }
    }
}

Please note that InvokeRequired returns

true if the control's Handle was created on a different thread than the calling thread (indicating that you must make calls to the control through an invoke method); otherwise, false.

and therefore, if the control has not been created, the return value will be false!

Lorenzo
+1  A: 

You are likely getting to this code before the form has been shown and therefore the window handle has not been created.

You can add this code before your code and all should be good:

if (! this.IsHandleCreated)
   this.CreateHandle();

Edit: There's another problem with your code. Once the form is displayed, you cannot call ShowDialog() again. You will get an invalid operation exception. You may want to modify this method as others have proposed.

You might be better served calling the ShowDialog() directly from the calling class and have another method for BringToFront() or something like that...

Scott P