You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现符合条件的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 05:36:12