Create window without titlebar, with resizable border and without bogus 6px white stripe
Asked Answered
G

6

24

I want a window without title bar but with resizable frames and shadow. This can easily be achieved by removing WS_CAPTION and adding WS_THICKFRAME, however, since Windows 10, there's a 6px white non-client area.

With the following code I create a window and paint all the client area with black, the window gets a left, right and bottom 6px transparent margins, however the top margin is white.

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

Renders: image showing the problem

How can I remove the white stripe ? I also found this related Qt bug report QTBUG-47543 which was closed as not being a Qt problem, because it can be reproduced with win32 api.

Godfree answered 27/9, 2016 at 18:5 Comment(1)
Check the following solution: #19919647Finegrain
B
15

That's not a bug. In Windows 10 the borders on left/right/bottom are transparent. The top border is not transparent. You should leave it as is. Probably nobody will complain.

To change it, you must modify the non-client area. This is rather difficult in Windows Vista and above. See Custom Window Frame Using DWM for reference.

  • Find border thickness

  • Use DwmExtendFrameIntoClientArea to get access to non-client area

  • Use BeginBufferedPaint to draw opaque color over non-client area

Windows 10 example:

enter image description here

(See the next example for compatibility with 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;
}

For compatibility with Windows Vista/7/8 use this procedure instead. This will paint over left/top/bottom borders as well as top border. This window will appear as a simple rectangle, with resizing borders:

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);
}
Blasien answered 27/9, 2016 at 22:4 Comment(5)
Your Win7 compat example doesn't have shadow around frame, any way to get one ?Godfree
You can add wc.style = CS_DROPSHADOW; for WNDCLASS registration. This drops shadow for right side and bottom side. To add shadows all around it has to be done manually, with GDI+ for example.Blasien
Sometimes I still see the white stripe. Catching WM_NCACTIVATE fixes most of the occurrances, but sometimes when moving the window really fast it gets the white stripe. Any other Windows event I should block ?Godfree
@BarmakShemirani it removes the white resize border on top and also resizability of window! I want to be able to resize the window. I am using Win 10. What I am doing wrong? I am exactly using your codeTheatheaceous
@Theatheaceous you probably didn't add WS_THICKFRAME style for your windowBlasien
R
4

Just to expand on this a little; in order to remove the white stripe one just has to remove the corresponding value from the first rect in NCCALCSIZE. pywin32 code would be:

    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
Ryurik answered 17/6, 2017 at 2:20 Comment(1)
It seems to me that it is better to use not the constant "6", but the result of a function call AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL); and then get border_thickness.topTiffa
T
2

I think we don't need to work with DWM to remove this border. This white top resize border belongs to the non-client area of a window. So for removing it you should handle window messages related to the resizing and activating of non-client area of a window like below: ( tested only on 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 achieve 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);
  }
Theatheaceous answered 22/11, 2021 at 8:24 Comment(0)
F
0
case WM_NCCALCSIZE: {
    // This must always be last.
    NCCALCSIZE_PARAMS* sz = reinterpret_cast<NCCALCSIZE_PARAMS*>(lparam);

    // Add 8 pixel to the top border when maximized so the app isn't cut off

        // on windows 10, if set to 0, there's a white line at the top
        // of the app and I've yet to find a way to remove that.
    sz->rgrc[0].top += 1;
    sz->rgrc[0].right -= 8;
    sz->rgrc[0].bottom -= 8;
    sz->rgrc[0].left -= -8;
     
    // Previously (WVR_HREDRAW | WVR_VREDRAW), but returning 0 or 1 doesn't
    // actually break anything so I've set it to 0. Unless someone pointed a
    // problem in the future.
    return 0;
}
Frostwork answered 8/2, 2023 at 2:51 Comment(0)
C
0

Based on @blameless75 solution for WinUi3

I use Microsoft.Windows.CsWin32 to generate PInvoke methods.

NativeMethods.txt

NCCALCSIZE_PARAMS
AdjustWindowRectEx
GetWindowLongPtr
SetLastError
SetWindowLong
SetWindowLongPtr

Win32Helper method

public static IntPtr SetWindowLong(IntPtr hWnd, WINDOW_LONG_PTR_INDEX nIndex, IntPtr dwNewLong)
{
    int error = 0;
    IntPtr result = IntPtr.Zero;
    // Win32 SetWindowLong doesn't clear error on success
    PInvoke.SetLastError(0);

#if x86
    int tempResult = PInvoke.SetWindowLong((Windows.Win32.Foundation.HWND)hWnd, nIndex, IntPtrToInt32(dwNewLong));
    error = Marshal.GetLastWin32Error();
    result = new IntPtr(tempResult);
#endif

#if x64 || ARM64

    result = PInvoke.SetWindowLongPtr((Windows.Win32.Foundation.HWND)hWnd, nIndex, dwNewLong);
    error = Marshal.GetLastWin32Error();
#endif
    if (result == IntPtr.Zero && error != 0)
    {
        throw new System.ComponentModel.Win32Exception(error);
    }

Window

using Microsoft.UI.Xaml;
using System;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Demo
{
    public class PopupWindow : Window
    {
        private const int WM_NCCALCSIZE = 0x0083;
        
        private IntPtr setWindowLongWndProc;
        private WNDPROC wndProc;
        RECT rect = new RECT();
        public PopupWindow(): base()
        {
            var hWndMain = WinRT.Interop.WindowNative.GetWindowHandle(this);
            var style = (WINDOW_STYLE)PInvoke.GetWindowLongPtr(new HWND(hWndMain), WINDOW_LONG_PTR_INDEX.GWL_STYLE);            
            PInvoke.AdjustWindowRectEx(ref rect, style & ~WINDOW_STYLE.WS_CAPTION, false, 0);

            wndProc = WNDPROC;
            setWindowLongWndProc = Win32Helper.SetWindowLong(hWndMain, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(wndProc));            
            // hide title bar or create own here
        }

        private LRESULT WNDPROC(HWND param0, uint paraml, WPARAM param2, LPARAM param3)
        {
            switch (paraml)
            {
                case WM_NCCALCSIZE:
                    if (param2.Value == 1)
                    {
                        var structure = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(param3);                        
                        structure.rgrc._0.top += rect.top;
                        Marshal.StructureToPtr(structure, param3, false);
                    }                                    
                    break;
            }

            return PInvoke.CallWindowProc((WNDPROC)Marshal.GetDelegateForFunctionPointer(setWindowLongWndProc, typeof(WNDPROC)), param0, paraml, param2, param3);
        }
    }
}
Caseous answered 27/9, 2023 at 10:20 Comment(0)
D
-1

Change the style of dialog.

LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle |= WS_THICKFRAME; // 6px white stripe cause of this.
lStyle = lStyle & ~WS_CAPTION;
Donnetta answered 13/3, 2019 at 4:55 Comment(5)
While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.Read this.Deceit
You just copied 3 lines of code from my original post. Can you show the code that will actually fix it ?Godfree
@SergioMartins remove WS_THCIFRAME property.Donnetta
That makes the window non resizable, but the question was to keep the window resizableAulic
While this removes the annoying horizontal border just from the top of the window, you're still unable to resize the window manually.Regardful

© 2022 - 2025 — McMap. All rights reserved.