windows 如何使子进程窗口在我的进程中显示为模态?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/613513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I make a child process window to appear modal in my process?
提问by moogs
I have an application that calls some other utility application to set some settings for a particular device. That utility application is called using ShellExecuteEx.
我有一个应用程序调用其他一些实用程序来为特定设备设置一些设置。使用 ShellExecuteEx 调用该实用程序应用程序。
So as not to confuse the user, it would be better to made the window of the utility application modal to my main window. How does one do this?
为了不混淆用户,最好将实用程序应用程序的窗口设置为我的主窗口。如何做到这一点?
Things I've tried:
我尝试过的事情:
- WaitForSingleObjectEx on the process after ShellExecuteEx, INFINITE TIMEOUT - window is modal, but main application does not repaint (because it's waiting for the single object!)
- WaitForSingleObjectEx on the process after ShellExecuteEx, some small timeout, then call Peekmessage and DispatchMessage - repaint now works, but utility application is no longer "modal". The main application responds to mouse clicks, button clicks, etc
- EnableWindow(FALSE), then do method #2, then EnableWindow(TRUE) - WORKS!!!, but after this, the z-order of my application changed. (it's now below some other window). why?!
- 在 ShellExecuteEx 之后的进程上 WaitForSingleObjectEx,无限超时 - 窗口是模态的,但主应用程序不会重新绘制(因为它正在等待单个对象!)
- 在 ShellExecuteEx 之后的进程上 WaitForSingleObjectEx,一些小的超时,然后调用 Peekmessage 和 DispatchMessage - 重绘现在可以工作,但实用程序应用程序不再是“模态”。主应用程序响应鼠标点击、按钮点击等
- EnableWindow(FALSE),然后执行方法 #2,然后 EnableWindow(TRUE) - WORKS!!!,但在此之后,我的应用程序的 z 顺序发生了变化。(它现在位于其他窗口下方)。为什么?!
回答by Will Rickards
You have two things to simulate: ownership and modality.
您需要模拟两件事:所有权和模式。
To simulate ownership: You need to set the owner of your new child process window to your window. This should alleviate any z ordering issues. Though I don't know if this works from another process. If not then you might have to attach your thread input queues and then call it. Or use some other code injection technique.
模拟所有权:您需要将新子进程窗口的所有者设置为您的窗口。这应该可以缓解任何 z 排序问题。虽然我不知道这是否适用于另一个过程。如果没有,那么您可能必须附加您的线程输入队列,然后调用它。或者使用其他一些代码注入技术。
SetWindowLong <target window handle>, GWL_HWNDPARENT, <new owner handle>
To simulate modality, I think you are on the right track with EnableWindow and the WaitForSingleObjectEx.
为了模拟模态,我认为您使用 EnableWindow 和 WaitForSingleObjectEx 是正确的。
回答by Matthew Xavier
The short answer is that there is no way to seamlessly make a window in thread B modal for a window in thread A, even if the threads are in the same process. If you own the code for both windows, you may be able to come close, but in that case you will achieve much better results for the effort by putting all of your UI in one thread.
简短的回答是,即使线程在同一进程中,也无法为线程 A 中的窗口无缝地创建线程 B 模式中的窗口。如果您拥有两个窗口的代码,您可能会接近,但在这种情况下,通过将所有 UI 放在一个线程中,您将获得更好的结果。
If you try to suggest to the user that thread B's window is modal for thread A's, there are a lot of subtle Z-order and activation behaviors you have to get right (as you have noticed) lest you suffer an uncanny-valley effect of sorts, where it's clear to the user that thread B's window is trying to be something it's not and therefore seems broken.
如果您尝试向用户建议线程 B 的窗口是线程 A 的模态窗口,那么您必须正确处理许多微妙的 Z 顺序和激活行为(如您所见),以免遭受排序,用户很清楚线程 B 的窗口试图成为它不是的东西,因此看起来已经坏了。
To avoid that, I would take this approach:
为了避免这种情况,我会采取这种方法:
- The user clicks on "FDA Inspection" in canner.exe's main window. canner.exe shows a modal dialog indicating that it is opening an external program ("Opening Botulism Settings..."). This disables the main window, etc. so that the user knows a modal interaction is taking place.
- canner.exe calls ShellExecuteEx() to start botulism.exe.
- canner.exe calls WaitForInputIdle() on the handle returned from ShellExecuteEx(). WaitForInputIdle() will return (approximately, but usually close enough) when botulsim.exe is ready for user interaction. If botulism.exe typically takes five or more seconds to show its UI, I may use a short timeout with WaitforInputIdle() in a loop and occasionally process any pending messages with PeekMessage()/ProcessMessage().
- canner.exe changes its dialog text to reflect that it is waiting for the user to close botulism.exe ("Close Botulism Settings to continue...").
- canner.exe calls MsgWaitForMultipleObjects() in a loop to wait until botulsim.exe closes. MsgWaitForMultipleObjects() will return when the handles passed are signaled or when there are messages waiting in the thread's queue.
- If the user clicks the close box in canner.exe's modal dialog while canner.exe is waiting, canner.exe prompts the user that botulism.exe is still running ("Botulism Settings is still open, continue anyway?", "Yes, I know" or "No, I'm not done"). If confirmed, canner.exe closes the dialog and cancels the original FDA inspection started in step 1 and returns to the main window's message loop.
- When MsgWaitForMultipleObjects() indicates that botulism.exe is finished, canner.exe closes the dialog and continues normally with the FDA inspection started in step 1.
- 用户单击 canner.exe 主窗口中的“FDA 检查”。canner.exe 显示一个模式对话框,指示它正在打开外部程序(“打开肉毒杆菌中毒设置...”)。这会禁用主窗口等,以便用户知道正在发生模态交互。
- canner.exe 调用ShellExecuteEx() 来启动botulism.exe 。
- canner.exe对从 ShellExecuteEx() 返回的句柄调用WaitForInputIdle()。当 botulsim.exe 准备好进行用户交互时,WaitForInputIdle() 将返回(大约,但通常足够接近)。如果 botulism.exe 通常需要五秒或更长时间来显示其 UI,我可能会在循环中使用 WaitforInputIdle() 的短超时,并偶尔使用 PeekMessage()/ProcessMessage() 处理任何挂起的消息。
- canner.exe 更改其对话框文本以反映它正在等待用户关闭 botulism.exe(“关闭肉毒杆菌设置以继续...”)。
- canner.exe在循环中调用MsgWaitForMultipleObjects() 以等待 botulsim.exe 关闭。MsgWaitForMultipleObjects() 将在传递的句柄发出信号或线程队列中有消息等待时返回。
- 如果用户在 canner.exe 等待时单击 canner.exe 模式对话框中的关闭框,canner.exe 会提示用户 botulism.exe 仍在运行(“Botulism 设置仍处于打开状态,是否继续?”、“是的,我知道”或“不,我还没有完成”)。如果确认,canner.exe 将关闭对话框并取消在步骤 1 中开始的原始 FDA 检查并返回到主窗口的消息循环。
- 当 MsgWaitForMultipleObjects() 指示 botulism.exe 已完成时,canner.exe 关闭对话框并继续正常进行 FDA 检查在步骤 1 中开始。
This way, if everything proceeds normally and quickly, the interaction may well be seamless, but if something goes wrong with the child process or the Z-order gets changed, etc. it will be clear why the parent process is waiting and what the user needs to do to either cancel or go on with the task he started.
这样,如果一切正常且快速地进行,交互很可能是无缝的,但是如果子进程出现问题或 Z-order 被更改等,那么父进程为什么在等待以及用户在等待什么就很清楚了需要取消或继续他开始的任务。
回答by Maurice Flanagan
EnableWindow is correct, this is generally how message boxes and other "modal" windows do it. As for zorder changing, you can intercept the WM_WINDOWPOSCHANGING message and set the SWP_NOZORDER flag to prevent the zorder change. Make sure you only do this while you are setting EnableWindow(false).
EnableWindow 是正确的,这通常是消息框和其他“模态”窗口的做法。至于zorder改变,你可以拦截WM_WINDOWPOSCHANGING消息并设置SWP_NOZORDER标志来防止zorder改变。确保仅在设置 EnableWindow(false) 时执行此操作。
回答by Avram
Just logical suggestion,
Maybe you can create invisible modal form, and from him use the method #1.
只是合乎逻辑的建议,
也许您可以创建隐形模态表单,并从他那里使用方法#1。
回答by jdigital
Let's take a look at your approach #3, which is very close to what you want. I suspect the problem is that when the secondary app closes, Windows decides that it doesn't want to restore focus to a disabled window. You could try to re-enable your window before that happens but that's likely to be tricky (and not worth the effort).
让我们来看看您的方法 #3,它非常接近您想要的。我怀疑问题在于当辅助应用程序关闭时,Windows 决定不想将焦点恢复到禁用的窗口。您可以尝试在此之前重新启用您的窗口,但这可能会很棘手(并且不值得付出努力)。
Instead of disabling the window directly, try disabling it by just ignoring user input. So rather than calling EnableWindow, change your message loop to filter out input messages. In particular, if
不要直接禁用窗口,而是尝试通过忽略用户输入来禁用它。因此,与其调用 EnableWindow,不如更改您的消息循环以过滤掉输入消息。特别地,如果
msg >= WM_KEYFIRST || msg <= WM_KEYLAST || msg >= WM_MOUSEFIRST || msg <= WM_MOUSELAST
then discard the message; otherwise, pass it on to the normal dispatch loop. What you're doing is creating your own disabled window, but Windows doesn't know that.
然后丢弃消息;否则,将其传递给正常的调度循环。您正在做的是创建自己的禁用窗口,但 Windows 不知道。