【问题标题】:Win32: Add black borders to fullscreen windowWin32:为全屏窗口添加黑色边框
【发布时间】:2015-03-10 15:43:57
【问题描述】:

我正在尝试在 Windows 的全屏模式下保留内容纵横比。如果显示纵横比与内容纵横比不同,我想将桌面的其余部分隐藏在黑色边框后面。是否可以使用 Win32 api 创建内容居中和黑色边框的全屏窗口?

在 OS X 中,这可以通过以下代码轻松实现:

CGSize ar;
ar.width = 800;
ar.height = 600;
[self.window setContentAspectRatio:ar];
[self.window center];
[self.window toggleFullScreen:nil];

如果我以 16:9 显示运行上述代码,我的应用程序将进入全屏模式,内容居中(因为它是 4:3),并且屏幕两侧都有黑色边框。

我尝试在 Windows 中实现相同的功能,但我开始怀疑这是否可能。我当前的全屏代码保持纵横比和 使内容居中,但如果fullscreenWidthfullscreenHeight 不等于displayWidthdisplayHeight,则在窗口两侧显示桌面:

bool enterFullscreen(int fullscreenWidth, int fullscreenHeight)
{
    DEVMODE fullscreenSettings;
    bool isChangeSuccessful;

    int displayWidth = GetDeviceCaps(m_hDC, HORZRES);
    int displayHeight = GetDeviceCaps(m_hDC, VERTRES);

    int colourBits = GetDeviceCaps(m_hDC, BITSPIXEL);
    int refreshRate = GetDeviceCaps(m_hDC, VREFRESH);

    EnumDisplaySettings(NULL, 0, &fullscreenSettings);
    fullscreenSettings.dmPelsWidth = fullscreenWidth;
    fullscreenSettings.dmPelsHeight = fullscreenHeight;
    fullscreenSettings.dmBitsPerPel = colourBits;
    fullscreenSettings.dmDisplayFrequency = refreshRate;
    fullscreenSettings.dmFields = DM_PELSWIDTH |
        DM_PELSHEIGHT |
        DM_BITSPERPEL |
        DM_DISPLAYFREQUENCY;

    SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_TOPMOST);
    SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
    SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, displayWidth, displayHeight, SWP_SHOWWINDOW);
    isChangeSuccessful = ChangeDisplaySettings(&fullscreenSettings, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
    ShowWindow(m_hWnd, SW_MAXIMIZE);

    RECT rcWindow;
    GetWindowRect(m_hWnd, &rcWindow);

    // calculate content position
    POINT ptDiff;
    ptDiff.x = ((rcWindow.right - rcWindow.left) - fullscreenWidth) / 2;
    ptDiff.y = ((rcWindow.bottom - rcWindow.top) - fullscreenHeight) / 2;

    AdjustWindowRectEx(&rcWindow, GetWindowLong(m_hWnd, GWL_STYLE), FALSE, GetWindowLong(m_hWnd, GWL_EXSTYLE));
    SetWindowPos(m_hWnd, 0, ptDiff.x, ptDiff.y, displayWidth, displayHeight, NULL);

    return isChangeSuccessful;
}

【问题讨论】:

    标签: c++ winapi window fullscreen


    【解决方案1】:

    完成您正在寻找的最简单的方法是创建一个子窗口 (C) 来呈现您的内容,将多余的空间留给父窗口 (P )。

    P 应该使用黑色画笔作为其背景。注册窗口类(RegisterClass)时,为WNDCLASS structurehbrBackground 成员指定(HBRUSH)GetStockObject(BLACK_BRUSH)。为了防止在擦除背景时闪烁,P 应该有 WS_CLIPCHILDREN Window Style

    每当P改变它的大小时,一个WM_SIZE message被发送到P的窗口过程。然后处理程序可以调整 C 的位置和大小以保持纵横比。

    要创建无边框子窗口C,请在对CreateWindow 的调用中使用WS_CHILD | WS_VISIBLE 窗口样式。如果您想在父 P 中处理鼠标输入,请添加 WS_DISABLED 窗口样式。


    示例代码(为简洁起见省略了错误检查):
    #define STRICT 1
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    
    // Globals
    HWND g_hWndContent = NULL;
    
    // Forward declarations
    LRESULT CALLBACK WndProcMain( HWND, UINT, WPARAM, LPARAM );
    LRESULT CALLBACK WndProcContent( HWND, UINT, WPARAM, LPARAM );
    
    int APIENTRY wWinMain( HINSTANCE hInstance,
                           HINSTANCE /*hPrevInstance*/,
                           LPWSTR /*lpCmdLine*/,
                           int nCmdShow ) {
    

    主窗口类和内容窗口类都需要注册。注册几乎相同,除了背景画笔。内容窗口使用白色画笔,因此无需任何额外代码即可看到:

        // Register main window class
        const wchar_t classNameMain[] = L"MainWindow";
        WNDCLASSEXW wcexMain = { sizeof( wcexMain ) };
        wcexMain.style = CS_HREDRAW | CS_VREDRAW;
        wcexMain.lpfnWndProc = WndProcMain;
        wcexMain.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
        wcexMain.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
        wcexMain.lpszClassName = classNameMain;
        ::RegisterClassExW( &wcexMain );
    
        // Register content window class
        const wchar_t classNameContent[] = L"ContentWindow";
        WNDCLASSEXW wcexContent = { sizeof( wcexContent ) };
        wcexContent.style = CS_HREDRAW | CS_VREDRAW;
        wcexContent.lpfnWndProc = WndProcContent;
        wcexContent.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
        wcexContent.hbrBackground = (HBRUSH)::GetStockObject( WHITE_BRUSH );
        wcexContent.lpszClassName = classNameContent;
        ::RegisterClassExW( &wcexContent );
    

    注册了窗口类后,我们可以继续为每个类创建一个实例。请注意,内容窗口最初的大小为零。实际大小在父级的WM_SIZE 处理程序中计算得出。

        // Create main window
        HWND hWndMain = ::CreateWindowW( classNameMain,
                                         L"Constant AR",
                                         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                         CW_USEDEFAULT, CW_USEDEFAULT, 800, 800,
                                         NULL,
                                         NULL,
                                         hInstance,
                                         NULL );
        // Create content window
        g_hWndContent = ::CreateWindowW( classNameContent,
                                         NULL,
                                         WS_CHILD | WS_VISIBLE,
                                         0, 0, 0, 0,
                                         hWndMain,
                                         NULL,
                                         hInstance,
                                         NULL );
    

    其余部分是样板 Windows 应用程序代码:

        // Show application
        ::ShowWindow( hWndMain, nCmdShow );
        ::UpdateWindow( hWndMain );
    
        // Main message loop
        MSG msg = { 0 };
        while ( ::GetMessageW( &msg, NULL, 0, 0 ) > 0 )
        {
            ::TranslateMessage( &msg );
            ::DispatchMessageW( &msg );
        }
    
        return (int)msg.wParam;
    }
    

    窗口类的行为是在 Window Procedure 内部实现的:

    LRESULT CALLBACK WndProcMain( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
    
        switch ( message ) {
        case WM_CLOSE:
            ::DestroyWindow( hWnd );
            return 0;
    
        case WM_DESTROY:
            ::PostQuitMessage( 0 );
            return 0;
    
        default:
            break;
    

    除了标准的消息处理之外,主窗口的窗口过程会调整内容的大小以适应主窗口的大小变化:

        case WM_SIZE: {
            const SIZE ar = { 800, 600 };
            // Query new client area size
            int clientWidth = LOWORD( lParam );
            int clientHeight = HIWORD( lParam );
            // Calculate new content size
            int contentWidth = ::MulDiv( clientHeight, ar.cx, ar.cy );
            int contentHeight = ::MulDiv( clientWidth, ar.cy, ar.cx );
    
            // Adjust dimensions to fit inside client area
            if ( contentWidth > clientWidth ) {
                contentWidth = clientWidth;
                contentHeight = ::MulDiv( contentWidth, ar.cy, ar.cx );
            } else {
                contentHeight = clientHeight;
                contentWidth = ::MulDiv( contentHeight, ar.cx, ar.cy );
            }
    
            // Calculate offsets to center content
            int offsetX = ( clientWidth - contentWidth ) / 2;
            int offsetY = ( clientHeight - contentHeight ) / 2;
    
            // Adjust content window position
            ::SetWindowPos( g_hWndContent,
                            NULL,
                            offsetX, offsetY,
                            contentWidth, contentHeight,
                            SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER );
    
            return 0;
        }
        }
    
        return ::DefWindowProcW( hWnd, message, wParam, lParam );
    }
    

    内容窗口的窗口过程不实现任何自定义行为,只是将所有消息转发给默认实现:

    LRESULT CALLBACK WndProcContent( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
        return ::DefWindowProcW( hWnd, message, wParam, lParam );
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 2018-04-22
      • 1970-01-01
      • 2012-03-15
      • 2021-12-27
      • 2023-03-26
      • 1970-01-01
      相关资源
      最近更新 更多