如何将一个窗口保持在另一个应用程序窗口的前面

Posted

技术标签:

【中文标题】如何将一个窗口保持在另一个应用程序窗口的前面【英文标题】:How to keep a window in front of another applications window 【发布时间】:2018-10-21 18:04:49 【问题描述】:

我需要在所选应用程序的窗口顶部显示闪烁的边框(在我的示例中,应用程序是 cmd.exe)。为此,我使用了分层窗口。一切正常,除了一件事:如果目标窗口(在我的情况下 - cmd.exe)与另一个窗口重叠(如果另一个窗口在前台),我不能把它放在前面。它在我最大化/最小化目标窗口时起作用,但在目标窗口重叠时不起作用。点击任务栏中的应用图标无法恢复。

const COLORREF MY_COLOR_KEY = RGB(255, 128, 0);
HWND cmdHanlde = NULL;
constexpr unsigned int timerIdWindowUpdate = 1;
constexpr unsigned int timerIdFrameColor = 2;
bool tick = false;
bool minimized = false;

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
             _In_opt_ HINSTANCE hPrevInstance,
             _In_ LPWSTR    lpCmdLine,
             _In_ int       nCmdShow)

    WNDCLASSEX wc = ;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName = L"MyTransparentFrame";
    wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;

    wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
    
        switch (msg)
        
        case WM_PAINT:
        
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            RECT rc; GetClientRect(hwnd, &rc);
            HPEN hPen = CreatePen(PS_SOLID, 5, tick ? RGB(255, 128, 1) : RGB(255, 201, 14));
            HBRUSH hBrush = CreateSolidBrush(MY_COLOR_KEY);
            HGDIOBJ hOldPen = SelectObject(hdc, hPen);
            HGDIOBJ hOldBrush = SelectObject(hdc, hBrush);

            Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);

            if (hOldPen)
                SelectObject(hdc, hOldPen);
            if (hOldBrush)
                SelectObject(hdc, hOldBrush);
            if (hPen)
                DeleteObject(hPen);
            if (hBrush)
                DeleteObject(hBrush);

            EndPaint(hwnd, &ps);
        
        break;
        case WM_TIMER:
        
            if (wp == timerIdWindowUpdate)
            
                WINDOWPLACEMENT windowPlacement =  sizeof(WINDOWPLACEMENT), ;
                if (::GetWindowPlacement(cmdHanlde, &windowPlacement))
                
                    if (windowPlacement.showCmd == SW_SHOWMINIMIZED
                        || !IsWindowVisible(cmdHanlde))
                    
                        minimized = true;
                    
                    else
                    
                        RECT rect = ;
                        DwmGetWindowAttribute(cmdHanlde, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
                        MONITORINFO monInfo;
                        monInfo.cbSize = sizeof(MONITORINFO);
                        GetMonitorInfoW(MonitorFromWindow(cmdHanlde, MONITOR_DEFAULTTONEAREST), &monInfo);
                        if (cmdHanlde != NULL && ::IsZoomed(cmdHanlde))
                        
                            rect.left = monInfo.rcWork.left;
                            rect.top = monInfo.rcWork.top;
                            rect.bottom = monInfo.rcWork.bottom > rect.bottom ? rect.bottom : monInfo.rcWork.bottom;
                            rect.right = monInfo.rcWork.right > rect.right ? rect.right : monInfo.rcWork.right;
                        
                        if (minimized)
                        
                            ::SetWindowPos(hwnd, cmdHanlde, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                            minimized = false;
                        
                        else
                        
                            ::SetWindowPos(cmdHanlde, hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                            ::SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
                        SWP_SHOWWINDOW);
                        
                    
                
            
            else if (wp == timerIdFrameColor)
            
                tick = !tick;
                ::RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE);
            
            break;
        
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProcW(hwnd, msg, wp, lp);
        

        return 0;
    ;

    RegisterClassEx(&wc);

    HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE |     WS_EX_LAYERED |     WS_EX_TRANSPARENT, wc.lpszClassName, L"", WS_POPUP | WS_VISIBLE | WS_DISABLED,
0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
    ::SetTimer(hwnd, timerIdWindowUpdate, 50, NULL);
    ::SetTimer(hwnd, timerIdFrameColor, 500, NULL);
    SetLayeredWindowAttributes(hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY);
    ShowWindow(hwnd, SW_SHOW);
    cmdHanlde = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

    return (int)msg.wParam;

那么这里的问题是目标窗口被另一个窗口重叠后如何恢复?

【问题讨论】:

我认为这应该被简化为一个最小的例子。所有分层窗口和自定义绘制代码都与问题无关。它应该只是一个普通的窗口。 奇怪的行为是因为::SetWindowPos(hwnd, cmdHanlde, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);::SetWindowPos(cmdHanlde, hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); @Swordfish,你能解释一下这里有什么奇怪的地方吗? @rudolfninja 我提到无法一键恢复最小化的cmd.com。它需要几次点击和运气才能恢复。 @Swordfish,是的,但我想从第一次点击中恢复它。有办法吗? 【参考方案1】:
if (minimized) 
    ShowWindow(hwnd, SW_HIDE);
    minimized = false;
 else 
    SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left, 
                 rect.bottom - rect.top, SWP_NOZORDER);
    SetWindowPos(hwnd, GetNextWindow(cmdHandle, GW_HWNDPREV), 0, 0, 0, 0, 
                 SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);

【讨论】:

我认为两个SetWindowPos 调用可以减少到一个。 刚刚做了一个快速测试 - 似乎需要单独的调用,因为系统会忽略位置/大小,如果还指定了 z 顺序。

以上是关于如何将一个窗口保持在另一个应用程序窗口的前面的主要内容,如果未能解决你的问题,请参考以下文章

使用 SetParent 冻结父窗口

如何使 MDI 子窗口保持在其兄弟窗口之上?

没有窗口的 Mac 应用程序

c# 如何保持一个MDI子窗口永远最大化?

如何制作出现在其他窗口/应用程序前面的闪亮或 javascript 警报

在 qlikview 中,是不是可以保持脚本窗口始终打开?