窗体的InvokeRequired == false而包含的控件的InvokeRequired == true

7

怎么可能呢?我有一个基于System.Windows.Forms.Form派生的Windows窗体控件,其中包含WebBrowser控件。Webbrowser对象实例在窗体的构造函数中创建(在InitializeComponent()方法中)。然后,在后台线程中操作WebBrowser的内容,我发现在某些情况下Form.InvokeRequired == false,而WebBrowser.InvokeRequired == true。这是怎么回事呢?


它是在启动或关闭表单时发生还是一直存在? - Albin Sunnanbo
当表单已经被创建但尚未显示(整个表单,而不仅仅是浏览器)时会发生这种情况。我不会在创建后立即显示表单。 - Dmitrii Lobanov
3个回答

9

Form.InvokeRequired 在窗体显示之前返回 false

我进行了简单的测试:

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();

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

输出结果为:

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

还要注意的是,这在MSDN上有一定的文档记录:

如果控件的句柄尚不存在,则 InvokeRequired 沿着控件的父级链搜索,直到找到具有窗口句柄的控件或窗体。如果找不到适当的句柄,则 InvokeRequired 方法返回 false。

这意味着如果不需要调用 Invoke(调用发生在同一线程上)或者控件在不同的线程上创建但是控件的句柄尚未创建,则 InvokeRequired 可以返回 false。

如果控件的句柄尚未创建,则不能简单地调用控件的属性、方法或事件。这可能会导致控件的句柄在后台线程上被创建,将控件隔离到没有消息泵的线程上,并使应用程序不稳定。

您可以通过在后台线程上返回 false 时还检查 IsHandleCreated 的值来防止此情况。如果控件句柄尚未创建,则必须等待它被创建后才能调用 Invoke 或 BeginInvoke。通常,这仅在应用程序的主窗体(如 Application.Run(new MainForm()))的构造函数中创建后台线程之前(在显示窗体或调用 Application.Run 之前)发生。

您的解决方案还要检查 IsHandleCreated 的值。

编辑:
Handle 可以在 WebBrowser 控件内部或外部的任何时候创建。这不会自动创建父窗体的句柄。

我创建了一个示例:

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);
}

输出结果如下:

按钮1:假,当前状态:假
按钮1:真,当前状态:假


但是我检查了包含的控件(WebBrowser)的InvokeRequired属性,发现它为True,而form.InvokeRequired为False。根据逻辑,如果子控件在同一个线程中创建,那么子控件在层次结构中的所有父控件的InvokeRequired属性应该与父控件的相同。在我的情况下,WebBrowser控件是在窗体的构造函数中创建的,也就是说它们是在同一个线程中创建的。也许WebBrowser控件有一些特定的行为? - Dmitrii Lobanov
1
@Dmitry:不是的。控件的句柄可以独立于父窗体的句柄创建。我已经在我的答案中更新了一个示例。 - Albin Sunnanbo
+1 非常感谢。我花了很长时间尝试模拟跨线程异常,以获取失败的单元测试场景。这是创建它的关键。现在能够编写显示控件被安全访问的测试了 :) - Jim Counts

1

0

我一直在调查这个奇怪的行为。 我需要从不同的线程操作一些控件(例如显示连接到主机的设备信息或根据不同设备状态触发操作)。

这个链接给了我一个很好的提示: http://csharpfeeds.com/post/2898/Control.Trifecta_InvokeRequired_IsHandleCreated_and_IsDisposed.aspx

我仍然不知道微软人员打算如何使用他们自己的东西(在许多方面都不同意),但是,在一个应用程序中,我不得不使用以下肮脏和恶心的解决方法:

  • 在主线程中创建控件/窗体(确保它是主线程)。
  • 在同一个过程中,检查控件句柄。这个简单的检查将强制它被创建并且在正确的线程中!

多么丑陋啊! 我想知道是否有其他人有更好的方法来做到这一点。

_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;

(抱歉在这个有点旧的帖子中发帖,但我认为这可能对某些人有用)。


请注意,在您的示例中,句柄实际上将由检查_my_control.Handle == IntPtr.Zero创建。更短的方法可能是_myControl = new ControlClass { Owner = this }; var temp = _myControl.Handle; - g t

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接