c++winapigdi

How to draw a border on top of another application's window?


I need to draw a border on top of another application's window (the main purpose is to highlight the window that the user chooses from a running applications list).

I'm trying to draw the border on top of the native window border:

HPEN framePen = ::CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
HWND handle = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");

WINDOWPLACEMENT winPlacement;
GetWindowPlacement(handle, &winPlacement);
if (winPlacement.showCmd == SW_SHOWMINIMIZED)
{
    ShowWindow(handle, SW_RESTORE);
}

SetWindowPos(handle, HWND_TOP, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(handle);

PAINTSTRUCT ps;
RECT rect = {};
::GetClientRect(handle, &rect);
HDC hdc = ::BeginPaint(handle, &ps);
::SelectObject(hdc, framePen);
::Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
::EndPaint(handle, &ps);

But the border isn't drawn.

In this example, I used the handle of the cmd window, but in fact it doesn't matter.

Why isn't the border drawn? How can I draw it?


Solution

  • Finally I managed to solve the problem with the following code:

    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))
                        {
                            ShowWindow(hwnd, SW_HIDE);
                            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;
    }
    

    May be it is not the best solution, but it works for me. Could you please take a look on it and tell if there something to improve?