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

Posted

技术标签:

【中文标题】创建没有标题栏的窗口,具有可调整大小的边框并且没有虚假的 6px 白色条纹【英文标题】:Create window without titlebar, with resizable border and without bogus 6px white stripe 【发布时间】:2017-02-05 11:44:03 【问题描述】:

我想要一个没有标题栏但有可调整大小的框架和阴影的窗口。 这可以通过删除 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);

渲染:

如何去除白色条纹? 我还发现了这个相关的 Qt 错误报告 QTBUG-47543 已关闭,因为它不是 Qt 问题,因为它可以用 win32 api 重现。

【问题讨论】:

检查以下解决方案:***.com/questions/19919147/… 【参考方案1】:

我认为我们不需要使用 DWM 来移除此边框。此白色顶部调整大小边框属于窗口的非客户区。因此,要删除它,您应该处理与窗口非客户区的大小调整和激活相关的窗口消息,如下所示:(仅在 Win 10 上测试)

  LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  
    /* When we have a custom titlebar in the window, we don't need the non-client area of a normal window
      * to be painted. In order to acheive this, we handle the "WM_NCCALCSIZE" which is responsible for the
      * size of non-client area of a window and set the return value to 0. Also we have to tell the
      * application to not paint this area on activate and deactivation events so we also handle
      * "WM_NCACTIVATE" message. */
      switch( nMsg )
      
      case WM_NCACTIVATE:
      
        /* Returning 0 from this message disable the window from receiving activate events which is not
        desirable. However When a visual style is not active (?) for this window, "lParam" is a handle to an
        optional update region for the nonclient area of the window. If this parameter is set to -1,
        DefWindowProc does not repaint the nonclient area to reflect the state change. */
        lParam = -1;
        break;
      
      /* To remove the standard window frame, you must handle the WM_NCCALCSIZE message, specifically when
      its wParam value is TRUE and the return value is 0 */
      case WM_NCCALCSIZE:
        if( wParam )
        
          /* Detect whether window is maximized or not. We don't need to change the resize border when win is
          *  maximized because all resize borders are gone automatically */
          WINDOWPLACEMENT wPos;
          // GetWindowPlacement fail if this member is not set correctly.
          wPos.length = sizeof( wPos );
          GetWindowPlacement( hWnd, &wPos );
          if( wPos.showCmd != SW_SHOWMAXIMIZED )
          
            RECT borderThickness;
            SetRectEmpty( &borderThickness );
            AdjustWindowRectEx( &borderThickness,
              GetWindowLongPtr( hWnd, GWL_STYLE ) & ~WS_CAPTION, FALSE, NULL );
            borderThickness.left *= -1;
            borderThickness.top *= -1;
            NCCALCSIZE_PARAMS* sz = reinterpret_cast< NCCALCSIZE_PARAMS* >( lParam );
            // Add 1 pixel to the top border to make the window resizable from the top border
            sz->rgrc[ 0 ].top += 1;
            sz->rgrc[ 0 ].left += borderThickness.left;
            sz->rgrc[ 0 ].right -= borderThickness.right;
            sz->rgrc[ 0 ].bottom -= borderThickness.bottom;
            return 0;
          
        
        break;
      
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
  

【讨论】:

此解决方案有效。【参考方案2】:

改变对话框的样式。

LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle |= WS_THICKFRAME; // 6px white stripe cause of this.
lStyle = lStyle & ~WS_CAPTION;

【讨论】:

虽然此代码可能会回答问题,但提供有关其解决问题的方式和/或原因的附加上下文将提高​​答案的长期价值。Read this。 您刚刚从我的原始帖子中复制了 3 行代码。你能展示实际修复它的代码吗? @SergioMartins 删除 WS_THCIFRAME 属性。 这使得窗口不可调整大小,但问题是保持窗口可调整大小【参考方案3】:

只是稍微扩展一下;为了删除白色条纹,只需从 NCCALCSIZE 中的第一个矩形中删除相应的值。 pywin32 代码为:

    if msg == WM_NCCALCSIZE:
        if wParam:
            res = CallWindowProc(
                wndProc, hWnd, msg, wParam, lParam
            )
            sz = NCCALCSIZE_PARAMS.from_address(lParam)
            sz.rgrc[0].top -= 6 # remove 6px top border!
            return res

【讨论】:

在我看来,最好不要使用常量“6”,而是使用函数调用的结果 AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL) ;然后得到border_thickness.top【参考方案4】:

这不是错误。在 Windows 10 中,左/右/下的边框是透明的。上边框不透明。你应该保持原样。可能没有人会抱怨。

要改变它,你必须修改非客户区。这在 Windows Vista 及更高版本中是相当困难的。参考Custom Window Frame Using DWM。

查找边框粗细

使用DwmExtendFrameIntoClientArea 访问非客户区

使用BeginBufferedPaint 在非客户区绘制不透明颜色

Windows 10 示例:

请参阅下一个示例以了解与 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 兼容,请改用此过程。这将在左/上/下边框以及上边框上绘制。此窗口将显示为一个简单的矩形,并调整边框:

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

【讨论】:

您的 Win7 兼容示例在框架周围没有阴影,有什么方法可以得到吗? 您可以添加wc.style = CS_DROPSHADOW; 进行WNDCLASS 注册。这会在右侧和底部留下阴影。要在四周添加阴影,必须手动完成,例如使用 GDI+。 有时我仍然看到白色条纹。捕捉 WM_NCACTIVATE 修复了大多数情况,但有时当快速移动窗口时,它会出现白色条纹。我应该阻止任何其他 Windows 事件吗? @BarmakShemirani 它删除了顶部的白色调整大小边框以及窗口的可调整性!我希望能够调整窗口大小。我正在使用 Win 10。我做错了什么?我正在使用您的代码 @Reza 你可能没有为你的窗口添加WS_THICKFRAME 样式

以上是关于创建没有标题栏的窗口,具有可调整大小的边框并且没有虚假的 6px 白色条纹的主要内容,如果未能解决你的问题,请参考以下文章

在没有框架的python中创建一个QMainWindow,尽管它是可移动和可调整大小的

如何创建一个有边框但没有标题栏的表单? (如 Windows 7 上的音量控制)

创建自定义窗口大小调整功能

如何使图像可调整大小的裁剪框

如何在 Pyqt 中创建可调整大小的布局 UI?

CSS:使 div 可调整大小