非模态化对话框(使模态对话框变为非模态对话框)

非模态化对话框(使模态对话框变为非模态对话框)

我在 Windows 10 上,对许多内置应用程序(例如 Windows 资源管理器的选项对话框)和第三方应用程序(例如Eclipse 的运行配置对话框)。

例子

打开 Windows 资源管理器 ( Win+ E),转到选项卡看法,然后点击选项。选项对话框打开。只要选项对话框保持打开状态,您就无法与资源管理器窗口(您从中打开对话框)进行任何交互。甚至禁止移动或调整窗口大小。

我不明白这种行为有什么原因。打开选项对话框后,我仍然可以打开另一个 Explorer 窗口(在同一个进程中运行)并与该窗口交互。我认为 Explorer 完全能够允许与打开选项对话框的窗口进行交互。

提到的第三方应用程序也是如此。

问题

是否有一个工具*可以使模态窗口变为非模态窗口?
* 您可以替换术语“工具”通过黑客、补丁或者任何你能想到的方式。

理想情况下,该工具将适用于系统上的所有窗口。但是,也欢迎以下部分解决方案:

  • 用于禁用特定 UI 库(例如 Windows Forms 或 Java Swing)中的模态的工具
  • 用于禁用特定应用程序(例如 Windows 资源管理器)中的模态的工具
  • 允许移动和调整背景窗口(打开模式对话框的窗口)大小的工具,但不允许以任何其他方式与之交互。这是某些 Linux 窗口管理器(例如 Cinnamon)的默认设置。

当被禁止的交互以不可预见的方式改变应用程序的状态时(例如,在模态对话框中编辑其属性时在后台窗口中删除某个项目),受影响的应用程序崩溃是正常的。


⚠️以下部分对我的问题并不重要,但对您的答案可能很重要。如果您要写“那是不可能的”之类的话,我很高兴您能先阅读它们并仔细思考一下。

然而,我必须恳求你:请不要开始讨论实施细节。我不太想自己实现这个。这就是为什么我在 superuser.com 而不是 stackoverflow.com 上提问的原因。


类似问题及解决方案

我确信您可以拦截创建模态对话框的调用;毕竟,类似问题也有解决方案:

  • 任何不可调整大小的对话框都可以使用以下方法调整大小调整大小启用
    有趣(或悲伤?)的事实:ResizeEnable 上次更新是在 2003 年,但仍可在 Windows 10 上正常运行。(Windows XP 于 2001 年发布,但从 ResizeEnable 的更改日志来看,作者似乎在上次发布时没有使用 XP。)

  • 许多全屏应用程序可以在窗口中运行,使用窗口

技术细节

我非常清楚,模式对话框可以返回一个值,并且应用程序无法在没有该返回值的情况下继续运行。但是,至少在理论上必须有一个解决方法:

如果 UI 库提供了一种使用带返回值的模态对话框的方法,那么该库还必须实现一种方法来暂停当前线程并在另一个线程中显示模态对话框,以便可以与模态对话框进行交互。这甚至适用于单线程 UI(这似乎是标准)。例如,在 Java Swing 中,事件调度线程 (ETD) 提供了方法createSecondaryLoop()Swing 本身使用它来实现具有返回值的模式对话框。通常这些方法还会将 UI 控制权移至新线程,这样背景窗口就不会被冻结。这甚至适用于 Windows 资源管理器,如此屏幕截图(打开模态对话框时,视频在后台播放):

我们也可以使用该功能。一般方法是:

  • 拦截创建模态对话框的调用
  • 使用 UI 库自身的功能暂停当前线程并将 UI 控制权移至新线程
  • 创建并显示等效的非模态对话框
  • 当对话框关闭时,检索返回值的数据
  • 使用 UI 库自身的功能恢复原始 UI 线程
  • 从非模态对话框返回数据

答案1

哈里麦克指出,AutoHotKey 可能能够取消窗口模式化,这让我这个问题包含以下 AHK 脚本(带有一个小语法错误,已在以下版本中修复):

^e::
MouseGetPos,,, WindowUnderMouse
WinSet, Style, -0x8000000, ahk_id %WindowUnderMouse%
return

安装 AutoHotKey 后,将此脚本保存在 中file.ahk,双击文件(或右键单击并选择Run Script)。如果您有一个模式对话框并想与其父/背景窗口交互,请将鼠标悬停在父/背景窗口上并按CtrlE(这就是^e::)所代表的;背景/父窗口现在应该再次对交互做出反应,而无需关闭模式对话框。

“风格”有据可查这里.-0x8000000记录为

WS_DISABLED| 0x8000000| +/-Disabled。创建一个最初处于禁用状态的窗口。

答案2

模态对话框有两个主要用途:

  1. 获取用户参数。
    此处的示例是文本编辑器的要编辑文件的打开对话框。

  2. 继续之前出现警告/问题。
    一个著名的例子是 Windows 更新对话框请求重新启动 Windows 的权限。

我明白您是在寻求自动回答/单击特定对话框的方法,因为我没有看到通用答案。例如,使用单击每个对话框的“确定”按钮的工具肯定会导致灾难。

可能存在针对特定对话的工具,即通过标题和/或发起流程来识别的对话。您可以尝试现有工具,也可以使用以下产品轻松构建自己的工具: 自动热键 一起Spy++检查给定对话框的元素。

对于第一种情况,你也许可以使用以下工具 最后通行证 用记住的值填充对话框的字段。

对于第二种情况,AutoHotkey 脚本是最好的工具。您也可以查看 Microsoft 2008 文章 启用默认回复其中详细说明了一个注册表项,该项导致每个函数被拦截 MessageBox并自动选择默认按钮(我不知道它在 Windows 10 中是否仍然有效并且也没有动力去尝试)。

我想不出任何其他有用的案例。

请注意,我上面的分析是为了让程序以正常方式运行,而不是强行将部分或所有模式对话框转换为无模式对话框。这是因为在我创建的所有程序中,我无法想象一旦模式对话框没有阻止其原始线程,任何程序都会继续正常运行。

答案3

模式对话框

模式对话框应为具有窗口菜单、标题栏和粗边框的弹出窗口;也就是说,对话框模板应指定 WS_POPUP、WS_SYSMENU、WS_CAPTION 和 DS_MODALFRAME 样式。尽管应用程序可以指定 WS_VISIBLE 样式,但无论对话框模板是否指定 WS_VISIBLE 样式,系统始终都会显示模式对话框。应用程序不得创建具有 WS_CHILD 样式的模式对话框。具有此样式的模式对话框会禁用自身,从而阻止任何后续输入到达应用程序。

DialogBox应用程序使用或函数创建模态对话框 DialogBoxIndirectDialogBox需要包含对话框模板的资源的名称或标识符; DialogBoxIndirect需要包含对话框模板的内存对象的句柄。DialogBoxParamDialogBoxIndirectParam 函数也创建模态对话框;它们与前面提到的函数相同,但在创建对话框时将指定的参数传递给对话框过程。

当创建模式对话框时,系统使它成为活动窗口。该对话框保持活动状态,直到对话框过程调用 EndDialog 函数或系统激活另一个应用程序中的窗口。在模式对话框被销毁之前,用户和应用程序都不能使所有者窗口处于活动状态。

如果所有者窗口尚未被禁用,系统会在创建模式对话框时自动禁用该窗口及其所有子窗口。所有者窗口将保持禁用状态,直到对话框被销毁。尽管对话框过程可能随时启用所有者窗口,但启用所有者窗口违背了模式对话框的目的,因此不建议这样做。当对话框过程被销毁时,系统会再次启用所有者窗口,但前提是模式对话框导致所有者被禁用。

当系统创建模式对话框时,它会向当前正在捕获鼠标输入的窗口(如果有)发送 WM_CANCELMODE 消息。收到此消息的应用程序应释放鼠标捕获,以便用户可以在模式对话框中移动鼠标。由于系统禁用了所有者窗口,因此如果所有者在收到此消息后未能释放鼠标,则所有鼠标输入都将丢失。

为了处理模式对话框的消息,系统启动自己的消息循环,暂时控制整个应用程序的消息队列。当系统检索到不是明确用于对话框的消息时,它会将消息发送到相应的窗口。如果它检索到 WM_QUIT 消息,它会将消息发回应用程序消息队列,以便应用程序的主消息循环最终可以检索该消息。

每当应用程序消息队列为空时,系统就会向所有者窗口发送 WM_ENTERIDLE 消息。应用程序可以使用此消息在对话框停留在屏幕上时执行后台任务。当应用程序以这种方式使用消息时,应用程序必须频繁放弃控制权(例如,通过使用 PeekMessage 函数),以便模式对话框可以接收任何用户输入。为了防止模式对话框发送 WM_ENTERIDLE 消息,应用程序可以在创建对话框时指定 DS_NOIDLEMSG 样式。

应用程序使用 EndDialog 函数销毁模式对话框。在大多数情况下,当用户从对话框的窗口菜单中单击“关闭”或单击对话框中的“确定”或“取消”按钮时,对话框过程将调用 EndDialog。通过在调用 EndDialog 函数时指定一个值,对话框可以通过 DialogBox 函数(或其他创建函数)返回一个值。系统在销毁对话框后返回此值。大多数应用程序使用此返回值来确定对话框是成功完成其任务还是被用户取消。在对话框过程调用 EndDialog 函数之前,系统不会从创建对话框的函数返回控制权。

非模式对话框

非模式对话框应为具有窗口菜单、标题栏和细边框的弹出窗口;也就是说,对话框模板应指定 WS_POPUP、WS_CAPTION、WS_BORDER 和 WS_SYSMENU 样式。除非模板指定 WS_VISIBLE 样式,否则系统不会自动显示对话框。

应用程序使用CreateDialogCreateDialogIndirect函数创建非模式对话框。CreateDialog需要包含对话框模板的资源的名称或标识符; CreateDialogIndirect需要包含对话框模板的内存对象的句柄。另外两个函数CreateDialogParamCreateDialogIndirectParam也创建非模式对话框;它们在创建对话框时将指定的参数传递给对话框过程。

CreateDialog和其他创建函数都返回对话框的窗口句柄。应用程序和对话框过程可以使用此句柄来管理对话框。例如,如果对话框模板中未指定 WS_VISIBLE,则应用程序可以通过将窗口句柄传递给函数来显示对话框ShowWindow

非模式对话框既不会禁用所有者窗口也不会向其发送消息。创建对话框时,系统会将其设为活动窗口,但用户或应用程序可以随时更改活动窗口。如果对话框变为非活动状态,则即使所有者窗口处于活动状态,它仍会按 Z 顺序位于所有者窗口之上。

应用程序负责检索和发送输入消息到对话框。大多数应用程序使用主消息循环来实现这一点。但是,要允许用户使用键盘移动和选择控件,应用程序必须调用该 IsDialogMessage函数。有关该函数的更多信息,请参阅对话框键盘接口

非模式对话框不能像模式对话框那样向应用程序返回值,但对话框过程可以使用该SendMessage函数将信息发送到所有者窗口。

应用程序必须在终止前销毁所有非模式对话框。可以使用 函数销毁非模式对话框 DestroyWindow。在大多数情况下,对话框过程会 DestroyWindow响应用户输入(例如单击“取消”按钮)而调用 。如果用户从未以这种方式关闭对话框,则应用程序必须调用DestroyWindow

DestroyWindow使对话框的窗口句柄无效,因此对使用该句柄的函数的任何后续调用都会返回错误值。为了防止出现错误,对话框过程应通知所有者对话框已被销毁。许多应用程序维护一个包含对话框句柄的全局变量。当对话框过程销毁对话框时,它还会将全局变量设置为 NULL,表示对话框不再有效。

对话框过程不能调用EndDialog销毁非模式对话框的函数。

当创建模态对话框时,系统使它成为活动窗口。对话框保持活动状态,直到对话框过程调用该EndDialog函数或系统激活另一个应用程序中的窗口。在模态对话框被销毁之前,用户和应用程序都不能使所有者窗口处于活动状态。

如果所有者窗口尚未被禁用,系统会在创建模式对话框时自动禁用该窗口及其所有子窗口。所有者窗口将保持禁用状态,直到对话框被销毁。尽管对话框过程可能随时启用所有者窗口,但启用所有者窗口违背了模式对话框的目的,因此不建议这样做。当对话框过程被销毁时,系统会再次启用所有者窗口,但前提是模式对话框导致所有者被禁用。

https://docs.microsoft.com/en-us/windows/win32/dlgbox/about-dialog-boxes

所以你一定希望你的应用程序崩溃。

相关内容