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

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

想知道你在 WPF 编写 Window.ShowDialog() 之后,WPF 框架是如何帮你实现模态窗口的吗?

本文就带你来了解这一些。


本文内容

    • `Window.ShowDialog`
    • `ShowHelper`

Window.ShowDialog

WPF 显示模态窗口的方法就是 Window.ShowDialog,因此我们直接进入这个方法查看。由于 .NET Core 版本的 WPF 已经开源,我们会使用 .NET Core 版本的 WPF 源代码。

Window.ShowDialog 的源代码可以在这里查看:

  • Window.cs

这个方法非常长,所以我只把其中与模态窗口最关键的代码和相关注释留下,其他都删除(这当然是不可编译的):

public Nullable<bool> ShowDialog()
{
    // NOTE:
    // _threadWindowHandles is created here.  This reference is nulled out in EnableThreadWindows
    // when it is called with a true parameter.  Please do not null it out anywhere else.
    // EnableThreadWindow(true) is called when dialog is going away.  Once dialog is closed and
    // thread windows have been enabled, then there no need to keep the array list around.
    // Please see BUG 929740 before making any changes to how _threadWindowHandles works.
    _threadWindowHandles = new ArrayList();
    //Get visible and enabled windows in the thread
    // If the callback function returns true for all windows in the thread, the return value is true.
    // If the callback function returns false on any enumerated window, or if there are no windows
    // found in the thread, the return value is false.
    // No need for use to actually check the return value.
    UnsafeNativeMethods.EnumThreadWindows(SafeNativeMethods.GetCurrentThreadId(),
                                            new NativeMethods.EnumThreadWindowsCallback(ThreadWindowsCallback),
                                            NativeMethods.NullHandleRef);
    //disable those windows
    EnableThreadWindows(false);

    try
    {
        _showingAsDialog = true;
        Show();
    }
    catch
    {
        // NOTE:
        // See BUG 929740.
        // _threadWindowHandles is created before calling ShowDialog and is deleted in
        // EnableThreadWindows (when it's called with true).
        //
        // Window dlg = new Window();
        // Button b = new button();
        // b.OnClick += new ClickHandler(OnClick);
        // dlg.ShowDialog();
        //
        //
        // void OnClick(...)
        // {
        //      dlg.Close();
        //      throw new Exception();
        // }
        //
        //
        // If above code is written, then we get inside this exception handler only after the dialog
        // is closed.  In that case all the windows that we disabled before showing the dialog have already
        // been enabled and _threadWindowHandles set to null in EnableThreadWindows.  Thus, we don't
        // need to do it again.
        //
        // In any other exception cases, we get in this handler before Dialog is closed and thus we do
        // need to enable all the disable windows.
        if (_threadWindowHandles != null)
        {
            // Some exception case. Re-enable the windows that were disabled
            EnableThreadWindows(true);
        }
    }
}

觉得代码还是太长?不要紧,我再简化一下:

  1. EnumThreadWindows 获取当前线程的所有窗口
  2. 把当前线程中的所有窗口都禁用掉(用的是 Win32 API 的禁用哦,这不会导致窗口内控件的样式变为禁用状态)
  3. 将窗口显示出来(如果出现异常,则还原之前禁用的窗口)

可以注意到禁用掉的窗口是“当前线程”的哦。

ShowHelper

接下来的重点方法是 Window.ShowDialog 中的那句 Show()。在 Show() 之前设置了 _showingAsDialogtrue,于是这里会调用 ShowHelper 方法并传入 true

下面的代码也是精简后的 ShowHelper 方法:

private object ShowHelper(object booleanBox)
{
    try
    {
        // tell users we're going modal
        ComponentDispatcher.PushModal();

        _dispatcherFrame = new DispatcherFrame();
        Dispatcher.PushFrame(_dispatcherFrame);
    }
    finally
    {
        // tell users we're going non-modal
        ComponentDispatcher.PopModal();
    }
}

可以看到,重点是 PushModalPopModal 以及 PushFrame

PushFrame 的效果就是让调用 ShowDialog 的代码看起来就像阻塞了一样(实际上就是阻塞了,只不过开了新的消息循环看起来 UI 不卡)。

关于 PushFrame 为什么能够“阻塞”你的代码的同时还能继续响应 UI 操作的原理,可以阅读:

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

那么 ComponentDispatcher.PushModalComponentDispatcher.PopModal 呢?可以在这里(ComponentDispatcherThread.cs)看它的代码,实际上是为了模态计数以及引发事件的,对模态的效果没有本质上的影响。


我的博客会首发于 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 复制文件的时候如何保持文件夹结构不变
  • 77. Combinations
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Java 网络编程(2):UDP 的使用
  • Javascript编码规范
  • JavaScript实现分页效果
  • js操作时间(持续更新)
  • Otto开发初探——微服务依赖管理新利器
  • SQLServer插入数据
  • Vue 重置组件到初始状态
  • vuex 学习笔记 01
  • 基于游标的分页接口实现
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 如何学习JavaEE,项目又该如何做?
  • 通过几道题目学习二叉搜索树
  • 新版博客前端前瞻
  • 一起参Ember.js讨论、问答社区。
  • 鱼骨图 - 如何绘制?
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​马来语翻译中文去哪比较好?
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (C语言)逆序输出字符串
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (一)appium-desktop定位元素原理
  • . NET自动找可写目录
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .net framework profiles /.net framework 配置
  • .NET 事件模型教程(二)
  • /proc/vmstat 详解
  • @Builder用法
  • @media screen 针对不同移动设备
  • @property python知乎_Python3基础之:property
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [AX]AX2012 AIF(四):文档服务应用实例
  • [BUG]vscode插件live server无法自动打开浏览器
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子