Win32:为全屏添加黑色边框 window
Win32: Add black borders to fullscreen window
我正在尝试在 Windows 中保持全屏模式下的内容纵横比。如果显示纵横比与内容纵横比不同,我想将桌面的其余部分隐藏在黑色边框后面。是否可以使用 Win32 api 创建具有居中内容和黑色边框的全屏 window?
在 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 中实现相同的功能,但我开始怀疑它是否可行。我当前的全屏代码保持纵横比和
将内容居中,但如果 fullscreenWidth
和 fullscreenHeight
不等于 displayWidth
和 displayHeight
,则在 window 两侧显示桌面:
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;
}
完成您要寻找的内容的最简单方法是创建子 window (C) 来呈现您的内容,留下任何多余的 space 给父级 (P).
P 应使用黑色画笔作为背景创建。指定 (HBRUSH)GetStockObject(BLACK_BRUSH)
for the hbrBackground
member of the WNDCLASS structure when registering the window class (RegisterClass). To prevent flicker while erasing the background, P should have the WS_CLIPCHILDREN
Window Style.
每当 P 改变它的大小时,一个 WM_SIZE message 被发送到 P 的 window 过程。然后处理程序可以调整 C 的位置和大小以保持纵横比。
要创建无边框子项 window C,请在对 CreateWindow 的调用中使用 WS_CHILD | WS_VISIBLE
window 样式。如果您想在父级 P 中处理鼠标输入,请添加 WS_DISABLED
window 样式。
示例代码(为简洁起见省略了错误检查):
#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 ) {
主要内容和内容window class都需要注册。注册几乎相同,除了背景画笔。内容window使用白色画笔,无需任何额外代码即可看到:
// 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 );
随着 window classes 的注册,我们可以继续并为每个创建一个实例。请注意,内容 window 最初是零大小的。实际大小在父级的 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 class 的行为在其 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;
除了标准消息处理之外,主要 window 的 window 过程会调整内容的大小以适应主要 window 的大小更改时的大小:
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 );
}
内容 window 的 window 过程没有实现任何自定义行为,只是将所有消息转发到默认实现:
LRESULT CALLBACK WndProcContent( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}
我正在尝试在 Windows 中保持全屏模式下的内容纵横比。如果显示纵横比与内容纵横比不同,我想将桌面的其余部分隐藏在黑色边框后面。是否可以使用 Win32 api 创建具有居中内容和黑色边框的全屏 window?
在 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 中实现相同的功能,但我开始怀疑它是否可行。我当前的全屏代码保持纵横比和
将内容居中,但如果 fullscreenWidth
和 fullscreenHeight
不等于 displayWidth
和 displayHeight
,则在 window 两侧显示桌面:
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;
}
完成您要寻找的内容的最简单方法是创建子 window (C) 来呈现您的内容,留下任何多余的 space 给父级 (P).
P 应使用黑色画笔作为背景创建。指定 (HBRUSH)GetStockObject(BLACK_BRUSH)
for the hbrBackground
member of the WNDCLASS structure when registering the window class (RegisterClass). To prevent flicker while erasing the background, P should have the WS_CLIPCHILDREN
Window Style.
每当 P 改变它的大小时,一个 WM_SIZE message 被发送到 P 的 window 过程。然后处理程序可以调整 C 的位置和大小以保持纵横比。
要创建无边框子项 window C,请在对 CreateWindow 的调用中使用 WS_CHILD | WS_VISIBLE
window 样式。如果您想在父级 P 中处理鼠标输入,请添加 WS_DISABLED
window 样式。
示例代码(为简洁起见省略了错误检查):
#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 ) {
主要内容和内容window class都需要注册。注册几乎相同,除了背景画笔。内容window使用白色画笔,无需任何额外代码即可看到:
// 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 );
随着 window classes 的注册,我们可以继续并为每个创建一个实例。请注意,内容 window 最初是零大小的。实际大小在父级的 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 class 的行为在其 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;
除了标准消息处理之外,主要 window 的 window 过程会调整内容的大小以适应主要 window 的大小更改时的大小:
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 );
}
内容 window 的 window 过程没有实现任何自定义行为,只是将所有消息转发到默认实现:
LRESULT CALLBACK WndProcContent( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}