Borderless window with shadow

I'm trying to achieve something like a Visual Studio installer using a borderless window and drop shadow:

screenshot

I've tried various options like the CS_DROPSHADOW

DWM API, but as soon as I apply the style WS_THICKFRAME

, the shadow disappears.

This is my code for creating and centering the window:

RECT R = {0, 0, _clientWidth, _clientHeight};
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
_mainWnd = CreateWindow(L"D3DWndClassName", _mainWndCaption.c_str(), WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom, nullptr, nullptr, _appInst, nullptr);

if(!_mainWnd){
    MessageBox(nullptr, L"CreateWindow FAILED", nullptr, 0);
    PostQuitMessage(0);
}

RECT rc;

GetWindowRect(_mainWnd, &rc);

LONG lStyle = GetWindowLong(_mainWnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU );
SetWindowLong(_mainWnd, GWL_STYLE, lStyle);


int xPos = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;

SetWindowPos(_mainWnd, 0, xPos, yPos, _clientWidth, _clientHeight, SWP_NOZORDER);

ShowWindow(_mainWnd, SW_SHOW);
UpdateWindow(_mainWnd);

      

+3


source to share


1 answer


You can create this effect using a combination DwmExtendFrameIntoClientArea()

and returning 0

from WM_NCCALCSIZE

if wParam TRUE

. Detailed steps below.

  • Style windows should be such that it will normally be displayed full frame ( WS_CAPTION|WS_POPUP

    well for me), but does not include any WS_MINIMIZE

    , WS_MAXIMIZE

    , WS_SYSMENU

    .
  • Call DwmExtendFrameIntoClientArea()

    with MARGINS{0,0,0,1}

    . We don't need a transparent frame, so just set the bottom margin.
  • Call SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED)

    for the system to recalculate the NC area.
  • Returns 0 from WM_NCCALCSIZE

    if wParam TRUE

    . This affects the expansion of the client area to the size of the window, including the frame but excluding the shadow. See the notes section of the documentation.
  • In WM_PAINT

    place the frame and content area as you wish, but be sure to use an opaque alpha channel (value 255) for the area of ​​the box defined by the call DwmExtendFrameIntoClientArea()

    . Otherwise, part of the regular frame will be visible in this area. You can use GDI + for this, since most normal GDI functions ignore the alpha channel. BitBlt()

    with a 32 bit original bit containing an opaque alpha channel also works.
  • You can handle WM_NCHITTEST

    if you want to resize the window.

The effect of all of this is that you paint "over" the normal window frame, which is now inside the client area due to DWM calls, but retains the normal window shadow . Don't worry, the paint does not flicker even if you resize the window.



Any standard or custom controls can be placed in this window. Just make sure the child controls do not overlap the border defined by the call DwmExtendFrameIntoClientArea()

, because most GDI-based controls ignore the alpha channel.

Here is a minimal, self-contained example application:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dwmapi.h>
#include <unknwn.h>
#include <gdiplus.h>
#pragma comment( lib, "dwmapi" )
#pragma comment( lib, "gdiplus" )
namespace gdip = Gdiplus;

INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam );

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // Initialize GDI+
    gdip::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdipToken = 0;
    gdip::GdiplusStartup( &gdipToken, &gdiplusStartupInput, nullptr );

    struct MyDialog : DLGTEMPLATE {
        WORD dummy[3] = { 0 };  // unused menu, class and title
    }
    dlg;
    dlg.style = WS_POPUP|WS_CAPTION|DS_CENTER;
    dlg.dwExtendedStyle = 0;
    dlg.cdit = 0;  // no controls in template
    dlg.x = 0;
    dlg.y = 0;
    dlg.cx = 300;  // width in dialog units
    dlg.cy = 200;  // height in dialog units

    DialogBoxIndirectW( hInstance, &dlg, nullptr, MyDialogProc );

    gdip::GdiplusShutdown( gdipToken );

    return 0;
}

INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
        case WM_INITDIALOG:
        {
            SetWindowTextW( hDlg, L"Borderless Window with Shadow" );

            // This plays together with WM_NCALCSIZE.
            MARGINS m{ 0, 0, 0, 1 };
            DwmExtendFrameIntoClientArea( hDlg, &m );

            // Force the system to recalculate NC area (making it send WM_NCCALCSIZE).
            SetWindowPos( hDlg, nullptr, 0, 0, 0, 0, 
                SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED);
            return TRUE;
        }
        case WM_NCCALCSIZE:
        {
            // Returning 0 from the message when wParam is TRUE removes the standard
            // frame, but keeps the window shadow.
            if( wParam == TRUE )
            {
                SetWindowLong( hDlg, DWL_MSGRESULT, 0 ); 
                return TRUE;
            }
            return FALSE;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps{ 0 };
            HDC hdc = BeginPaint( hDlg, &ps );

            // Draw with GDI+ to make sure the alpha channel is opaque.
            gdip::Graphics gfx{ hdc };
            gdip::SolidBrush brush{ gdip::Color{ 255, 255, 255 } };
            gfx.FillRectangle( &brush, ps.rcPaint.left, ps.rcPaint.top, 
                ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top );

            EndPaint( hDlg, &ps );
            return TRUE;
        }
        case WM_NCHITTEST:
        {
            // Returning HTCAPTION allows the user to move the window around by clicking 
            // anywhere.
            // Depending on the mouse coordinates passed in LPARAM, you may 
            // return other values to enable resizing.
            SetWindowLong( hDlg, DWL_MSGRESULT, HTCAPTION ); 
            return TRUE;
        }
        case WM_COMMAND:
        {
            WORD id = LOWORD(wParam);
            if( id == IDOK || id == IDCANCEL )
            {
                EndDialog( hDlg, id );
                return TRUE;
            }
            return FALSE;
        }
    }
    return FALSE; // return FALSE to let DefDialogProc handle the message
}

      

+6


source







All Articles