您可以通过使用 和 的组合来创建此效果,DwmExtendFrameIntoClientArea()
作为if wParam is0
的消息结果。详细步骤如下。WM_NCCALCSIZE
TRUE
- 窗口样式应该是这样的,通常会显示整个框架(
WS_CAPTION|WS_POPUP
对我来说效果很好),但不要包括任何WS_MINIMIZE
, WS_MAXIMIZE
, WS_SYSMENU
.
DwmExtendFrameIntoClientArea()
用调用MARGINS{0,0,0,1}
。我们真的不想要一个透明的框架,所以只设置底部边距就足够了。
- 调用
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED)
让系统重新计算NC面积。
WM_NCCALCSIZE
如果 wParam 为,则返回 0 TRUE
。这具有将客户区扩展到包括 frame 在内的窗口大小的效果,但不包括阴影。请参阅文档的备注部分。
- 根据
WM_PAINT
需要绘制框架和内容区域,但请确保对调用定义的边距区域使用不透明的 Alpha 通道(值为 255)DwmExtendFrameIntoClientArea()
。否则,该区域将可见部分常规框架。您可以为此使用 GDI+,因为大多数常规 GDI 函数会忽略 Alpha 通道。BitBlt()
使用包含不透明 alpha 通道的 32bpp 源位图也可以。
WM_NCHITTEST
如果你想要一个可调整大小的窗口,你可以处理。
所有这一切的效果是,由于 DWM 调用,您“覆盖”了现在位于客户区域内的常规窗口框架,但保留了常规窗口阴影。不用担心“涂漆”不会产生任何闪烁,即使您使窗口可调整大小。
您可以将任何标准或用户定义的控件放入此窗口。只要确保子控件不与DwmExtendFrameIntoClientArea()
调用定义的边距重叠,因为大多数基于 GDI 的控件都会忽略 alpha 通道。
这是一个最小的、独立的示例应用程序:
#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:
{
// Setting 0 as the message result when wParam is TRUE removes the
// standard frame, but keeps the window shadow.
if( wParam == TRUE )
{
SetWindowLong( hDlg, DWLP_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,
static_cast<INT>( ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.top ),
static_cast<INT>( ps.rcPaint.right - ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.bottom - ps.rcPaint.top ) );
EndPaint( hDlg, &ps );
return TRUE;
}
case WM_NCHITTEST:
{
// Setting HTCAPTION as the message result allows the user to move
// the window around by clicking anywhere within the window.
// Depending on the mouse coordinates passed in LPARAM, you may
// set other values to enable resizing.
SetWindowLong( hDlg, DWLP_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
}