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

什么是模态窗口?本文带你了解模态窗口的本质

做 Windows 桌面应用开发的小伙伴们对“模态窗口”(Modal Dialog)一定不陌生。如果你希望在模态窗口之上做更多的事情,或者自己实现一套模态窗口类似的机制,那么你可能需要了解模态窗口的本质。

本文不会太深,只是从模态窗口一词出发,抵达大家都熟知的一些知识为止。


本文内容

    • 开发中的模态窗口
    • 模态窗口的三个特点
    • 实现模态窗口
    • API 调用
      • 禁用主窗口
      • 阻塞代码等待操作完成
      • 进行 UI 强提醒

开发中的模态窗口

在各种系统、语言和框架中,只要有用户可以看见的界面,都存在模态窗口的概念。从交互层面来说,它的形式是在保留主界面作为环境来显示的情况下,打开一个新的界面,用户只能在新的界面上操作,完成或取消后才能返回主界面。从作用上来说,通常是要求用户必须提供某些信息后才能继续操作,或者单纯只是为了广告。

模态窗口的三个特点

如果你希望自己搞一套模态窗口出来,那么只需要满足这三点即可。你可以随便加戏但那都无关紧要。

  1. 保留主界面显示的同时,禁用主界面的用户交互;
  2. 显示子界面,主界面在子界面操作完成后返回;
  3. 当用户试图跳过子界面的交互的时候进行强提醒。

拿 Windows 系统中的模态对话框为例子,大概就像下面这两张图片这样:

有一个小的子界面盖住了主界面,要求用户必须进行选择。Windows 系统设置因为让背景变暗了,所以用户肯定会看得到需要进行的交互;而任务管理器没有让主界面变暗,所以用户在操作子界面的时候,模态窗口的边框和标题栏闪烁以提醒用户注意。

Windows 系统设置

任务管理器

实现模态窗口

对于 Windows 操作系统来说,模态窗口并不是一个单一的概念,你并不能仅通过一个 API 调用就完成显示模态窗口,你需要在不同的时机调用不同的 API 来完成一个模态窗口。如果要完整实现一个自己的模态窗口,则需要编写实现以上三个特点的代码。

当然,你可能会发现实际上你显示一个模态窗口仅仅一句话调用就够了,那是因为你所用的应用程序框架帮你完成了模态窗口的一系列机制。

关于 WPF 框架是如何实现模态窗口的,可以阅读:直击本质:WPF 框架是如何实现模态窗口的

关于如何自己实现一个跨越线程/进程边界的模态窗口,可以阅读:实现 Windows 系统上跨进程/跨线程的模态窗口

如果你希望定制以上第三个特点中强提醒的动画效果,可以阅读:WPF window 子窗口反馈效果(抖动/阴影渐变) - 唐宋元明清2188 - 博客园。

API 调用

为了在 Windows 上实现模态窗口,需要一些 Win32 API 调用(当然,框架够用的话直接用框架就好)。

禁用主窗口

我们需要使用到 BOOL EnableWindow(HWND hWnd, BOOL bEnable); 来启用与禁用某个窗口。

EnableWindow(hWnd, false);
try
{
    // 模态显示一个窗口。
}
finally
{
    EnableWindow(hWnd, true);
}
[DllImport("user32")]
private static extern bool EnableWindow(IntPtr hwnd, bool bEnable);

阻塞代码等待操作完成

因为 async/await 的出现,阻塞其实可以使用 await 来实现。虽然这不是真正的阻塞,但可以真实反应出“异步”这个过程,也就是虽然这里在等待,但实际上依然能够继续在同一个线程响应用户的操作。

UWP 中的新 API 当然已经都是使用 async/await 来实现模态等待了,不过 WPF/Windows Forms 比较早,只能使用 Dispatcher 线程模型来实现模态等待。

于是我们可以考虑直接使用现成的 Dispatcher 线程模型来完成等待,方法是调用下面两个当中的任何一个:

  • Window.ShowDialog 也就是直接使用窗口原生的模态
  • Dispatcher.PushFrame 新开一个消息循环以阻塞当前代码的同时继续响应 UI 交互

上面 Window.ShowDialog 的本质也是在调用 Dispatcher.PushFrame,详见:

  • 直击本质:WPF 框架是如何实现模态窗口的

关于 PushFrame 新开消息循环阻塞的原理可以参考:

  • 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分) - walterlv

当然,还有其他可以新开消息循环的方法。

进行 UI 强提醒

由于我们一开始禁用了主窗口,所以如果用户试图操作主窗口是不会有效果的。然而如果用户不知道当前显示了一个模态窗口需要操作,那么给出提醒也是必要的。

简单的在 UI 上的提醒是最简单的了,比如:

  • 将主界面变暗(UWP 应用,Web 应用喜欢这么做)
  • 将主界面变模糊(iOS 应用喜欢这么做)
  • 在模态窗口上增加一个很厚重的阴影(Android 应用喜欢这么做)

然而 Windows 和 Mac OS 这些古老的系统由于兼容性负担不能随便那么改,于是需要有其他的提醒方式。

Windows 采用的方式是让标题栏闪烁,让阴影闪烁。

而这些特效的处理,来自于子窗口需要处理一些特定的消息 WM_SETCURSOR

详见:WPF window 子窗口反馈效果(抖动/阴影渐变) - 唐宋元明清2188 - 博客园

通常你不需要手工处理这些消息,但是如果你完全定制了窗口样式,则可能需要自行做一个这样的模态窗口提醒效果。


我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 使用 .editorconfig 配置 .NET/C# 项目的代码分析规则的严重程度
  • 如何在 .NET 项目中开启不安全代码(以便启用 unsafe fixed 等关键字)
  • WPF 高性能位图渲染 WriteableBitmap 及其高性能用法示例
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • 使用 Direct3D11 的 OpenSharedResource 方法渲染来自其他进程/设备的共享资源(SharedHandle)
  • 将 Direct3D11 在 GPU 中的纹理(Texture2D)导出到内存(Map)或导出成图片文件
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • C#/.NET 当我们在写事件 += 和 -= 的时候,方法是如何转换成事件处理器的
  • 清理 git 仓库太繁琐?试试 bfg!删除敏感信息删除大文件一句命令搞定(比官方文档还详细的使用说明)
  • 可集成到文件管理器,一句 PowerShell 脚本发布某个版本的所有 NuGet 包
  • Windows 系统的默认字体是什么?应用的默认字体是什么?
  • C# 8.0 的可空引用类型,不止是加个问号哦!你还有很多种不同的可空玩法
  • 一个简单的方法:截取子类名称中不包含基类后缀的部分
  • 使用 MSBuild Target 复制文件的时候如何保持文件夹结构不变
  • 如何在 MSBuild 中正确使用 % 来引用每一个项(Item)中的元数据
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 【5+】跨webview多页面 触发事件(二)
  • Cookie 在前端中的实践
  • iOS小技巧之UIImagePickerController实现头像选择
  • JS变量作用域
  • js操作时间(持续更新)
  • Node + FFmpeg 实现Canvas动画导出视频
  • node入门
  • OSS Web直传 (文件图片)
  • pdf文件如何在线转换为jpg图片
  • Transformer-XL: Unleashing the Potential of Attention Models
  • ubuntu 下nginx安装 并支持https协议
  • VUE es6技巧写法(持续更新中~~~)
  • 对JS继承的一点思考
  • 高性能JavaScript阅读简记(三)
  • 基于webpack 的 vue 多页架构
  • 入口文件开始,分析Vue源码实现
  • 新书推荐|Windows黑客编程技术详解
  • 运行时添加log4j2的appender
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • ​虚拟化系列介绍(十)
  • ###C语言程序设计-----C语言学习(6)#
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (二)hibernate配置管理
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • .NET Project Open Day(2011.11.13)
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .net连接MySQL的方法
  • .Net小白的大学四年,内含面经
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • /etc/motd and /etc/issue