如何实现符合条件的Windows窗口焦点与Z-order循环切换
窗口Z-order循环切换异常排查
我正在开发一个程序,用于实现一组符合条件的窗口(前台窗口,以及与该窗口屏幕区域存在大量重叠的窗口)的Z-order循环切换。假设有4个符合条件的窗口(含前台窗口),调用该程序4次应依次将每个窗口置于前台。
已将程序编译并通过AutoHotKey绑定快捷键,但运行时发现窗口焦点循环不符合预期:部分窗口会被多次聚焦,而非按顺序依次切换。
主程序代码
#define WIN32_LEAN_AND_MEAN #include <Windows.h> #undef min #undef max #include "enumWindows.h" #include <iostream> #include <vector> #include <optional> #include <format> #include <fstream> #include <unordered_map> RECT getWindowRect(HWND hwnd) { RECT theRect; if (IsIconic(hwnd)) { WINDOWPLACEMENT placement; GetWindowPlacement(hwnd, &placement); theRect = placement.rcNormalPosition; } else { GetWindowRect(hwnd, &theRect); } return theRect; } bool isTopMostWindow(HWND hwnd) { LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); return (exstyle & WS_EX_TOPMOST) != 0; } int width(RECT const& r) { return r.right - r.left; } int height(RECT const& r) { return r.bottom - r.top; } int area(RECT const& r) { return width(r) * height(r); } std::optional<RECT> intersection(RECT a, RECT b) { RECT theRect; theRect.left = std::max(a.left, b.left); theRect.right = std::min(a.right, b.right); theRect.top = std::max(a.top, b.top); theRect.bottom = std::min(a.bottom, b.bottom); if (width(theRect) <= 0 || height(theRect) <= 0) return std::nullopt; return theRect; } std::ostream& getLogStream() { #ifdef CONSOLE return std::cerr; #else static std::ofstream logFile("C:\\temp\\wincycle.log", std::ios::app); return logFile; #endif } void debugPrintWindowOrder(std::vector<HWND> const& windowStack) { std::ostream& ostr = getLogStream(); std::vector<HWND> sortedWindows = windowStack; std::sort(sortedWindows.begin(), sortedWindows.end()); std::unordered_map<HWND, size_t> shortNames; for (size_t i = 0; i < sortedWindows.size(); i++) { shortNames[sortedWindows[i]] = i; } for (auto window : windowStack) { ostr << shortNames[window] << ' '; } ostr << " { "; for (size_t i = 0; i < sortedWindows.size(); i++) { ostr << std::format("{}={:06x}, ", i, (size_t)sortedWindows[i]); } ostr << "}\n"; } void winCycle() { HWND fgWindow = GetForegroundWindow(); bool topmost = isTopMostWindow(fgWindow); if (fgWindow) { std::vector<HWND> windowStack; auto fgWindowRect = getWindowRect(fgWindow); float fgWindowArea = (float) (width(fgWindowRect) * height(fgWindowRect)); enumTaskbarWindows([&](HWND hwnd) { if (topmost == isTopMostWindow(hwnd)) { auto windowRect = getWindowRect(hwnd); if (auto interOpt = intersection(fgWindowRect, windowRect)) { auto const& intersection = *interOpt; float windowArea = (float) area(windowRect); float intersectionArea = (float) area(intersection); // greater than 90% overlap if (intersectionArea > 0.9 * fgWindowArea && intersectionArea >= 0.9 * windowArea) { windowStack.push_back(hwnd); } } } }); debugPrintWindowOrder(windowStack); if (windowStack.size() > 1) { auto hdwp = BeginDeferWindowPos(windowStack.size()); for (size_t i = 0; i < windowStack.size(); i++) { size_t stackIndex = (i + 1) % windowStack.size(); HWND insertAfter = HWND_TOP; if (i != 0) { size_t prevIndex = (stackIndex + windowStack.size() - 1) % windowStack.size(); insertAfter = windowStack[prevIndex]; } hdwp = DeferWindowPos(hdwp, windowStack[stackIndex], insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } EndDeferWindowPos(hdwp); SetForegroundWindow(windowStack[1]); if (IsIconic(windowStack[1])) { ShowWindow(windowStack[1], SW_NORMAL); } } } } int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { winCycle(); }
其中enumTaskbarWindows()函数定义于enumWindows.h,它调用EnumWindows()并筛选出任务栏中的窗口,预期返回与Alt-Tab一致的窗口列表。
enumWindows.h代码
#ifndef ENUM_WINDOWS_202208261917 #define ENUM_WINDOWS_202208261917 #include <string> #include <vector> namespace enum_windows_details { inline HWND getLastVisibleActivePopUpOfWindow(HWND window) { int const maxLastActivePopupIterations = 50; int level = maxLastActivePopupIterations; HWND currentWindow = window; while (level-- > 0) { HWND lastPopUp = GetLastActivePopup(currentWindow); if (IsWindowVisible(lastPopUp)) return lastPopUp; if (lastPopUp == currentWindow) return NULL; currentWindow = lastPopUp; } return NULL; } inline bool eligibleForActivation(HWND hwnd, HWND shellWindow) { if (hwnd == shellWindow) return false; HWND root = GetAncestor(hwnd, GA_ROOTOWNER); if (getLastVisibleActivePopUpOfWindow(root) != hwnd) return false; char classNameBuffer[256]; int length = GetClassNameA(hwnd, classNameBuffer, sizeof(classNameBuffer)); if (length == 0) return false; std::string className = classNameBuffer; char const* classNamesToSkip[] = { "Shell_TrayWnd", "DV2ControlHost", "MsgrIMEWindowClass", "SysShadow", "Button", "Windows.UI.Core.CoreWindow", "ApplicationFrameWindow" }; for (auto toSkip : classNamesToSkip) { if (className == toSkip) return false; } { char buffer[] = "WMP9MediaBarFlyout"; int buflen = sizeof(buffer) - 1; if (className.size() >= buflen) { if (memcmp(buffer, className.c_str(), buflen) == 0) return false; } } return true; } template<typename HandlerFunc> BOOL CALLBACK enumWindowFunc(HWND hwnd, LPARAM lParam) { HandlerFunc* pHandler = (HandlerFunc*) lParam; (*pHandler)(hwnd); return TRUE; } } // namespace enum_windows_details template<typename HandlerFunc> void enumWindows(HandlerFunc handler) { using namespace enum_windows_details; EnumWindows(&enumWindowFunc<HandlerFunc>, (LPARAM) &handler); } template<typename HwndHandler> void enumTaskbarWindows(HwndHandler handler) { using namespace enum_windows_details; HWND shellWindow = GetShellWindow(); int currentPid = GetCurrentProcessId(); enumWindows([&] (HWND hwnd) { if (!eligibleForActivation(hwnd, shellWindow)) return; DWORD windowPid; GetWindowThreadProcessId(hwnd, &windowPid); if (windowPid == currentPid || GetWindowTextLengthA(hwnd) == 0) return; LONG exstyle = GetWindowLongA(hwnd, GWL_EXSTYLE); if (exstyle & WS_EX_TOOLWINDOW) return; handler(hwnd); }); } #endif // #ifndef ENUM_WINDOWS_202208261917
日志输出
实际输出
3 1 0 2 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 1 2 3 0 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 2 3 0 1 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 3 0 1 2 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 0 2 3 1 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 2 3 1 0 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 3 1 0 2 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 1 2 3 0 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 2 3 0 1 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, }
预期输出
3 1 0 2 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 1 0 2 3 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 0 2 3 1 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 2 3 1 0 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, } 3 1 0 2 { 0=020318, 1=0204f2, 2=4f0cc6, 3=6e0998, }
当前windowStack已按EnumWindows()返回顺序存储符合条件的窗口,且windowStack[0]为前台窗口。我的循环逻辑是从windowStack[1]开始依次切换,最后回到windowStack[0]。
提问
是否正确使用了DeferWindowPos()或SetForegroundWindow()?为何窗口未按预期重新排序?
内容的提问来源于stack exchange,提问作者Benjamin Lindley
相关产品推荐
相关产品推荐

