【问题标题】:Going Fullscreen in win32 without 2 WM_SIZE message在没有 2 WM_SIZE 消息的情况下在 win32 中全屏显示
【发布时间】:2022-02-08 00:34:04
【问题描述】:

所以我在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;

提前致谢!

【问题讨论】:

  • isFullscreen = isFullscreen ^ 1; - 只有游戏开发者才会编写这样的代码。 :) 除此之外,通过检测您的第二个 WM_SIZE 与前一个 WM_SIZE 的宽度/高度相同,什么问题不能解决?或者您是否收到两条不同参数的 WM_SIZE 消息?
  • @selbie 但它的高度和宽度与前一个不同。它几乎就像在一种情况下 SetWindowLongPtr 导致 WM_SIZE。但这两种情况都不是。但我不知道 SetWindowLongPtr 是否真的在生成消息
  • 我在 WM_SIZE 中放置了一个打印语句当窗口化时:Old: 1280 720 New: 1264 681。 When Going fullscreen Old: 1264 681 New: 1280 720 Old: 1920 1080 New: 1920 1080 注意全屏函数也设置了 screen_width 和 screen_height 全局变量
  • 您似乎忽略了documentation 的一部分:“某些窗口数据已被缓存,因此您使用SetWindowLongPtr 所做的更改在您调用SetWindowPos 函数之前不会生效." 我相信窗口边框是“某些窗口数据” 的一部分。尝试将您的 MoveWindow 呼叫替换为对 SetWindowPos 的呼叫。我不知道这是否能解决您当前的问题,但这是您需要解决的问题。
  • @IInspectable 当我切换到 SetWindowPos 时,它不再让我 Alt-Tab 退出程序(也许我必须手动处理?)。也仍然调用它两次。我将不得不做更多的调查。也许我的程序中的其他东西正在生成 2 条消息。

标签: winapi visual-c++ fullscreen directx-12


【解决方案1】:

更新答案:

Win32 API 允许您一次修改一个窗口的参数。当参数被修改时,API 可能会也可能不会更新窗口并触发一个 WM_SIZE,该 WM_SIZE 将是给定当前参数的窗口大小。

由于要拥有一个完整的全屏窗口,您至少需要进行 2 次调用,一次更新 GWL_STYLE,另一次更新 GWL_EXSTYLE,因此您很有可能获得 2 次 WM_SIZE 调用。其中一个会给你没有菜单的窗口大小,另一个是全屏窗口大小。这取决于您调用 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 回答这个问题之前我一直在使用它,是验证窗口大小实际上已经改变。因为我可以在上面的调用中保证一件事是你永远不会得到超过 1 个新尺寸的调用。最多你会得到一个旧尺寸的 WM_SIZE 调用,你会通过检查它是否与当前尺寸相同来丢弃它。

如果您使用 DX12 的 DevicesResources 模板,您会看到OnWindowSizeChanged() 中有一个检查,如果大小没有更改,则该检查不会执行任何操作。

【讨论】:

  • 这如何帮助从窗口中删除任何非客户区?显然,当进入“全屏”时,您需要从窗口中移除边框和标题栏。否则,您将得到一个窗口,其客户区要么没有填满整个显示区域,要么非客户区延伸到相邻的监视器中。这读起来像是一个解决了与问题中所述问题不匹配的问题的答案。
  • 全屏有很多方法。其中一种方法是全屏,但菜单栏留在屏幕上。问题是如何避免多次 WM_SIZE 调用,而不是想要哪种全屏样式。
  • 将“全屏”和“最大化窗口”混为一谈不会把它变成问题的答案。
  • 公平。我将使用以不同方式回答问题的解决方案来更新我的答案。
猜你喜欢
  • 2012-06-26
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多