【问题标题】:Draw border on top of another application window在另一个应用程序窗口顶部绘制边框
【发布时间】:2018-10-17 15:10:10
【问题描述】:

我需要在另一个应用程序的窗口顶部绘制边框(主要目的是突出显示用户从正在运行的应用程序列表中选择的窗口)。我正在尝试在本机窗口边框顶部绘制边框,但未绘制边框。代码如下:

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

在这个例子中使用了cmd窗口的句柄,但实际上并没有关系。 你能告诉我为什么不画边界以及如何画它吗? 谢谢。

【问题讨论】:

  • 你不能像那样在别人的窗户上画画。您需要创建一个透明窗口,将其放置在与另一个窗口相同的位置,并在其上绘制边框
  • 你的意思是我需要创建一个透明背景的WNDCLASSA对象,然后为这个对象调用RegisterClass,然后把这个窗口放在句柄的窗口上,然后在新创建的窗口上画边框,对吧?
  • 此 API 是否符合您的目的? docs.microsoft.com/en-us/windows/desktop/api/winuser/…
  • @Soonts,看起来不错……甚至很不错,但是可以改变闪烁的颜色吗?
  • 不,颜色取决于 Windows 主题。此外,FlashWindowEx 不适用于自定义窗口,它们会自行绘制标题栏。

标签: c++ winapi gdi


【解决方案1】:

你不能直接在另一个窗口上绘图,因为系统可能随时刷新窗口,覆盖你的绘图。

要使您的绘图持久化,请创建一个layered window,位于另一个窗口的顶部。

  1. 使用WS_EX_LAYERED 标志创建窗口。
  2. 将颜色键传递给SetLayeredWindowAttributes()
  3. 在您的WM_PAINT 处理程序中,使用颜色键绘制矩形的内部(使用它作为画笔)。您使用颜色键绘制的所有内容都将变得透明。用所需颜色绘制矩形的边框(将其用于笔)。

这是一个让您入门的最小示例。框架可以通过拖放来移动。

请注意,没有错误处理以保持示例代码简洁。您应该检查每个 Windows API 调用的返回值。

#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;
}

【讨论】:

  • 感谢您的示例。我编辑了您的代码以使其在 cmd 窗口周围绘制边框,但不幸的是,边框绘制在 cmd 窗口的后面,而不是在它的顶部。我在最初的帖子中添加了我现在没有的代码。我应该改变什么来在 cmd 的窗口顶部绘制边框?
【解决方案2】:

最后我设法用下面的代码解决了这个问题:

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

也许这不是最好的解决方案,但它对我有用。能否请您看一下,看看是否有需要改进的地方?

【讨论】:

  • 还有一个问题,如果另一个窗口在前台,你不能再把控制台窗口放在前面。
  • @zett42,不幸的是你是对的 =( 我可以在最小化后将窗口带到前面,但如果另一个窗口在前台,则不能。任何想法如何解决它?检查窗口是否重叠然后以同样的方式使用这个布尔标志?
  • 我真的没有一个好主意。我会将此作为一个新问题提出:“如何让一个窗口始终位于另一个应用程序窗口的前面”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
相关资源
最近更新 更多