Borderless window with shadow
I'm trying to achieve something like a Visual Studio installer using a borderless window and drop shadow:
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);
source to share
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 anyWS_MINIMIZE
,WS_MAXIMIZE
,WS_SYSMENU
. - Call
DwmExtendFrameIntoClientArea()
withMARGINS{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 wParamTRUE
. 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 callDwmExtendFrameIntoClientArea()
. 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
}
source to share