我正在使用 C++、OpenGL 和 Win32 开发 Windows 11 应用程序。我一直在使用 Visual Studio Code、Discord、Steam 等应用程序使用的技巧,即使用 DirectX、OpenGL 或 Vulkan(在我的例子中是 OpenGL)等图形 API 绘制整个窗口。主要技巧之一是删除标题栏区域,这有效地使整个窗口成为客户区。因此,可以使用 OpenGL 绘制“标题栏”,这消除了使用
WM_NCPAINT
. 的问题
我实现它的方法是处理
WM_NCCALCSIZE
可以消除标题栏区域的地方,然后只需创建 OpenGL 上下文并绘制到窗口即可。然而,问题是当用户将鼠标悬停在“最大化”按钮上时,原生 Windows 应用程序中显示的弹出窗口(如下所列)在处理 WM_NCCALCSIZE
时不会出现。
此外,我通过在光标位于所需位置时返回
WM_NCHITTEST
然后最大化窗口来正确处理HTMAXBUTTON
。这当然有效,但未显示本机弹出窗口。你可能会说,“好吧,你期望在 OpenGL 中绘制整个窗口是什么?”,那么在那种情况下,当用户将鼠标悬停在最大化时,其他使用相同技术(如 Visual Studio Code)的应用程序为什么会出现这个弹出窗口?按钮?
我通过 Microsoft Spy++ 等工具进行了广泛的研究,发现 Visual Studio Code 等应用确实使用 DirectX 图形 API 绘制了整个窗口。下面我附上了悬停自定义最大化按钮时我的应用程序当前行为的图像:
正如您在上面看到的,悬停时没有出现全屏弹出窗口。
WM_NCHITTEST
处理得当,因为当光标位于该位置时会返回 HTMAXBUTTON
,单击该位置确实会最大化应用程序这一事实证实了这一点。这是用于处理WM_NCCALCSIZE
的代码:
if (message == WM_NCCALCSIZE) {
if (wParam == TRUE) {
// ------ Hack to remove the title bar (non-client) area ------
// In order to hide the standard title bar we must change
// the NCCALCSIZE_PARAMS, which dictates the title bar area.
//
// In Windows 10 we can set the top component to '0', which
// in effect hides the default title bar.
// However, for unknown reasons this does not work in
// Windows 11, instead we are required to set the top
// component to '1' in order to get the same effect.
//
// Thus, we must first check the Windows version before
// altering the NCCALCSIZE_PARAMS structure.
Win32Utilities& instance = Win32Utilities::Instance();
int top = instance.getWindowsVersionString() == "Windows 11" ? 1 : 0;
LPNCCALCSIZE_PARAMS ncParams = (LPNCCALCSIZE_PARAMS) lParam;
ncParams->rgrc[0].top += top;
ncParams->rgrc[0].left += 0;
ncParams->rgrc[0].bottom -= 0;
ncParams->rgrc[0].right -= 0;
return 0;
}
}
您可以忽略此消息中使用的技巧,因为如果它是 Windows 11,它将有效地将顶部组件设置为 1,否则设置为 0。如果我删除这段代码,标准标题栏将再次可见,请参见下图:
这里有趣的是,窗口弹出窗口确实正确地绘制在 OpenGL 上下文上,但这仅在悬停本机最大化按钮时有效。请记住,
WM_NCHITTEST
仍然是手动处理的。因此,似乎返回 HTMAXBUTTON
并不是显示此弹出窗口的唯一要求。那么最终的问题是:如何使这个最大化悬停弹出窗口出现在 Win32/OpenGL 应用程序中?
编辑1: 经过更多的挖掘之后,问题似乎出现了(正如@所建议的那样 user7860670) 与
WM_NCCALCSIZE
消息的处理有关。更具体地说,当前仅当wParam
为TRUE
时才处理消息。此外,根据要求,这是当前的命中测试功能:
LRESULT WindowWin32::HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
// Get the point coordinates for the hit test
POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Get the window rectangle.
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
// Determine if the hit test is for resizing. Default middle (1,1)
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window
if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 29)
{
fOnResizeBorder = (ptMouse.y <= rcWindow.top + 8);
uRow = 0;
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 10)
{
uRow = 2;
}
// Determine if the point is on the left or right side of the window
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 10)
{
uCol = 0; // left side
}
else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 10)
{
uCol = 2; // right side
}
// Maximize button
if (uRow == 0 && ptMouse.x >= rcWindow.right - 60 && ptMouse.x < rcWindow.right - 30) {
return HTMAXBUTTON;
}
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.right &&
ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.bottom &&
uRow == 1 && uCol == 1) {
return HTCLIENT;
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
LRESULT hitTests[3][3] =
{
{ HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
return hitTests[uRow][uCol];
}
编辑 2:由于问题现在被阻止回答,其他人讨论了无关紧要的内容,例如 Windows 版本字符串;我设法自己解决了这个问题。问题一直是
WM_NCCALCSIZE
。这是有效的实现:
if (message == WM_NCCALCSIZE) {
// ------ Hack to remove the title bar (non-client) area ------
// In order to hide the standard title bar we must change
// the NCCALCSIZE_PARAMS, which dictates the title bar area.
//
// In Windows 10 we can set the top component to '0', which
// in effect hides the default title bar.
// However, for unknown reasons this does not work in
// Windows 11, instead we are required to set the top
// component to '1' in order to get the same effect.
//
// Thus, we must first check the Windows version before
// altering inflation of the window.
Win32Utilities& instance = Win32Utilities::Instance();
const int d = instance.getWindowsVersionString() == "Windows 11" ? 1 : 0;
InflateRect((LPRECT)lParam, -d, -d);
}