C#父子窗体往返场景下Close()失效但Hide()运行正常问题咨询
根因分析
你的问题本质是Winform的消息驱动机制和ShowDialog()的阻塞特性共同导致的:
Close()方法不是同步执行的UI操作:调用Close()时,系统只是往UI线程的消息队列里插入了一条WM_CLOSE消息,不会立刻完成窗体的关闭、隐藏渲染,必须等当前正在执行的事件处理函数完全结束,UI线程回到消息循环调度阶段,才会处理WM_CLOSE消息,完成Child1的关闭动作。ShowDialog()是同步阻塞方法:你在调用Close()之后立刻执行了_callingForm.SelectSchedule(_sname),这个方法内部调用了callSelectDoWs.ShowDialog(),会直接阻塞当前UI线程,直到Child2被关闭才会返回。在阻塞期间,UI线程不会处理消息队列里的任何消息,自然也不会处理之前插入的WM_CLOSE消息,所以Child1会一直显示到Child2关闭为止。
而Hide()是同步方法,调用后会立刻修改窗体的Visible属性为false、完成隐藏渲染,不需要等消息队列调度,所以效果符合预期。
解决方法
方法1:调整执行时序,Child1完全关闭后再执行后续逻辑(最推荐)
不需要用回调接口传递参数,给Child1增加公开属性存储选中的计划名,父窗体等ShowDialog()返回后再执行后续逻辑,此时Child1已经完全关闭:
// Child1 新增属性 public string SelectedScheduleName { get; private set; } // Child1 保存按钮逻辑修改 private void SaveBTN_Click(Object sender, EventArgs e) { SelectedScheduleName = _sname; this.DialogResult = DialogResult.OK; // 设置DialogResult会自动触发Close } // 父窗体调用Child1的逻辑修改 private void ChangeActvLBLasBTN_Click(Object sender, EventArgs e) { SetActvSchedule callSetActvSched = new SetActvSchedule(this); callSetActvSched.StartPosition = FormStartPosition.Manual; callSetActvSched.Location = new Point(this.Left + 45, this.Top + 25); // 等Child1关闭后再执行后续逻辑 if (callSetActvSched.ShowDialog() == DialogResult.OK) { SelectSchedule(callSetActvSched.SelectedScheduleName); } }
方法2:延后回调执行时机
如果要保留现有回调结构,可以用BeginInvoke把回调逻辑丢到消息队列末尾,等Child1关闭完成后再执行:
// Child1 保存按钮逻辑修改 private void SaveBTN_Click(Object sender, EventArgs e) { this.Close(); // BeginInvoke会等当前事件处理完成、消息队列空闲时再执行回调,此时Child1已经关闭 _callingForm.BeginInvoke(new Action(() => { _callingForm.SelectSchedule(_sname); })); }
排查思路
- 注释掉
SelectSchedule方法中调用ShowDialog()弹出Child2的代码,验证Child1是否可以在点击按钮后立刻关闭,确认是否是Child2的阻塞导致的问题。 - 在
Close()调用前后加断点,观察Child1的Visible、IsDisposed属性变化,确认Close()调用后UI属性未立刻更新。 - 可以用Spy++工具监听窗体消息时序,确认
WM_CLOSE消息的实际处理时间点是否在Child2关闭之后。
内容的提问来源于stack exchange,提问作者Art Hansen
相关产品推荐
相关产品推荐

