← 全部文章 WinForm

WinForm 窗体句柄什么时候创建,事件按什么顺序

2022 年 4 月 5 日 · 2 分钟读完

目录
  1. 测试一:new 之后
  2. 测试二:Show 之后
  3. 测试三:Application.Run
  4. 结论
  5. 延伸

项目里实例化一个窗体后马上对它调用 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 加载界面卡死。

# WinForm # 多线程 # 界面

原文首发于 blog.csdn.net