【问题标题】:How can I make a child process window to appear modal in my process?如何使子进程窗口在我的进程中显示为模态?
【发布时间】:2023-03-10 04:37:01
【问题描述】:

我有一个应用程序调用其他一些实用程序应用程序来为特定设备设置一些设置。该实用程序应用程序是使用 ShellExecuteEx 调用的。

为了不让用户感到困惑,最好将实用程序应用程序的窗口设置为我的主窗口。如何做到这一点?

我尝试过的事情:

  1. ShellExecuteEx 后进程上的 WaitForSingleObjectEx,INFINITE TIMEOUT - 窗口是模态的,但主应用程序不会重新绘制(因为它正在等待单个对象!)
  2. ShellExecuteEx 后进程上的 WaitForSingleObjectEx,一些小的超时,然后调用 Peekmessage 和 DispatchMessage - 重绘现在可以工作,但实用程序应用程序不再是“模态”。主应用程序响应鼠标点击、按钮点击等
  3. EnableWindow(FALSE),然后执行方法 #2,然后 EnableWindow(TRUE) - 工作!!!,但在此之后,我的应用程序的 z 顺序发生了变化。 (它现在位于其他窗口下方)。为什么?!

【问题讨论】:

  • 请求编辑:标题更改为“如何使子进程窗口在我的进程中显示为模态?”。您当前的标题没有反映制作子窗口模式和将窗口注入另一个进程之间的区别(请参阅 SetParent())

标签: windows winapi


【解决方案1】:

您需要模拟两件事:所有权和模式。

模拟所有权: 您需要将新子进程窗口的所有者设置为您的窗口。 这应该可以缓解任何 z 排序问题。 虽然我不知道这是否适用于另一个进程。 如果没有,那么您可能必须附加您的线程输入队列,然后调用它。 或者使用其他一些代码注入技术。

SetWindowLong <target window handle>, GWL_HWNDPARENT, <new owner handle>

为了模拟模态,我认为您在 EnableWindow 和 WaitForSingleObjectEx 的正确轨道上。

【讨论】:

  • SetWindowLong GWL_HWNDPARENT 确实可以跨进程工作,它会为您加入输入队列。
【解决方案2】:

简短的回答是,即使线程在同一个进程中,也无法在线程 B 模式下无缝地为线程 A 中的窗口创建一个窗口。如果您拥有两个窗口的代码,您可能会接近,但在这种情况下,通过将所有 UI 放在一个线程中,您将获得更好的结果。

如果您尝试向用户建议线程 B 的窗口是线程 A 的模态窗口,那么您必须正确处理许多微妙的 Z 顺序和激活行为(正如您所注意到的),以免遭受不可思议的影响——各种山谷效应,用户很清楚线程 B 的窗口试图成为它不是的东西,因此似乎坏了。

为了避免这种情况,我会采取这种方法:

  1. 用户点击canner.exe主窗口中的“FDA检查”。 canner.exe 显示一个模式对话框,指示它正在打开一个外部程序(“正在打开肉毒杆菌中毒设置...”)。这会禁用主窗口等,以便用户知道正在发生模式交互。
  2. canner.exe 调用ShellExecuteEx() 来启动botulism.exe。
  3. canner.exe 在从 ShellExecuteEx() 返回的句柄上调用 WaitForInputIdle()。当 botulsim.exe 准备好进行用户交互时,WaitForInputIdle() 将返回(大约,但通常足够接近)。如果 botulism.exe 通常需要五秒或更长时间才能显示其 UI,我可能会在循环中使用 WaitforInputIdle() 的短暂超时,并偶尔使用 PeekMessage()/ProcessMessage() 处理任何待处理的消息。
  4. canner.exe 更改其对话框文本以反映它正在等待用户关闭 botulism.exe(“Close Botulism Settings to continue...”)。
  5. canner.exe 在循环中调用MsgWaitForMultipleObjects() 以等待botulsim.exe 关闭。当传递的句柄发出信号或线程队列中有消息等待时,MsgWaitForMultipleObjects() 将返回。
  6. 如果用户在 canner.exe 等待时单击 canner.exe 模式对话框中的关闭框,canner.exe 会提示用户 botulism.exe 仍在运行(“Botulism 设置仍然打开,是否继续?”,“是的,我知道”或“不,我还没说完”)。如果确认,canner.exe 将关闭对话框并取消在步骤 1 中启动的原始 FDA 检查并返回到主窗口的消息循环。
  7. 当 MsgWaitForMultipleObjects() 指示 botulism.exe 已完成时,canner.exe 将关闭对话框并正常继续执行步骤 1 中开始的 FDA 检查。

这样,如果一切正常且快速进行,交互很可能是无缝的,但如果子进程出现问题或 Z 顺序发生变化等,就会清楚为什么父进程正在等待和用户需要做什么才能取消或继续他开始的任务。

【讨论】:

  • 您错过了 OP 的声明意图。他想让这个无缝,他希望 twp 应用程序看起来像一个。该解决方案暴露了管道。使用您的术语,最终用户不必知道“botulism.exe”。
  • 不幸的是,Windows 模型,当然还有对话管理器,它是模态的正常代理,并不是跨进程边界的无缝连接。如果我必须有一个接缝,我更喜欢一个坚固、可见的接缝,而不是一个隐藏的接缝,如果我碰巧绊倒就会裂开。
  • 你是对的,没有直接/直接的方法可以做到这一点。但是,这几乎可以肯定是可行的。问题是它是否值得 150 分。赏金花时间制定细节。这比通常的咨询费要少得多;-)
【解决方案3】:

EnableWindow 是正确的,这通常是消息框和其他“模态”窗口的做法。至于 zorder 变化,可以拦截 WM_WINDOWPOSCHANGING 消息并设置 SWP_NOZORDER 标志来防止 zorder 变化。确保仅在设置 EnableWindow(false) 时执行此操作。

【讨论】:

    【解决方案4】:

    只是合乎逻辑的建议,
    也许您可以创建不可见的模态表单,并从他那里使用方法#1。

    【讨论】:

      【解决方案5】:

      让我们看看你的方法#3,它非常接近你想要的。我怀疑问题是当辅助应用程序关闭时,Windows 决定它不想将焦点恢复到禁用的窗口。您可以尝试在此之前重新启用您的窗口,但这可能很棘手(而且不值得努力)。

      不要直接禁用窗口,而是尝试通过忽略用户输入来禁用它。因此,与其调用 EnableWindow,不如更改您的消息循环以过滤掉输入消息。特别是,如果

      msg >= WM_KEYFIRST || msg <= WM_KEYLAST || msg >= WM_MOUSEFIRST || msg <= WM_MOUSELAST
      

      然后丢弃该消息;否则,将其传递给正常的调度循环。您正在做的是创建自己的禁用窗口,但 Windows 不知道。

      【讨论】:

        【解决方案6】:

        试试这个。这会暂停调用者,直到子进程退出。

            private void btnChildApp_Click(object sender, EventArgs e)
            {
                Process p = Process.Start(@".\ChildApp.exe");
                p.WaitForExit();
            }
        

        【讨论】:

          猜你喜欢
          • 2014-06-29
          • 1970-01-01
          • 2012-07-17
          • 1970-01-01
          • 2013-01-11
          • 2016-03-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多