如何解决可以在 Window 类上调整 CS_DROPSHADOW 的偏移量吗?
我想要的是为我的无框窗口添加阴影效果。我使用 CS_DROPSHADOW 作为窗口样式。
int CALLBACK WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nCmdshow){
//register window
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_DROPSHADOW; //enable dropshadow
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground = (HBrush)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
MessageBox(NULL,_T("Call to RegisterClassEx Failed!"),szTitle,NULL);
return 1;
}
hInst = hInstance;
HWND hWnd = CreateWindow(szWindowClass,(WS_POPUP),450,500,NULL,hInstance,NULL); //using WS_POPUP for the sake of CS_DROPSHADOW
SetwindowLong(hWnd,GWL_STYLE,0); //Remove all border style
RECT rc;
GetwindowRect(hWnd,&rc);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
SetwindowPos(hWnd,xPos,yPos,SWP_NOZORDER); //Center the Window
if (!hWnd){
MessageBox(NULL,_T("Call to CreateWindow Failed!"),NULL);
return 1;
}
ShowWindow(hWnd,nCmdshow);
UpdateWindow(hWnd);
// Main message loop:
MSG msg;
while (GetMessage(&msg,0)){
TranslateMessage(&msg);
dispatchMessage(&msg);
}
return (int)msg.wParam;
}
问题是用CS_DROPSHADOW,我只对Window的BottOM|RIGHT
有效果,我希望四个面都一样的效果。我想如果我可以调整它的偏移量可能是可能的,但我不确定。
最初,我想要的确切输出是一个 white plain title-bar
,只有 Window Close Button
和 shadows on the Window Frame
,所以没有标题,没有最小化,也没有最大化按钮。我能够删除最大化、最小化和标题,但无法将标题栏的颜色设置为纯白色。
我很确定这可以通过设置阴影效果的偏移量在 QT 中用 4 行代码完成。也可以在 C#
上通过覆盖受保护表单的 CreateParams
并将 CS_DROPSHADOW
添加到类样式。
我对这个 Win32-API 不太熟悉,因此非常感谢您的帮助。
解决方法
我认为您真正想要的是自定义标题栏,这并不容易。您需要为此处理所有功能,例如拖动事件。
幸运的是,我们现在有了一个名为 Custom Window Frame Using DWM 的东西,您将能够自定义窗口的框架。
Here's 一个很好的例子,例子基于控制台子系统,归功于 melak47。
如果您想自定义按钮,您可以查看 here 中已接受的回答。
综上所述,这是一个使用 Windows 子系统的示例。
BorderlessWindow.hpp
#pragma once
#include <memory>
#include <string>
#include <Windows.h>
struct hwnd_deleter {
using pointer = HWND;
auto operator()(HWND handle) const -> void {
::DestroyWindow(handle);
}
};
using unique_handle = std::unique_ptr<HWND,hwnd_deleter>;
class BorderlessWindow {
public:
BorderlessWindow();
auto set_borderless(bool enabled) -> void;
auto set_borderless_shadow(bool enabled) -> void;
private:
static auto CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam) noexcept -> LRESULT;
auto hit_test(POINT cursor) const->LRESULT;
bool borderless = true; // is the window currently borderless
bool borderless_resize = false; // should the window allow resizing by dragging the borders while borderless
bool borderless_drag = true; // should the window allow moving my dragging the client area
bool borderless_shadow = true; // should the window display a native aero shadow while borderless
unique_handle handle;
};
Main.cpp
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include "BorderlessWindow.hpp"
#include <stdexcept>
#include <system_error>
#include <Windows.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <string.h>
#include <tchar.h>
#pragma comment(lib,"dwmapi.lib")
#define ID_EXIT_BUTTON 101
#define ID_MINIMIZED_BUTTON 102
namespace {
// we cannot just use WS_POPUP style
// WS_THICKFRAME: without this the window cannot be resized and so aero snap,de-maximizing and minimizing won't work
// WS_SYSMENU: enables the context menu with the move,close,maximize,minize... commands (shift + right-click on the task bar item)
// WS_CAPTION: enables aero minimize animation/transition
// WS_MAXIMIZEBOX,WS_MINIMIZEBOX: enable minimize/maximize
enum class Style : DWORD {
windowed = WS_POPUP | WS_CAPTION | WS_BORDER,aero_borderless = WS_POPUP | WS_CAPTION | WS_BORDER,basic_borderless = WS_POPUP | WS_CAPTION | WS_BORDER
};
auto maximized(HWND hwnd) -> bool {
WINDOWPLACEMENT placement;
if (!::GetWindowPlacement(hwnd,&placement)) {
return false;
}
return placement.showCmd == false;
}
/* Adjust client rect to not spill over monitor edges when maximized.
* rect(in/out): in: proposed window rect,out: calculated client rect
* Does nothing if the window is not maximized.
*/
auto adjust_maximized_client_rect(HWND window,RECT& rect) -> void {
if (!maximized(window)) {
return;
}
auto monitor = ::MonitorFromWindow(window,MONITOR_DEFAULTTONULL);
if (!monitor) {
return;
}
MONITORINFO monitor_info{};
monitor_info.cbSize = sizeof(monitor_info);
if (!::GetMonitorInfoW(monitor,&monitor_info)) {
return;
}
// when maximized,make the client area fill just the monitor (without task bar) rect,// not the whole window rect which extends beyond the monitor.
rect = monitor_info.rcWork;
}
auto last_error(const std::string& message) -> std::system_error {
return std::system_error(
std::error_code(::GetLastError(),std::system_category()),message
);
}
auto window_class(WNDPROC wndproc) -> const wchar_t* {
static const wchar_t* window_class_name = [&] {
WNDCLASSEXW wcx{};
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.hInstance = nullptr;
wcx.lpfnWndProc = wndproc;
wcx.lpszClassName = L"TSAWCLASS";
wcx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcx.hCursor = ::LoadCursorW(nullptr,IDC_ARROW);
const ATOM result = ::RegisterClassExW(&wcx);
if (!result) {
throw last_error("failed to register window class");
}
return wcx.lpszClassName;
}();
return window_class_name;
}
auto composition_enabled() -> bool {
BOOL composition_enabled = FALSE;
bool success = ::DwmIsCompositionEnabled(&composition_enabled) == S_OK;
return composition_enabled && success;
}
auto select_borderless_style() -> Style {
return composition_enabled() ? Style::aero_borderless : Style::basic_borderless;
}
auto set_shadow(HWND handle,bool enabled) -> void {
if (composition_enabled()) {
static const MARGINS shadow_state[2]{ { 0,0 },{ 1,1,1 } };
::DwmExtendFrameIntoClientArea(handle,&shadow_state[enabled]);
}
}
auto create_window(WNDPROC wndproc,void* userdata) -> unique_handle {
auto handle = CreateWindowExW(
0,window_class(wndproc),L"Here is the title of the Window,will only show on the taskbar.",static_cast<DWORD>(Style::aero_borderless),CW_USEDEFAULT,450,500,nullptr,userdata
);
if (!handle) {
throw last_error("failed to create window");
}
return unique_handle{ handle };
}
}
BorderlessWindow::BorderlessWindow()
: handle{ create_window(&BorderlessWindow::WndProc,this) }
{
set_borderless(borderless);
set_borderless_shadow(borderless_shadow);
::ShowWindow(handle.get(),SW_SHOW);
}
void BorderlessWindow::set_borderless(bool enabled) {
Style new_style = (enabled) ? select_borderless_style() : Style::windowed;
Style old_style = static_cast<Style>(::GetWindowLongPtrW(handle.get(),GWL_STYLE));
if (new_style != old_style) {
borderless = enabled;
::SetWindowLongPtrW(handle.get(),GWL_STYLE,static_cast<LONG>(new_style));
// when switching between borderless and windowed,restore appropriate shadow state
set_shadow(handle.get(),borderless_shadow && (new_style != Style::windowed));
//set window to center
RECT rc;
GetWindowRect(handle.get(),&rc);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
//SetWindowLong(handle.get(),WS_MAXIMIZEBOX);
SetWindowPos(handle.get(),xPos,yPos,SWP_NOZORDER);
// redraw frame
::SetWindowPos(handle.get(),SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
::ShowWindow(handle.get(),SW_SHOW);
}
}
void BorderlessWindow::set_borderless_shadow(bool enabled) {
if (borderless) {
borderless_shadow = enabled;
set_shadow(handle.get(),enabled);
}
}
HFONT CreateTitleBarButton(HWND hwnd) {
static HFONT s_hFont = NULL;
const TCHAR* fontName = _T("Calibri (Body)"); //you can set custom font name here.
const long nFontSize = 18;
HDC hdc = GetDC(hwnd);
LOGFONT logFont = { 0 };
logFont.lfHeight = -MulDiv(nFontSize,GetDeviceCaps(hdc,LOGPIXELSY),72);
logFont.lfWeight = FW_REGULAR;
_tcscpy_s(logFont.lfFaceName,fontName);
s_hFont = CreateFontIndirect(&logFont);
ReleaseDC(hwnd,hdc);
HWND btn_minimized = CreateWindowEx(NULL,TEXT("button"),TEXT("\u2212"),WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,379,-1,35,28,hwnd,(HMENU)ID_MINIMIZED_BUTTON,NULL,NULL);
HWND btn_exit = CreateWindowEx(NULL,TEXT("\u00D7"),415,(HMENU)ID_EXIT_BUTTON,NULL);
SendMessage(btn_exit,WM_SETFONT,(WPARAM)s_hFont,(LPARAM)MAKELONG(TRUE,0));
SendMessage(btn_minimized,0));
return s_hFont;
}
auto CALLBACK BorderlessWindow::WndProc(HWND hwnd,LPARAM lparam) noexcept -> LRESULT {
static HFONT FONT_TITLEBAR = NULL;
if (msg == WM_NCCREATE) {
auto userdata = reinterpret_cast<CREATESTRUCTW*>(lparam)->lpCreateParams;
// store window instance pointer in window user data
::SetWindowLongPtrW(hwnd,GWLP_USERDATA,reinterpret_cast<LONG_PTR>(userdata));
}
if (auto window_ptr = reinterpret_cast<BorderlessWindow*>(::GetWindowLongPtrW(hwnd,GWLP_USERDATA))) {
auto& window = *window_ptr;
switch (msg) {
case WM_NCCALCSIZE: {
if (wparam == TRUE && window.borderless) {
auto& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(lparam);
adjust_maximized_client_rect(hwnd,params.rgrc[0]);
return 0;
}
break;
}
case WM_NCHITTEST: {
// When we have no border or title bar,we need to perform our
// own hit testing to allow resizing and moving.
if (window.borderless) {
return window.hit_test(POINT{
GET_X_LPARAM(lparam),GET_Y_LPARAM(lparam)
});
}
break;
}
case WM_NCACTIVATE: {
if (!composition_enabled()) {
// Prevents window frame reappearing on window activation
// in "basic" theme,where no aero shadow is present.
return 1;
}
break;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc;
TCHAR greeting[] = _T("Hello,Windows desktop!");
hdc = BeginPaint(hwnd,&ps);
TextOut(hdc,5,greeting,_tcslen(greeting));
EndPaint(hwnd,&ps);
break;
}
case WM_CREATE: {
FONT_TITLEBAR = CreateTitleBarButton(hwnd);
break;
}
case WM_NOTIFY: {
static HBRUSH BRUSH_BUTTON_MINIMIZED_NORMAL = NULL;
static HBRUSH BRUSH_BUTTON_MINIMIZED_HOVER = NULL;
static HBRUSH BRUSH_BUTTON_MINIMIZED_CLICKED = NULL;
static HBRUSH BRUSH_BUTTON_EXIT_NORMAL = NULL;
static HBRUSH BRUSH_BUTTON_EXIT_HOVER = NULL;
static HBRUSH BRUSH_BUTTON_EXIT_CLICKED = NULL;
LPNMHDR some_item = (LPNMHDR)lparam;
if (some_item->idFrom == ID_EXIT_BUTTON && some_item->code == NM_CUSTOMDRAW) {
LPNMCUSTOMDRAW item = (LPNMCUSTOMDRAW)some_item;
if (item->uItemState & CDIS_SELECTED) {
if (BRUSH_BUTTON_EXIT_CLICKED == NULL)
BRUSH_BUTTON_EXIT_CLICKED = CreateSolidBrush(RGB(227,61,61));
//Select our color when the button is selected
if (BRUSH_BUTTON_EXIT_CLICKED == NULL)
BRUSH_BUTTON_EXIT_CLICKED = CreateSolidBrush(RGB(227,61));
//Create pen for button border
HPEN pen = CreatePen(PS_INSIDEFRAME,RGB(227,61));
//Select our brush into hDC
HGDIOBJ old_pen = SelectObject(item->hdc,pen);
HGDIOBJ old_brush = SelectObject(item->hdc,BRUSH_BUTTON_EXIT_CLICKED);
//If you want rounded button,then use this,otherwise use FillRect().
RoundRect(item->hdc,item->rc.left,item->rc.top,item->rc.right,item->rc.bottom,0);
//Clean up
SelectObject(item->hdc,old_pen);
SelectObject(item->hdc,old_brush);
DeleteObject(pen);
//Now,I don't want to do anything else myself (draw text) so I use this value for return:
return CDRF_DODEFAULT;
} else {
if (item->uItemState & CDIS_HOT) //Our mouse is over the button
{
if (BRUSH_BUTTON_EXIT_HOVER == NULL)
BRUSH_BUTTON_EXIT_HOVER = CreateSolidBrush(RGB(227,61));
HPEN pen = CreatePen(PS_INSIDEFRAME,61));
HGDIOBJ old_pen = SelectObject(item->hdc,pen);
HGDIOBJ old_brush = SelectObject(item->hdc,BRUSH_BUTTON_EXIT_HOVER);
RoundRect(item->hdc,0);
SelectObject(item->hdc,old_pen);
SelectObject(item->hdc,old_brush);
DeleteObject(pen);
return CDRF_DODEFAULT;
}
//Select our color when our button is doing nothing
if (BRUSH_BUTTON_EXIT_NORMAL == NULL)
BRUSH_BUTTON_EXIT_NORMAL = CreateSolidBrush(RGB(255,255,255));
HPEN pen = CreatePen(PS_INSIDEFRAME,RGB(255,255));
HGDIOBJ old_pen = SelectObject(item->hdc,BRUSH_BUTTON_EXIT_NORMAL);
RoundRect(item->hdc,0);
SelectObject(item->hdc,old_brush);
DeleteObject(pen);
return CDRF_DODEFAULT;
}
}
else if (some_item->idFrom == ID_MINIMIZED_BUTTON && some_item->code == NM_CUSTOMDRAW) {
LPNMCUSTOMDRAW item = (LPNMCUSTOMDRAW)some_item;
if (item->uItemState & CDIS_SELECTED) {
if (BRUSH_BUTTON_MINIMIZED_CLICKED == NULL)
BRUSH_BUTTON_MINIMIZED_CLICKED = CreateSolidBrush(RGB(232,232,232));//CreateGradientBrush(RGB(180,0),180,item);
//Select our color when the button is selected
if (BRUSH_BUTTON_MINIMIZED_CLICKED == NULL)
BRUSH_BUTTON_MINIMIZED_CLICKED = CreateSolidBrush(RGB(232,232));
//Create pen for button border
HPEN pen = CreatePen(PS_INSIDEFRAME,RGB(232,232));
//Select our brush into hDC
HGDIOBJ old_pen = SelectObject(item->hdc,BRUSH_BUTTON_MINIMIZED_CLICKED);
//If you want rounded button,I don't want to do anything else myself (draw text) so I use this value for return:
return CDRF_DODEFAULT;
}
else {
if (item->uItemState & CDIS_HOT) //Our mouse is over the button
{
if (BRUSH_BUTTON_MINIMIZED_HOVER == NULL)
BRUSH_BUTTON_MINIMIZED_HOVER = CreateSolidBrush(RGB(214,214,214));
HPEN pen = CreatePen(PS_INSIDEFRAME,RGB(214,214));
HGDIOBJ old_pen = SelectObject(item->hdc,BRUSH_BUTTON_MINIMIZED_HOVER);
RoundRect(item->hdc,old_brush);
DeleteObject(pen);
return CDRF_DODEFAULT;
}
//Select our color when our button is doing nothing
if (BRUSH_BUTTON_MINIMIZED_NORMAL == NULL)
BRUSH_BUTTON_MINIMIZED_NORMAL = CreateSolidBrush(RGB(255,BRUSH_BUTTON_MINIMIZED_NORMAL);
RoundRect(item->hdc,old_brush);
DeleteObject(pen);
return CDRF_DODEFAULT;
}
}
break;
}
case WM_COMMAND: {
if (LOWORD(wparam) == ID_MINIMIZED_BUTTON) {
ShowWindow(hwnd,SW_MINIMIZE);
}else if (LOWORD(wparam) == ID_EXIT_BUTTON) {
SendMessage(hwnd,WM_CLOSE,0);
}
break;
}
case WM_CLOSE: {
::DestroyWindow(hwnd);
return 0;
}
case WM_DESTROY: {
DeleteObject(FONT_TITLEBAR);
PostQuitMessage(0);
return 0;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN: {
switch (wparam) {
case VK_F8: { window.borderless_drag = !window.borderless_drag; return 0; }
case VK_F9: { window.borderless_resize = !window.borderless_resize; return 0; }
case VK_F10: { window.set_borderless(!window.borderless); return 0; }
case VK_F11: { window.set_borderless_shadow(!window.borderless_shadow); return 0; }
}
break;
}
}
}
return ::DefWindowProcW(hwnd,msg,wparam,lparam);
}
auto BorderlessWindow::hit_test(POINT cursor) const -> LRESULT {
// identify borders and corners to allow resizing the window.
// Note: On Windows 10,windows behave differently and
// allow resizing outside the visible window frame.
// This implementation does not replicate that behavior.
const POINT border{
::GetSystemMetrics(SM_CXFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER),::GetSystemMetrics(SM_CYFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)
};
RECT window;
if (!::GetWindowRect(handle.get(),&window)) {
return HTNOWHERE;
}
const auto drag = borderless_drag ? HTCAPTION : HTCLIENT;
enum region_mask {
client = 0b0000,left = 0b0001,right = 0b0010,top = 0b0100,bottom = 0b1000,};
const auto result =
left * (cursor.x < (window.left + border.x)) |
right * (cursor.x >= (window.right - border.x)) |
top * (cursor.y < (window.top + border.y)) |
bottom * (cursor.y >= (window.bottom - border.y));
switch (result) {
case left: return borderless_resize ? HTLEFT : drag;
case right: return borderless_resize ? HTRIGHT : drag;
case top: return borderless_resize ? HTTOP : drag;
case bottom: return borderless_resize ? HTBOTTOM : drag;
case top | left: return borderless_resize ? HTTOPLEFT : drag;
case top | right: return borderless_resize ? HTTOPRIGHT : drag;
case bottom | left: return borderless_resize ? HTBOTTOMLEFT : drag;
case bottom | right: return borderless_resize ? HTBOTTOMRIGHT : drag;
case client: return drag;
default: return HTNOWHERE;
}
}
int CALLBACK WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nCmdShow) {
try {
BorderlessWindow window;
MSG msg;
while (::GetMessageW(&msg,0) == TRUE) {
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
}
catch (const std::exception& e) {
::MessageBoxA(nullptr,e.what(),"Unhandled Exception",MB_OK | MB_ICONERROR);
}
}
您可以通过清理和删除不需要的内容来改进代码。我将无法解释写入该代码的所有内容,但您可以查看注释以进行澄清。另外,请阅读 Custom Window Frame Using DWM 的文档。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。