以弹出窗口的形式启动另一个应用程序

launch another application as popup window

本文关键字:启动 另一个 应用程序 窗口      更新时间:2023-10-16

我有一个MFC应用程序,它以弹出窗口的形式启动其他(通用窗口、黑盒)应用程序,并等待它们完成。父母和孩子之间不需要交流/互动,也应避免。只需要"子应用程序的行为与父应用程序的模式对话框相同"的行为。正确的方法是什么?

"以子窗口的形式启动另一个应用程序"的示例可以在以下位置看到:将应用程序激活为另一应用程序的子窗口/弹出窗口,这将导致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);

现在,我的小问题是前景视觉样式(例如,前景=蓝色标题栏vs.背景=灰色标题栏)和键盘输入焦点行为:

1) 最初移除子应用程序的WS_APPWINDOW样式会移除子应用的前台视觉和输入焦点。在这一点上,没有任何应用程序具有焦点。

2) 当用户单击任何父应用程序窗口时,子前景视觉样式将被切换。键盘焦点保留在子应用程序中。示例:子应用程序具有前景+焦点->单击父级第一次->子级失去前景,保留焦点->单击母级第二次->子级别获得前台,保持焦点->等等。

预期行为("正常"MFC弹出窗口的作用):子应用程序具有前景+焦点->单击父项->子标题栏短暂闪烁并保留前景+焦点子应用程序没有前景+焦点->单击父项->子标题栏获得前景和键盘焦点

现在这是最糟糕的问题:3) 我遇到过一个MFC应用程序,当单独启动时,用户可以打开"模式对话框堆栈"a->B->C->D->E,窗口的所有权与此完全匹配(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,它对窗口管理器很好,但可以打破儿童黑匣子应用程序的行为)。

  • 当子应用程序在没有窗口的情况下(间歇性)运行时,在父消息循环中添加一些"在子应用程序运行时忽略输入">
  • 为每次调用创建共享内存和结构以保存[pparentPid,parentHwnd,childPid]
  • 为[父级非主窗口、其UI线程、子挂钩的列表]创建DLL实例内存
  • 钩子系统范围到WH_CBT->HCBT_CREATEWND,如果childPid匹配,则在列表中注册窗口,如果还不存在,则仅为该子线程注册另一个钩子HCBT_ACTIVATE
  • 将全系统挂接至WH_CBT->HCBT_DESTROYND,如果childPid匹配,则在列表中注销窗口,如果这是给定线程的最后一个窗口,则注销HCBT_ACTIVATE挂接,如果这也是子应用程序的最后一次窗口,则取消挂接父HCBT_AACTIVATE挂接到焦点父
  • 父线程HCBT_ACTIVATE钩子阻止获得焦点,而是使用EnumWindows集中子应用程序
  • 如果目标是父线程,则子线程HCBT_ACTIVATE挂钩可防止焦点丢失,使父线程按Z顺序保持在子线程的正下方
  • 创建挂起的子进程,并仅在挂钩到位时恢复
  • 记得在任何地方解开

原始方法

基本上在前面的解决方案的第一点应用焦点切换,当点击父项时,它会在子项上闪烁,因为焦点会来回转移。

  • 在父消息循环中"在子应用程序运行时忽略输入"(放弃各种按键、单击等消息),当子应用程序正在运行时,请使用EnumWindows关注子应用程序

更改不属于您的其他窗口的父/子关系非常棘手且容易出错。如果启动器程序与启动的程序没有通信,并且主要目的是避免启动器出现任何UI,则可以在使用ShowWindow(SW_hide)成功启动辅助程序后简单地隐藏启动器。在隐藏模式下,它继续监视启动的程序,并在辅助程序终止时取消隐藏。

尝试使用API函数"ShellExecute()"。