So I am creating a fullscreen function in win32 c doing:
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 );
}
}
However when it goes fullscreen, the function generates 2 WM_SIZE messages. While when it goes windowed, it generates only 1.
Why is that the case? And how can I make it generate only 1 WM_SIZE message for the proper full screen size?
How can I update an HWND's style and position atomically? asks about it but no one answers it
The reasons I need this is because I am using DirectX12 and on WM_SIZE I wait for all the signals at the end of the command queues before resizing all the swap chain back buffers. And I don't want to have to resize the swap chain twice when switching to fullscreen mode.
case WM_SIZE:
{
screen_width = LOWORD( LParam );
screen_height = HIWORD( LParam );
//DirectX stuff here
}break;
Thanks in advance!
CodePudding user response:
Updated Answer:
The Win32 API allows you to modify parameters of the window one at a time. When a parameter is modified, the API may or may not update the window and trigger a WM_SIZE that will be the size of the window given the current parameters.
Since to have a complete full screen window you need to make at least 2 calls, one to update GWL_STYLE and another to update GWL_EXSTYLE, you have a big chance of getting 2 WM_SIZE calls. One of them will give you the window size without the menu, and the other the full screen window size. It depends in which order you call SetWindowLongPtr
, but you'll probably get 2 WM_SIZE and only the second one is "correct", i.e. the one you want in the end.
The more reliable solution to your problem is to use a variable at the top of Main.cpp:
int isTogglingFullScreen = false;
Then inside your full screen toggle code (note where isTogglingFullScreen
is being set):
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;
Finally, inside WM_SIZE, change
else if (!s_in_sizemove && game)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
to
else if (!s_in_sizemove && game && !isTogglingFullScreen)
{
game->OnWindowSizeChanged(LOWORD(lParam), HIWORD(lParam));
}
This will give you a single call to OnWindowSizeChanged()
when you toggle full screen, and the call will be with the correct final size.
--
Old Answer:
If you only want a single WM_SIZE to trigger, when you switch to full screen then you should go for something like this:
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);
Any SetWindowLongPtr
call to GWL_STYLE will trigger a WM_SIZE, so make sure it's only called with GWL_EXSTYLE
. For example, if you both set GWL_EXSTYLE
to what you want, and reset GWL_STYLE
to 0, you'll trigger WM_SIZE twice.
To make it clearer:
- Don't use GWL_STYLE in SetWindowLongPtr because it triggers a useless WM_SIZE
- ShowWindow will trigger WM_SIZE
- The above code will ultimately only trigger WM_SIZE once.
It turns out that YMMV. It's entirely possible that the first time you switch fullscreen, you'll get 2 WM_SIZE. One will be with the original size, and the other with the new size. Subsequent calls will trigger only one WM_SIZE.
Hence, the really bulletproof solution (which I was using anyway before playing around with the SetWindowLongPtr
to answer this question, is to validate that the window size has actually changed. Because one thing that I can guarantee in the above call is that you'll never get more than 1 call with the new size. At most you'll get a WM_SIZE call with the old size, which you'll discard by checking that it's the same as the current size.
If you use the DevicesResources template for DX12, you'll see that there's a check in OnWindowSizeChanged()
that does nothing if the size hasn't changed.