c++ - Win32 : Add black borders to fullscreen window

标签 c++ winapi window fullscreen

我试图在 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 不等于 displayWidth,则在窗口两侧显示桌面显示高度:

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) 来呈现您的内容,将任何多余的空间留给父窗口 (P ).

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

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

要创建无边框子窗口 C,请使用 WS_CHILD |调用 CreateWindow 时的 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 );
}

关于c++ - Win32 : Add black borders to fullscreen window,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28968012/

相关文章:

c++ - 哪个文件操作更快,读还是写

c++ - 在白色背景上绘制球体(C++ 和 OpenGL)

winapi - 判断两个目录名是否指向同一个目录

java - 按钮多次打开新的 JFrame。我该如何阻止呢?

C# 接口(interface) C++ DLL?

c++ - 绕道钩住 CreateFile 函数触发堆栈溢出

c++ - 无法写入注册表

c - "scrolling range"是什么意思?

javascript - 使用浏览器大小和调整大小获取、设置和重置高度

wpf - 使用 WPF 更改 FontSize 与窗口大小的关系?