WinForm 窗体句柄什么时候创建,事件按什么顺序
2022 年 4 月 5 日 · 2 分钟读完
项目里实例化一个窗体后马上对它调用 Invoke,报错「在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke」。做个测试看看句柄到底什么时候创建。
测试一:new 之后
两个基本窗体,在 Form1 的 Load 事件里:
private void Form1_Load(object sender, EventArgs e)
{
var form = new Form2();
Console.WriteLine("form 窗体句柄是否被创建:" + form.IsHandleCreated);
}

输出 False:窗体类实例化之后,句柄并没有创建。
测试二:Show 之后
private void Form1_Load(object sender, EventArgs e)
{
var form = new Form2();
Console.WriteLine("form 窗体句柄是否被创建:" + form.IsHandleCreated);
form.Show();
}
再给 Form2 的各个事件都加上打印:

红线以上是 Show 之前,句柄都没创建;一进入 Show() 就立刻创建了。窗体显示时事件的大致顺序:
| 顺序 | 事件 | 说明 |
|---|---|---|
| 1 | HandleCreated |
创建窗体句柄 |
| 2 | BindingContextChanged |
数据绑定上下文改变 |
| 3 | Load |
窗体加载 |
| 4 | VisibleChanged |
可见性改变 |
| 5 | Activated |
成为活动窗体 |
| 6 | Shown |
第一次显示完成 |
测试三:Application.Run
程序入口里的 Application.Run(form) 会在当前线程启动消息循环,并让窗体可见:

顺序和 Show 大致相同。
结论
窗体通过 Show()、ShowDialog() 或 Application.Run() 显示时才获得句柄,句柄创建是显示流程的第一步。在这之前对它 Invoke 就会报错。
延伸
- 访问
Handle属性会强制创建句柄:var h = form.Handle;之后IsHandleCreated就是 true 了,而且是在当前线程上创建的。所以如果在后台线程里 new 窗体并访问了 Handle,这个窗体就属于那个后台线程,之后会出各种奇怪的问题。窗体一定要在界面线程上创建。 - 安全的跨线程更新:先判断再调用,句柄还没有时就不要 Invoke:
if (form.IsHandleCreated && !form.IsDisposed)
form.BeginInvoke(new Action(() => form.Text = "更新"));
- 初始化代码放哪里:只和数据有关的放构造函数;要用到控件尺寸、句柄的放
Load;需要窗体已经显示出来(比如弹提示框、开始耗时加载)的放Shown,这样用户先看到窗体。 - 关闭时的顺序:
FormClosing(可以e.Cancel = true取消)→FormClosed→Deactivate→HandleDestroyed→Disposed。停止后台线程、断开 PLC 连接适合放在FormClosing。 - 为什么要 Invoke:所有控件只能在创建它的线程上访问,这个线程跑着消息循环。
Invoke就是把委托作为一条消息投递过去,所以需要句柄才能投递。见 WinForm 加载界面卡死。
原文首发于 blog.csdn.net