Frame-окно вместо тонкой рамки заливает экран чёрным и блокирует всё остальное

Пишу крошечную утилиту-рамку поверх всех окон. Задача проста: тонкая линия вокруг границ экрана, всегда поверх всего, без взаимодействия с мышью/клавой и без влияния на остальной рабочий стол.

Но при запуске exe вижу следующее:

рамка рисуется.

весь экран под ней превращается в чёрный прямоугольник.

клики, открытые, закрытые программ и Alt-Tab не проходят - как будто приложение перехватило фокус.

Закрыть можно только через WIN + ALT + DELETE - выйти.

Компилирую g++ (MinGW-w64 ucrt64) -std=c++20 из MSYS2

#include <windows.h>
#include <thread>
#include <chrono>
#include <cmath>

constexpr int BORDER_WIDTH = 1;
constexpr int COLOR_CYCLE_TIME_MS = 10;

COLORREF HSVtoRGB(float hue) {
    float r, g, b;
    int i = int(hue * 6);
    float f = hue * 6 - i;
    float q = 1 - f;

    switch(i % 6){
        case 0: r = 1, g = f, b = 0; break;
        case 1: r = q, g = 1, b = 0; break;
        case 2: r = 0, g = 1, b = f; break;
        case 3: r = 0, g = q, b = 1; break;
        case 4: r = f, g = 0, b = 1; break;
        case 5: r = 1, g = 0, b = q; break;
        default: r = g = b = 0;
    }

    return RGB(int(r * 255), int(g * 255), int(b * 255));
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_DESTROY) {
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

void DrawFrame(HDC hdc, int width, int height, COLORREF color) {
    HBRUSH brush = CreateSolidBrush(color);
    RECT top = {0, 0, width, BORDER_WIDTH};
    RECT bottom = {0, height - BORDER_WIDTH, width, height};
    RECT left = {0, 0, BORDER_WIDTH, height};
    RECT right = {width - BORDER_WIDTH, 0, width, height};

    FillRect(hdc, &top, brush);
    FillRect(hdc, &bottom, brush);
    FillRect(hdc, &left, brush);
    FillRect(hdc, &right, brush);

    DeleteObject(brush);
}

void AddToAutostart() {
    HKEY hKey;
    const wchar_t* regPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
    const wchar_t* appName = L"FrameBorder";
    wchar_t path[MAX_PATH];
    GetModuleFileNameW(NULL, path, MAX_PATH);

    if (RegOpenKeyExW(HKEY_CURRENT_USER, regPath, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) {
        RegSetValueExW(hKey, appName, 0, REG_SZ, (BYTE*)path, (wcslen(path) + 1) * sizeof(wchar_t));
        RegCloseKey(hKey);
    }
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int) {
    const wchar_t CLASS_NAME[] = L"FrameWindow";

    WNDCLASSW wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClassW(&wc);

    int screenX = GetSystemMetrics(SM_CXSCREEN);
    int screenY = GetSystemMetrics(SM_CYSCREEN);

    HWND hwnd = CreateWindowExW(
        WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
        CLASS_NAME,
        L"Frame Border",
        WS_POPUP,
        0, 0, screenX, screenY,
        NULL, NULL, hInstance, NULL
    );

    if (hwnd == NULL) return 0;

    SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
    ShowWindow(hwnd, SW_SHOW);

    AddToAutostart();

    HDC hdc = GetDC(hwnd);

    float hue = 0.0f;
    MSG msg = {};
    while (true) {
        while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT)
                return 0;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        COLORREF color = HSVtoRGB(hue);
        DrawFrame(hdc, screenX, screenY, color);
        hue += 0.001f;
        if (hue > 1.0f) hue = 0.0f;

        std::this_thread::sleep_for(std::chrono::milliseconds(COLOR_CYCLE_TIME_MS));
    }

    ReleaseDC(hwnd, hdc);
    return 0;
}

Что я упускаю? Как заставить окно быть полностью прозрачным внутри, оставив видимой только рамку, и не блокировать ввод?


Ответы (1 шт):

Автор решения: user7860670

Архитектура окон опирается на передачу сообщений, так что все основные действия обычно стоит производить в обработчике соотв. сообщения. В данном случае для отрисовки важны WM_ERASEBKGND, где можно ничего не делать, предотвращая бесполезную перерисовку фона оконной процедурой по-умолчанию, WM_PAINT, где следует производить собственно перерисовку. Перерисовку имеет смысл вызывать в обработчике WM_TIMER вместо блокировки потока посредством sleep. Ну и обработчик сообщения закрытия окна WM_CLOSE (Alt + F4) нужен.

Если нужно рисовать только непрозрачную рамку, то в окне имеет смысл задействовать режим прозрачности по цвету (colorkey), черному в данном примере.

Ну и проверять возвращаемые значения функций на предмет корректности и не забывать подчищать.

#define WIN32_LEAN_AND_MEAN
#define STRICT
#include <Windows.h>

#include <cassert>

constexpr int BORDER_WIDTH = 1;
constexpr int COLOR_CYCLE_TIME_MS = 10;

static float g_hue{};
static int g_screenX{};
static int g_screenY{};

static COLORREF HSVtoRGB(float hue)
{
    float r, g, b;
    int i = int(hue * 6);
    float f = hue * 6 - i;
    float q = 1 - f;

    switch (i % 6)
    {
        case 0: r = 1, g = f, b = 0; break;
        case 1: r = q, g = 1, b = 0; break;
        case 2: r = 0, g = 1, b = f; break;
        case 3: r = 0, g = q, b = 1; break;
        case 4: r = f, g = 0, b = 1; break;
        case 5: r = 1, g = 0, b = q; break;
        default: r = g = b = 0;
    }

    return RGB(int(r * 255), int(g * 255), int(b * 255));
}

static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (WM_CLOSE == msg)
    {
        PostQuitMessage(0);
        return 0;
    }
    else if (WM_TIMER == msg)
    {
        ::InvalidateRect(hwnd, static_cast<RECT *>(nullptr), FALSE);
        return 0;
    }
    else if (WM_ERASEBKGND == msg)
    {
        return 1; // erased background
    }
    else if (WM_PAINT == msg)
    {
        HDC const window_context{::GetDC(hwnd)};
        assert(HDC{} != window_context);
        {
            RECT const window_rect{BORDER_WIDTH, BORDER_WIDTH, ::g_screenX - BORDER_WIDTH, ::g_screenY - BORDER_WIDTH};
            ::FillRect(window_context, &window_rect, reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH)));
        }
        {
            COLORREF color{::HSVtoRGB(::g_hue)};
            ::g_hue += 0.001f;
            if (1.0f < ::g_hue)
            {
                ::g_hue = 0.0f;
            }

            HBRUSH const brush{::CreateSolidBrush(color)};
            assert(HBRUSH{} != brush);
            {
                RECT const top{0, 0, ::g_screenX, BORDER_WIDTH};
                ::FillRect(window_context, &top, brush);
            }
            {
                RECT const bottom{0, ::g_screenY - BORDER_WIDTH, ::g_screenX, ::g_screenY};
                ::FillRect(window_context, &bottom, brush);
            }
            {
                RECT const left{0, 0, BORDER_WIDTH, ::g_screenY};
                ::FillRect(window_context, &left, brush);
            }
            {
                RECT const right{::g_screenX - BORDER_WIDTH, 0, ::g_screenX, ::g_screenY};
                ::FillRect(window_context, &right, brush);
            }
            [[maybe_unused]] auto const deleted{::DeleteObject(static_cast<HGDIOBJ>(brush))};
            assert(deleted);
        }
        [[maybe_unused]] auto const released{::ReleaseDC(hwnd, window_context)};
        assert(FALSE != released);
        [[maybe_unused]] auto const validated{::ValidateRect(hwnd, static_cast<RECT *>(nullptr))};
        assert(FALSE != validated);
        return 0;
    }
    return ::DefWindowProcW(hwnd, msg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
{
    auto const & sz_class_name{L"FrameWindow"};
    ::g_screenX = ::GetSystemMetrics(SM_CXSCREEN);
    ::g_screenY = ::GetSystemMetrics(SM_CYSCREEN);

    WNDCLASSW wc{};
    wc.lpfnWndProc = &::WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = sz_class_name;

    [[maybe_unused]] auto const class_atom{::RegisterClassW(&wc)};
    assert(ATOM{} != class_atom);

    HWND const hwnd
    {
        ::CreateWindowExW
        (
            WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW
        ,   sz_class_name
        ,   L"Frame Border"
        ,   WS_POPUP | WS_VISIBLE
        ,   0, 0, ::g_screenX, ::g_screenY
        ,   NULL, NULL, hInstance, NULL
        )
    };
    assert(HWND{} != hwnd);
    UINT_PTR const timer_id{};
    [[maybe_unused]] auto const timer_created{::SetTimer(hwnd, timer_id, COLOR_CYCLE_TIME_MS, nullptr)};
    assert(UINT_PTR{} != timer_created);
    [[maybe_unused]] auto const attributes_set{::SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 255, LWA_COLORKEY)};
    assert(attributes_set);
    for (;;)
    {
        MSG msg;
        if (::GetMessageW(&msg, NULL, 0, 0) <= 0)
        {
            break;
        }
        if (WM_QUIT == msg.message)
        {
            break;
        }
        ::TranslateMessage(&msg);
        ::DispatchMessageW(&msg);
        continue;
    }
    [[maybe_unused]] auto const timer_killed{::KillTimer(hwnd, timer_id)};
    assert(FALSE != timer_killed);
    [[maybe_unused]] auto const destroyed{::DestroyWindow(hwnd)};
    assert(FALSE != destroyed);
    [[maybe_unused]] auto const unregistered{::UnregisterClassW(sz_class_name, HINSTANCE{})};
    assert(FALSE != unregistered);
    return 0;
}

→ Ссылка