【发布时间】:2012-08-16 14:01:43
【问题描述】:
我有一个 MFC 应用程序,它以弹出窗口的形式启动其他(通用窗口、黑盒)应用程序并等待它们完成。父母和孩子之间不需要沟通/互动,应该避免。只需要“子应用程序表现为父应用程序的模态对话框”行为。 这样做的正确方法是什么?
“以 child 窗口的形式启动另一个应用程序”的示例可以在以下位置看到:Activating an application as a child/popup of another application 导致 http://www.codeproject.com/Articles/18724/Hosting-exe-applications-into-a-dialog(这不是我想要的,我想要模态弹出行为)
为简单起见,我们可以假设启动和启动的应用程序都有一个“堆栈”窗口(一个主窗口带有模式对话框,可以有自己的模式对话框)。
我当前的伪代码(为简单起见省略了错误处理和回调函数)
//get the current MFC dialog of launcher program we are launching the other app from
parentWnd = AfxGetMainWnd()->GetActiveWindow();
parentHwnd = parentWnd->GetSafeHwnd(); //HWND
// launch child and retrieve basic info from PROCESSINFO structure
CreateProcess(childExecutable); // => childProcessHandle, childProcessId
//get the "main" window of child application
EnumWindows(EnumProc_That_Retrieves_TopLevelWindow_With_childProcessId); // => childHwnd
//link the child window as popup
SetWindowLong(childHwnd, GW_OWNER, parentHwnd);
//disable input into parent window
parentWnd->EnableWindow(FALSE);
//remove taskbar entry for child
SetWindowLong(child, GWL_EXSTYLE, GetWindowLong(child, GWL_EXSTYLE)&~WS_APPWINDOW);
//now keep waiting for the child process termination and process parent messages (e.g. WM_PAINT)
while (MsgWaitForMultipleObjects(childProcessHandle and process QS_ALLINPUT) {
while (PeekMessage(PM_NOREMOVE)) AfxGetApp()->PumpMessage();
}
//re-enable input into parent window
parentWnd->EnableWindow(TRUE);
现在,我在前景视觉风格(例如前景 = 蓝色标题栏与背景 = 灰色标题栏)和键盘输入焦点行为方面的小问题:
1) 初始移除子 WS_APPWINDOW 样式会移除子应用的前景视觉和输入焦点。此时没有应用程序具有焦点。
2) 当用户点击任何父应用程序窗口时,子前景视觉样式被切换。键盘焦点保留在子应用程序中。 示例:子应用具有前景 + 焦点 -> 第一次单击父级 -> 子级失去 前景,保持焦点 -> 第二次单击父级 -> 子级获得 前景,保持焦点-> 等等
预期行为(“正常”MFC 弹出窗口的行为): 子应用具有前景 + 焦点 -> 点击父级 -> 子标题栏 短暂闪烁 并保留前景 + 焦点 子应用没有前景+焦点->点击父->子标题栏获得前景和键盘焦点
现在这是最糟糕的问题:
3)我遇到了一个MFC应用程序,当单独启动时,用户可以打开一个“模式对话框堆栈”A->B->C->D->E,windows的所有权与此完全匹配(E归D所有, D 归 C 等)。但是如果我从我的 MFC 应用程序 (M) 打开它,所有权看起来像 M->A->B->C,D,E (C,D,E 都归 B 所有,B 归 A,A 归通过我的应用程序窗口 M)。这会导致“没有支持的堆栈”http://blogs.msdn.com/b/oldnewthing/archive/2005/02/24/379635.aspx 问题。
当我删除 SetWindowLong(childHwnd, GW_OWNER, parentHwnd) 时,这种行为会消失,因此弄乱所有权可能会触发子应用程序的不良行为,但如果没有它,我似乎无法保证模态对话框的“一个位于另一个之上”的前提。
所以又是一个大问题:完成这项任务并避免我所描述的问题的正确方法是什么。
编辑
目前的解决方案
正如@mfc 建议的那样,我们必须避免弄乱所有者拥有的结构,因此任务基本上是以另一种方式为我们的父子对重新实现窗口管理器的这一方面。我已经使用 Windows Hooks 对解决方案的一部分进行了原型设计。然而,完成它似乎相当复杂和乏味,所以我决定采用另一种原始方法(截止日期,哦截止日期)。为了举例,我将描述两者的基本思想。
挂钩解决方案
免责声明:仅父焦点钩子已确认有效,其余为理论制作。也许有更清洁/更轻量级的实现,可以在实际的 Windows 窗口管理器实现中获得灵感(请记住,重点是避免设置 GW_OWNER 这对窗口管理器工作正常,但会破坏子黑盒应用程序的行为)。
- 当子应用程序在没有窗口的情况下(间歇性地)运行时,在父消息循环中添加一些“在子程序运行时忽略输入”
- 为每次调用创建共享内存和结构以保存 [parentPid, parentHwnd,childPid]
- 为 [父非拥有窗口列表、它们的 UI 线程、子挂钩] 创建 DLL 实例化内存
- 在系统范围内挂钩到 WH_CBT -> HCBT_CREATEWND,如果 childPid 匹配,则在列表中注册窗口,如果尚未存在,则仅为该子线程注册另一个挂钩 HCBT_ACTIVATE
- hook systemwide 到 WH_CBT -> HCBT_DESTROYWND,如果 childPid 匹配,取消注册列表中的窗口,如果这是给定线程的最后一个窗口,则取消注册 HCBT_ACTIVATE 钩子,如果这是子应用程序的最后一个窗口,取消钩子父 HCBT_ACTIVATE 钩子和焦点父李>
- 父线程 HCBT_ACTIVATE 挂钩可防止获得焦点,而是使用 EnumWindows 聚焦子应用。
- 如果目标是父级,子线程 HCBT_ACTIVATE 挂钩可防止焦点丢失,在 Z 顺序中将父级保持在子级下方
- 创建子进程挂起并仅在挂钩到位时恢复
- 记得随处脱钩
原始方法
基本上在先前解决方案的第一点中应用焦点开关,当单击父级时,它会随着焦点来回转移而在子级上闪烁。
- “子应用运行时忽略输入”(丢弃各种按键、点击等消息)在子应用运行时的父消息循环中,使用 EnumWindows 聚焦子应用。
【问题讨论】: