当前位置: 首页 > news >正文

关闭模态窗口后,父窗口居然跑到了其他窗口的后面

显示一个模态窗口,正常而普遍的操作。然而却一直有一个难缠的 BUG:当关闭模态窗口时,父窗口有时会跑到其他程序窗口的后面!

而最近读到了微软工程师写过的话之后,明白了这个 BUG 的产生缘由以及解决方法。


这是什么 BUG?

弹出模态窗口

  1. 弹出一个模态窗口,然后将模态窗口的父窗口设置为自身窗口;
  2. 切换到其他程序窗口中(比如 Windows 资源管理器窗口);
  3. 切换回此模态窗口,然后关闭这个模态窗口上。

你会发现,模态窗口关闭后,父窗口并没有回到当前的顶层显示中。取而代之的,是其他程序的窗口(比如 Windows 资源管理器窗口)。
用一张图来描述这个 BUG,将是这样的:

有这两个窗口,其中右边那个是我们开发的:

两个窗口

我们的窗口在资源管理器上面。然后,我们弹出模态子窗口:

我们在上面

现在,我们操作一下资源管理器:

操作资源管理器

然后,回到模态子窗口中,把它关掉:

关掉模态子窗口

我们期待模态子窗口关掉后,它的父窗口会在顶层继续供我们操作,但实际上,Windows 资源管理器却成为了顶层,我们的程序“掉下去了”:

不符合预期的结果

解释和解决方法

在《Windows 进化启示录》书中,微软有说到:

当销毁模态对话框时,这个对话框刚好是拥有前台焦点的窗口。现在,窗口管理器需要找到其他的窗口并把前台焦点交给这个窗口。
窗口管理器会首先试着把前台焦点交给对话框的所有者窗口,但此时这个窗口却仍然是禁止的,因此窗口管理器将跳过所有者窗口,并继续查找没有被禁止的窗口。

这很明显是 Windows 的 BUG,然而让微软感到无奈的是,经常有程序喜欢依赖于微软的 BUG 进行开发,一旦微软修复了 BUG,那些依赖于 BUG 开发的程序将变得不正常!

为解决兼容性问题的微软工程师默哀一分钟……

我曾经尝试在模态子窗口关闭后激活一下父窗口,但这样会导致窗口的层级闪烁一下(Windows 资源管理器会短暂地显示到我们的窗口之上)。

而这本书作者推荐的方法是:

  1. 重新激活所有者窗口
  2. 销毁模态对话框

于是,我试着监听模态子窗口的 Closing 事件,在其中写下主窗口的激活调用,自此 BUG 才算解决。

public ChildModalWindow()
{
    Closing += (sender, e) => Owner?.Activate();
}

将这样的解决办法封装成附加属性给所有的模态子窗口,这样设置附加属性即可解决问题。或者统一模态子窗口的窗口样式,在样式中解决这个 BUG,这样,所有使用了此窗口样式的模态子窗口也将解决问题。


参考资料

  • 《伟大的产品 —— Windows 进化启示录》by 微软软件工程师 Raymond Chen

相关文章:

  • 语义耦合(Semantic Coupling)
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • 在 Windows 安装期间将 MBR 磁盘转换为 GPT 磁盘
  • 解决大于 4GB 的 Windows 10 镜像在 UEFI 模式下的安装问题
  • 为什么 UEFI 方式启动的 U 盘必须使用 FAT32 文件系统?
  • 不再为命名而苦恼!使用 MSTestEnhancer 单元测试扩展,写契约就够了
  • Windows 10 自带那么多图标,去哪里找呢?
  • 如何删除 Windows 10 系统生成的 WindowsApps 文件夹
  • 命令“xxx.exe xxx”已退出,代码为 n。这些错误是什么意思?
  • 将 async/await 异步代码转换为安全的不会死锁的同步代码
  • 屏幕上那个灰色带有数字的框是什么?看着好难受!
  • Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码
  • Roslyn 入门:使用 Visual Studio 的语法可视化窗格查看和了解代码的语法树
  • 利用 ReSharper 自定义代码中的错误模式,在代码审查之前就发现并修改错误
  • 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁
  • 2017-08-04 前端日报
  • es的写入过程
  • JAVA SE 6 GC调优笔记
  • Java到底能干嘛?
  • Java-详解HashMap
  • nfs客户端进程变D,延伸linux的lock
  • node.js
  • SpriteKit 技巧之添加背景图片
  • Vue ES6 Jade Scss Webpack Gulp
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • vue--为什么data属性必须是一个函数
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 记一次删除Git记录中的大文件的过程
  • 力扣(LeetCode)21
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 世界上最简单的无等待算法(getAndIncrement)
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • 容器镜像
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​如何防止网络攻击?
  • #define
  • #pragma multi_compile #pragma shader_feature
  • (libusb) usb口自动刷新
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET Micro Framework初体验
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NET的数据绑定
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • @RestController注解的使用
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [2019.2.28]BZOJ4033 [HAOI2015]树上染色