在没有 2 WM_SIZE 消息的情况下在 win32 中进入全屏
Going Fullscreen in win32 without 2 WM_SIZE message
所以我在 win32 c++ 中创建了一个全屏函数:
uint8_t isFullscreen = 0;
RECT winRect; //Current Window Rect
RECT nonFullScreenRect; //Rect Not In Full Screen Position (used to restore window to not full screen position when coming out of fullscreen)
uint32_t screen_width = DEFAULT_SCREEN_WIDTH;
uint32_t screen_height = DEFAULT_SCREEN_HEIGHT;
void Fullscreen( HWND WindowHandle )
{
isFullscreen = isFullscreen ^ 1;
if( isFullscreen )
{
//saving off current window rect
nonFullScreenRect.left = winRect.left;
nonFullScreenRect.right = winRect.right;
nonFullScreenRect.bottom = winRect.bottom;
nonFullScreenRect.top = winRect.top;
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE ); //causes a resize msg
HMONITOR hmon = MonitorFromWindow(WindowHandle, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { sizeof( mi ) };
GetMonitorInfo( hmon, &mi );
screen_width = mi.rcMonitor.right - mi.rcMonitor.left;
screen_height = mi.rcMonitor.bottom - mi.rcMonitor.top;
MoveWindow( WindowHandle, mi.rcMonitor.left, mi.rcMonitor.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
else
{
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE );
screen_width = nonFullScreenRect.right - nonFullScreenRect.left;
screen_height = nonFullScreenRect.bottom - nonFullScreenRect.top;
MoveWindow( WindowHandle, nonFullScreenRect.left, nonFullScreenRect.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
}
然而,当它进入全屏时,该函数会生成 2 WM_SIZE 条消息。当它进入窗口时,它只生成 1.
为什么会这样?我怎样才能让它只为适当的全屏尺寸生成 1 WM_SIZE 条消息?
How can I update an HWND's style and position atomically?问了没人回答
我需要这个的原因是因为我正在使用 DirectX12 并且在 WM_SIZE 我在调整所有交换链后台缓冲区的大小之前等待命令队列末尾的所有信号。我不想在切换到全屏模式时两次调整交换链的大小。
case WM_SIZE:
{
screen_width = LOWORD( LParam );
screen_height = HIWORD( LParam );
//DirectX stuff here
}break;
提前致谢!
更新答案:
Win32 API 允许您一次修改 window 的一个参数。修改参数时,API 可能会也可能不会更新 window 并触发 WM_SIZE,这将是给定当前参数的 window 的大小。
因为要有一个完整的全屏window你至少需要调用2次,一次更新GWL_STYLE,一次更新GWL_EXSTYLE,你很有可能接到 2 WM_SIZE 个电话。其中一个将为您提供不带菜单的 window 尺寸,另一个将为您提供全屏 window 尺寸。这取决于你调用 SetWindowLongPtr
的顺序,但你可能会得到 2 WM_SIZE,只有第二个是“正确的”,即你最后想要的那个。
你的问题更可靠的解决方案是在 Main.cpp:
的顶部使用一个变量
int isTogglingFullScreen = false;
然后在全屏切换代码中(注意 isTogglingFullScreen
的设置位置):
case WM_SYSKEYDOWN:
if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000)
{
// Implements the classic ALT+ENTER fullscreen toggle
if (s_fullscreen)
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
isTogglingFullScreen = false;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0);
int width = 800;
int height = 600;
if (game)
game->GetDefaultSize(width, height);
SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWNORMAL);
}
else
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isTogglingFullScreen = false;
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
}
s_fullscreen = !s_fullscreen;
}
break;
最后,在WM_SIZE里面,改变
else if (!s_in_sizemove && game)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
至
else if (!s_in_sizemove && game && !isTogglingFullScreen)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
当您切换全屏时,这会给您一个 OnWindowSizeChanged()
的调用,并且该调用将具有正确的最终大小。
--
旧答案:
如果你只想触发一个 WM_SIZE,当你切换到全屏时,你应该这样做:
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
对 GWL_STYLE 的任何 SetWindowLongPtr
调用都会触发 WM_SIZE,因此请确保仅使用 GWL_EXSTYLE
调用它。例如,如果您将 GWL_EXSTYLE
设置为您想要的,并将 GWL_STYLE
重置为 0,您将触发 WM_SIZE 两次。
为了更清楚:
- 不要在 SetWindowLongPtr 中使用 GWL_STYLE 因为它会触发无用的 WM_SIZE
- ShowWindow 将触发 WM_SIZE
- 以上代码最终只会触发WM_SIZE一次。
原来是YMMV。完全有可能第一次切换全屏时,你会得到 2 WM_SIZE。一个将使用原始尺寸,另一个将使用新尺寸。后续调用只会触发一个 WM_SIZE.
因此,真正的防弹解决方案(我在玩 SetWindowLongPtr
来回答这个问题之前一直在使用,是验证 window 大小实际上已经改变了。因为一件事在上面的调用中我可以保证的是,你永远不会得到超过 1 个新大小的调用。最多你会得到一个 WM_SIZE 旧大小的调用,你会通过检查来丢弃它与当前尺寸相同。
如果您使用 DX12 的 DevicesResources 模板,您会看到 OnWindowSizeChanged()
中有一个检查,如果大小没有改变,它什么都不做。
所以我在 win32 c++ 中创建了一个全屏函数:
uint8_t isFullscreen = 0;
RECT winRect; //Current Window Rect
RECT nonFullScreenRect; //Rect Not In Full Screen Position (used to restore window to not full screen position when coming out of fullscreen)
uint32_t screen_width = DEFAULT_SCREEN_WIDTH;
uint32_t screen_height = DEFAULT_SCREEN_HEIGHT;
void Fullscreen( HWND WindowHandle )
{
isFullscreen = isFullscreen ^ 1;
if( isFullscreen )
{
//saving off current window rect
nonFullScreenRect.left = winRect.left;
nonFullScreenRect.right = winRect.right;
nonFullScreenRect.bottom = winRect.bottom;
nonFullScreenRect.top = winRect.top;
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE ); //causes a resize msg
HMONITOR hmon = MonitorFromWindow(WindowHandle, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { sizeof( mi ) };
GetMonitorInfo( hmon, &mi );
screen_width = mi.rcMonitor.right - mi.rcMonitor.left;
screen_height = mi.rcMonitor.bottom - mi.rcMonitor.top;
MoveWindow( WindowHandle, mi.rcMonitor.left, mi.rcMonitor.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
else
{
SetWindowLongPtr( WindowHandle, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE );
screen_width = nonFullScreenRect.right - nonFullScreenRect.left;
screen_height = nonFullScreenRect.bottom - nonFullScreenRect.top;
MoveWindow( WindowHandle, nonFullScreenRect.left, nonFullScreenRect.top, (int32_t)screen_width, (int32_t)screen_height, FALSE );
}
}
然而,当它进入全屏时,该函数会生成 2 WM_SIZE 条消息。当它进入窗口时,它只生成 1.
为什么会这样?我怎样才能让它只为适当的全屏尺寸生成 1 WM_SIZE 条消息?
How can I update an HWND's style and position atomically?问了没人回答
我需要这个的原因是因为我正在使用 DirectX12 并且在 WM_SIZE 我在调整所有交换链后台缓冲区的大小之前等待命令队列末尾的所有信号。我不想在切换到全屏模式时两次调整交换链的大小。
case WM_SIZE:
{
screen_width = LOWORD( LParam );
screen_height = HIWORD( LParam );
//DirectX stuff here
}break;
提前致谢!
更新答案:
Win32 API 允许您一次修改 window 的一个参数。修改参数时,API 可能会也可能不会更新 window 并触发 WM_SIZE,这将是给定当前参数的 window 的大小。
因为要有一个完整的全屏window你至少需要调用2次,一次更新GWL_STYLE,一次更新GWL_EXSTYLE,你很有可能接到 2 WM_SIZE 个电话。其中一个将为您提供不带菜单的 window 尺寸,另一个将为您提供全屏 window 尺寸。这取决于你调用 SetWindowLongPtr
的顺序,但你可能会得到 2 WM_SIZE,只有第二个是“正确的”,即你最后想要的那个。
你的问题更可靠的解决方案是在 Main.cpp:
的顶部使用一个变量int isTogglingFullScreen = false;
然后在全屏切换代码中(注意 isTogglingFullScreen
的设置位置):
case WM_SYSKEYDOWN:
if (wParam == VK_RETURN && (lParam & 0x60000000) == 0x20000000)
{
// Implements the classic ALT+ENTER fullscreen toggle
if (s_fullscreen)
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
isTogglingFullScreen = false;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, 0);
int width = 800;
int height = 600;
if (game)
game->GetDefaultSize(width, height);
SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWNORMAL);
}
else
{
isTogglingFullScreen = true;
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isTogglingFullScreen = false;
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
}
s_fullscreen = !s_fullscreen;
}
break;
最后,在WM_SIZE里面,改变
else if (!s_in_sizemove && game)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
至
else if (!s_in_sizemove && game && !isTogglingFullScreen)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
当您切换全屏时,这会给您一个 OnWindowSizeChanged()
的调用,并且该调用将具有正确的最终大小。
--
旧答案:
如果你只想触发一个 WM_SIZE,当你切换到全屏时,你应该这样做:
SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOWMAXIMIZED);
对 GWL_STYLE 的任何 SetWindowLongPtr
调用都会触发 WM_SIZE,因此请确保仅使用 GWL_EXSTYLE
调用它。例如,如果您将 GWL_EXSTYLE
设置为您想要的,并将 GWL_STYLE
重置为 0,您将触发 WM_SIZE 两次。
为了更清楚:
- 不要在 SetWindowLongPtr 中使用 GWL_STYLE 因为它会触发无用的 WM_SIZE
- ShowWindow 将触发 WM_SIZE
- 以上代码最终只会触发WM_SIZE一次。
原来是YMMV。完全有可能第一次切换全屏时,你会得到 2 WM_SIZE。一个将使用原始尺寸,另一个将使用新尺寸。后续调用只会触发一个 WM_SIZE.
因此,真正的防弹解决方案(我在玩 SetWindowLongPtr
来回答这个问题之前一直在使用,是验证 window 大小实际上已经改变了。因为一件事在上面的调用中我可以保证的是,你永远不会得到超过 1 个新大小的调用。最多你会得到一个 WM_SIZE 旧大小的调用,你会通过检查来丢弃它与当前尺寸相同。
如果您使用 DX12 的 DevicesResources 模板,您会看到 OnWindowSizeChanged()
中有一个检查,如果大小没有改变,它什么都不做。