winapi - 创建没有标题栏、可调整大小边框且没有虚假 6px 白色条纹的窗口

标签 winapi windows-10 paint gdi

我想要一个没有标题栏但有可调整大小的框架和阴影的窗口。
这可以通过删除 WS_CAPTION 并添加 WS_THICKFRAME 轻松实现,但是,从 Windows 10 开始,有一个 6px 的白色非客户区。

使用以下代码,我创建了一个窗口并用黑色绘制所有客户区,窗口获得左、右和底部 6px 的透明边距,但顶部边距为白色。

#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[]  = L"Sample Window Class";

    WNDCLASS wc = { };

    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window.

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        L"",    // Window text
                0,
        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
        );

    ShowWindow(hwnd, nCmdShow);

    LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
    lStyle |= WS_THICKFRAME;
    lStyle = lStyle & ~WS_CAPTION;
    SetWindowLong(hwnd, GWL_STYLE, lStyle);
    SetWindowPos(hwnd, NULL, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);

    // Run the message loop.

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

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);


            // Paint everything black
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOWTEXT));
            EndPaint(hwnd, &ps);
        }
        return 0;

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

渲染:
image showing the problem

我怎样才能去除白色条纹?
我还发现了这个相关的 Qt 错误报告 QTBUG-47543由于不是 Qt 问题而被关闭,因为它可以用 win32 api 重现。

最佳答案

那不是错误。在 Windows 10 中,左/右/底部的边框是透明的。顶部边框不透明。你应该保持原样。可能没有人会提示。

要更改它,您必须修改非客户区。这在 Windows Vista 及更高版本中相当困难。见 Custom Window Frame Using DWM以供引用。

  • 查找边框粗细
  • 使用 DwmExtendFrameIntoClientArea访问非客户区
  • 使用 BeginBufferedPaint在非客户区绘制不透明颜色

  • Windows 10 示例:

    enter image description here

    (有关与 Windows Vista、7、8 的兼容性,请参阅下一个示例)
    //requires Dwmapi.lib and UxTheme.lib
    #include <Windows.h>
    #include <Dwmapi.h>
    
    void my_paint(HDC hdc, RECT rc)
    {
        HBRUSH brush = CreateSolidBrush(RGB(0, 128, 0));
        FillRect(hdc, &rc, brush);
        DeleteObject(brush);
    }
    
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static RECT border_thickness;
    
        switch (uMsg)
        {
        case WM_CREATE:
        {
            //find border thickness
            SetRectEmpty(&border_thickness);
            if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_THICKFRAME)
            {
                AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
                border_thickness.left *= -1;
                border_thickness.top *= -1;
            }
            else if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_BORDER)
            {
                SetRect(&border_thickness, 1, 1, 1, 1);
            }
    
            MARGINS margins = { 0 };
            DwmExtendFrameIntoClientArea(hwnd, &margins);
            SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
            break;
        }
    
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
    
            RECT rc = ps.rcPaint;
            BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE };
            HDC memdc;
            HPAINTBUFFER hbuffer = BeginBufferedPaint(hdc, &rc, BPBF_TOPDOWNDIB, &params, &memdc);
    
            my_paint(memdc, rc);
    
            BufferedPaintSetAlpha(hbuffer, &rc, 255);
            EndBufferedPaint(hbuffer, TRUE);
    
            EndPaint(hwnd, &ps);
            return 0;
        }
    
        case WM_NCACTIVATE:
            return 0;
    
        case WM_NCCALCSIZE:
            if (lParam)
            {
                NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lParam;
                sz->rgrc[0].left += border_thickness.left;
                sz->rgrc[0].right -= border_thickness.right;
                sz->rgrc[0].bottom -= border_thickness.bottom;
                return 0;
            }
            break;
    
        case WM_NCHITTEST:
        {
            //do default processing, but allow resizing from top-border
            LRESULT result = DefWindowProc(hwnd, uMsg, wParam, lParam);
            if (result == HTCLIENT)
            {
                POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
                ScreenToClient(hwnd, &pt);
                if (pt.y < border_thickness.top) return HTTOP;
            }
            return result;
        }
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    
        }
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    
    int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int)
    {
        const wchar_t CLASS_NAME[] = L"Sample Window Class";
    
        WNDCLASS wc = {};
        wc.lpfnWndProc = WindowProc;
        wc.hInstance = hInstance;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.lpszClassName = CLASS_NAME;
        RegisterClass(&wc);
    
        CreateWindowEx(0, CLASS_NAME,   NULL,
            WS_VISIBLE | WS_THICKFRAME | WS_POPUP,
            10, 10, 600, 400, NULL, NULL, hInstance, NULL);
    
        MSG msg = {};
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return 0;
    }
    

    为了与 Windows Vista/7/8 兼容,请改用此过程。这将绘制左/上/下边框以及上边框。此窗口将显示为一个简单的矩形,并带有调整边框的大小:

    enter image description here
    //for Windows Vista, 7, 8, 10
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static RECT border_thickness;
    
        switch (uMsg)
        {
        case WM_CREATE:
        {
            //find border thickness
            SetRectEmpty(&border_thickness);
            if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_THICKFRAME)
            {
                AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
                border_thickness.left *= -1;
                border_thickness.top *= -1;
            }
            else if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_BORDER)
            {
                SetRect(&border_thickness, 1, 1, 1, 1);
            }
    
            MARGINS margins = { 0 };
            DwmExtendFrameIntoClientArea(hwnd, &margins);
            SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
            break;
        }
    
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
    
            RECT rc = ps.rcPaint;
            BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE };
            HDC memdc;
            HPAINTBUFFER hbuffer = BeginBufferedPaint(hdc, &rc, BPBF_TOPDOWNDIB, &params, &memdc);
    
            my_paint(memdc, rc);
    
            BufferedPaintSetAlpha(hbuffer, &rc, 255);
            EndBufferedPaint(hbuffer, TRUE);
    
            EndPaint(hwnd, &ps);
            return 0;
        }
    
        case WM_NCACTIVATE:
            return 0;
    
        case WM_NCCALCSIZE:
            if (lParam)
                return 0;
    
        case WM_NCHITTEST:
        {
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            ScreenToClient(hwnd, &pt);
            RECT rc;
            GetClientRect(hwnd, &rc);
            enum {left=1, top=2, right=4, bottom=8};
            int hit = 0;
            if (pt.x < border_thickness.left) hit |= left;
            if (pt.x > rc.right - border_thickness.right) hit |= right;
            if (pt.y < border_thickness.top) hit |= top;
            if (pt.y > rc.bottom - border_thickness.bottom) hit |= bottom;
    
            if (hit & top && hit & left) return HTTOPLEFT;
            if (hit & top && hit & right) return HTTOPRIGHT;
            if (hit & bottom && hit & left) return HTBOTTOMLEFT;
            if (hit & bottom && hit & right) return HTBOTTOMRIGHT;
            if (hit & left) return HTLEFT;
            if (hit & top) return HTTOP;
            if (hit & right) return HTRIGHT;
            if (hit & bottom) return HTBOTTOM;
    
            return HTCLIENT;
        }
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    
        }
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    

    关于winapi - 创建没有标题栏、可调整大小边框且没有虚假 6px 白色条纹的窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39731497/

    相关文章:

    c++ - 为什么 sleep 功能会禁用我的互斥体

    windows - 找不到任何 Direct3D 12 示例

    qt - 动态 QImage ,当没有指定初始大小时

    Android FingerPaint 撤消/重做实现

    c++ - unicode 窗口中的非 unicode WM_CHAR

    c - 在 Windows 中临时捕获控制台应用程序的标准输出

    c++ - 从另一个进程重新启动一个进程

    c# - UWP 如何绑定(bind)两个值?

    perl - 如何使用 perl 在 Windows 10 上获取 OsVersion

    android - 如何用手指在图像上删除油漆?