简体   繁体   中英

Draw border on top of another application window

I'm need to draw border on top another application's window (the main purpose is to highlight window user chooses from running applications list). I'm trying to draw border on top of native window border, but the border isn't drawn. Here is the code:

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);

In this example used handle of cmd window, but in fact it doesn't matter. Could you please tell why border isn't drawn and how to draw it? Thanks.

You can't draw directly on another window, because the system may refresh the window at any time, overwriting your drawing.

To make your drawing persistent, create a layered window , positioned on top of the other window.

  1. Create the window with WS_EX_LAYERED flag.
  2. Pass a color key to SetLayeredWindowAttributes() .
  3. In your WM_PAINT handler, draw the inside of the rectangle with the color key (by using it for the brush). Everything you draw with the color key will become transparent. Draw the border of the rectangle with the desired color (by using it for the pen).

Here is a minimal example to get you started. The frame can be moved around by drag-n-drop.

Note there is no error handling to keep the sample code concise. You should check the return value of each Windows API call.

#include <windows.h>

const COLORREF MY_COLOR_KEY = RGB( 255, 0, 255 );

int APIENTRY wWinMain( 
    HINSTANCE hInstance, HINSTANCE /*hPrevInst*/, LPWSTR /*lpCmdLine*/, int nCmdShow )
{
    WNDCLASSW wc{ sizeof( wc ) };
    wc.hInstance = hInstance;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor = LoadCursor( nullptr, IDC_ARROW );
    wc.hbrBackground = reinterpret_cast<HBRUSH>( COLOR_BTNFACE + 1 );
    wc.lpszClassName = L"MyTransparentFrame";

    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, 20, GetSysColor( COLOR_HIGHLIGHT ) );
                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_DESTROY:
                PostQuitMessage( 0 );
            break;

            case WM_NCHITTEST:
                return HTCAPTION;  // to be able to drag the window around
            break;

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

        return 0;       
    };

    RegisterClassW( &wc );

    HWND hwnd = CreateWindowExW( WS_EX_LAYERED, wc.lpszClassName, L"", WS_POPUP,
                         200, 200, 800, 600, nullptr, nullptr, hInstance, nullptr );

    SetLayeredWindowAttributes( hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY );
    ShowWindow( hwnd, nCmdShow );

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

    return (int) msg.wParam;
}

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM