InvokeRequired of Form == false and InvokeRequired of contained control == true
Asked Answered
N

3

7

how is it possible? I have windows Form control, derived from System.Windows.Forms.Form with WebBrowser control contained in this form. Webbrowser object instance is created in constructor of form (in InitializeComponent() method). Then in background thread I manipulate with content of WebBrowser, and I found that in some cases Form.InvokeRequired == false, while WebBrowser.InvokeRequired == true. How can it be?

Nicolas answered 25/10, 2010 at 11:7 Comment(2)
Does it occur during startup or closing of the form or all the time?Fetterlock
It occurs when the form already has been created, but not shown (entire form, not only browser). I don't show the form right after creation.Nicolas
F
9

Form.InvokeRequired returns false before the form is shown.

I did a simple test:

Form2 f2 = new Form2();
Thread t = new Thread(new ThreadStart(() => PrintInvokeRequired(f2)));
t.Start();
t.Join();

f2.Show();

t = new Thread(new ThreadStart(() => PrintInvokeRequired(f2)));
t.Start();
t.Join();

with the helper

private void PrintInvokeRequired(Form form)
{
    Console.WriteLine("IsHandleCreated: " + form.IsHandleCreated + ", InvokeRequired: " + form.InvokeRequired);
}

the output is

IsHandleCreated: False, InvokeRequired: False
IsHandleCreated: True, InvokeRequired: True

Also note that this is somewhat documented on MSDN:

If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false.

This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.

In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.

You can protect against this case by also checking the value of IsHandleCreated when InvokeRequired returns false on a background thread. If the control handle has not yet been created, you must wait until it has been created before calling Invoke or BeginInvoke. Typically, this happens only if a background thread is created in the constructor of the primary form for the application (as in Application.Run(new MainForm()), before the form has been shown or Application.Run has been called.

Your solution is to also check for IsHandleCreated.

Edit:
The Handle can be created at any time internal in the WebBrowser control or externally. This does not automatically create the handle of the parent form.

I created an example:

public Form2()
{
    InitializeComponent();

    Button button1 = new Button();
    this.Controls.Add(button1);

    Console.WriteLine("button1: " + button1.IsHandleCreated + " this: " + this.IsHandleCreated);
    var tmp = button1.Handle; // Forces the Handle to be created.
    Console.WriteLine("button1: " + button1.IsHandleCreated + " this: " + this.IsHandleCreated);
}

with the output:

button1: False this: False
button1: True this: False

Fetterlock answered 25/10, 2010 at 12:23 Comment(3)
But I check contained control (WebBrowser) InvokeRequired property, and it is True while form.InvokeRequired is False. According to logic InvokeRequired of all child controls in hierarchy must be the same as the InvokeRequired of parent control if child controls were created in the same thread as parent control. And in my situation webbrowser control is created in form's constructor i.e. they were created in one thread. Perhaps we have some specific behaviour of WebBrowser control?Nicolas
@Dmitry: No. The Handle of a control can be created independently of the Handle of the parent form. I have updated my answer with an example.Fetterlock
+1 Thanks so much. I spent a long time trying to simulate the cross thread exception to get a failing unit test scenario. This was key to creating it at will. Now able to write tests that show controls are accessed safely :)Scissile
N
1

Here's detailed investigation of corresponding and more generic problem: http://www.ikriv.com/en/prog/info/dotnet/MysteriousHang.html

Nicolas answered 25/10, 2010 at 19:53 Comment(0)
T
0

I've been investigating this same weird behaviour. I need to operate some controls from different threads (e.g. show information about a device that is connected to the host or trigger actions depending on different devices states).

This link gave me a good hint: http://csharpfeeds.com/post/2898/Control.Trifecta_InvokeRequired_IsHandleCreated_and_IsDisposed.aspx

I still don't know how MS people intended to make use of their own stuff (and don't agree in many aspects), but, in one application I had to make the following dirty and filthy workaround:

  • Create the control/form in the main thread (make sure it is the main thread).
  • In the same procedure, check the control Handle. This simple check will force it to be created and in the right thread!

How ugly, isn't? I'd like to know if anyone else has a better way to do it.

_my_control = new ControlClass( );
_my_control.Owner = this;

IntPtr hnd;

// Force Handle creation by reading it.
if ( !_my_control.IsHandleCreated || _my_control.Handle == IntPtr.Zero )
    hnd = _my_control.Handle;

(Sorry for posting in this somewhat old post, but I just thought it could be usefull to someone).

Tubulure answered 17/2, 2011 at 10:28 Comment(1)
Note that in your example, the handle will actually be created by the check _my_control.Handle == IntPtr.Zero. A shorter method might be _myControl = new ControlClass { Owner = this }; var temp = _myControl.Handle;Analgesic

© 2022 - 2024 — McMap. All rights reserved.