Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modern: Bring windows to foreground, fix opening from context menu cause window in background #548

Merged
merged 1 commit into from
Jan 18, 2025

Conversation

R-YaTian
Copy link
Contributor

@R-YaTian R-YaTian commented Jan 16, 2025

This pr will fix #428, fix #543, fix #86...
And also added the code for hopefully workaround issue #462, further test needed.

@MouriNaruto
Copy link
Member

That's good.

But I'm curious about how it works. I had tried the similar way but failed to solve the issue.

If you can tell me some details. I will be very happy.

Kenji Mouri

@R-YaTian
Copy link
Contributor Author

R-YaTian commented Jan 17, 2025

That's good.

But I'm curious about how it works. I had tried the similar way but failed to solve the issue.

If you can tell me some details. I will be very happy.

Kenji Mouri

其实我也是经过了多次的尝试和研究才得出了可行的解决方案。通过研究 "Windows 终端" 和 "Files" 项目的类似案例之后,其实可以归纳出解决 MSIX 打包应用程序的前台窗口问题大致分为两个步骤:

Step1: 正确处理前台窗口权限
Step2: 将窗口激活到前台

在针对第一个步骤的研究中,总结出了如下结果:

PROCESS_INFORMATION ProcessInformation;
CreateProcessW(...)
AllowSetForegroundWindow(ProcessInformation.dwProcessId); // does't work, it's weird
AllowSetForegroundWindow(GetProcessId(ProcessInformation.hProcess)); // works fine!

所以在后续的步骤中,获取进程 id 统一使用了 GetProcessId(ProcessInformation.hProcess)
正确许可窗口前置权限之后,要解决的问题就是将窗口激活到前台。这里我获取窗口句柄的方案是使用枚举窗口匹配进程 id 的方式结合 BringToForeground 回调函数以便后续实现激活前台窗口,这此处就不多赘述了。要将窗口置于前台,其实根据微软文档的描述,有多个 API 可以实现。但是实际的结果却是有点差强人意的 (以下测试环境为 Win10):

起初我尝试的方案是直接调用
if (processId == (DWORD) lParam) {
    ::SetForegroundWindow(hwnd);
    return FALSE;
}
这虽然成功将窗口带到前台,但是同时也将资源管理器带到了后台,关闭程序后,资源管理器会重新弹回前台
这个表现非常影响体验,于是我还尝试了其他API:
SetActiveWindow(hwnd); // 没反应
BringWindowToTop(hwnd); // 成功带到前台,但是再次点击程序会将资源管理器弹回前台
SwitchToThisWindow(...); // 同上

由此可见,要将窗口平滑地带到前台,单独使用某一个 api 可能无法达到效果,这其中甚至还涉及到前一个前台窗口与新的前台窗口之间的输入机制问题。所幸,经过(努力)查找资料,终于在 stackoverflow 上找到了一篇讨论类似问题的帖子.
其中提到了一种方案(据说是 MS 推荐方案)确实可以正确地、平滑地将窗口带到前台:

HWND hCurWnd = ::GetForegroundWindow();
DWORD dwMyID = ::GetCurrentThreadId();
DWORD dwCurID = ::GetWindowThreadProcessId(hCurWnd, NULL);
::AttachThreadInput(dwCurID, dwMyID, TRUE);
::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
::SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
::SetForegroundWindow(m_hWnd);
::SetFocus(m_hWnd);
::SetActiveWindow(m_hWnd);
::AttachThreadInput(dwCurID, dwMyID, FALSE);

以上代码将前一个前台进程(在我们的场景中,其实就是资源管理器)的输入机制附加到新进程中,再通过 SetWindowPos 将窗体置于最前并且取消强制最前,随后处理了窗体的聚焦并置为活动窗口,这也使得用户无需通过点击程序窗体(或是对话框)来聚焦窗口。
总的来说,问题的解决大致分为以上两个步骤,也确实踩了很多坑才研究出这套解决方案,很高兴在此分享解决问题的经验!

@MouriNaruto
Copy link
Member

Got it. Thanks.

Kenji Mouri

@MouriNaruto MouriNaruto merged commit 5e042a7 into M2Team:main Jan 18, 2025
1 check passed
@R-YaTian R-YaTian deleted the foreground branch January 20, 2025 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants