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 шт):
Архитектура окон опирается на передачу сообщений, так что все основные действия обычно стоит производить в обработчике соотв. сообщения. В данном случае для отрисовки важны 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;
}