1. 项目概述从“全屏”这个简单需求说起“窗口全屏”这四个字对于任何一个C开发者尤其是刚接触图形界面编程的朋友来说听起来都像是一个基础得不能再基础的功能。不就是让窗口铺满整个屏幕吗很多新手可能会想调用一个SetWindowPos或者ShowWindow(SW_MAXIMIZE)不就完事了吗我最初也是这么认为的直到在实际项目中尤其是在开发游戏、播放器、专业图形工具这类对显示效果、性能、交互有苛刻要求的应用时才真正踩遍了全屏模式下的每一个“坑”。这个标题“窗口全屏(C源码)”背后远不止一行API调用那么简单。它涉及Windows或跨平台图形子系统底层的工作机制、消息循环的微妙变化、资源管理的策略以及如何在不同场景下如游戏全屏、无边框全屏、多显示器适配做出最合适的技术选型。网上能找到的代码片段往往只解决了“能跑”的问题却隐藏了无数导致闪烁、卡顿、输入失效、DPI错乱的陷阱。今天我就结合自己十多年在Windows桌面开发中趟过的雷把“窗口全屏”这个主题掰开揉碎了讲清楚从最基础的实现到高级的优化技巧并提供一份可直接集成、生产级可用的C源码。2. 核心需求与场景拆解全屏不止一种在动手写代码之前我们必须明确你要的“全屏”到底是什么不同的需求对应完全不同的实现路径和技术要点。2.1 场景一传统的“最大化窗口”这是最简单的需求。用户点击了窗口右上角的最大化按钮或者你调用了ShowWindow(hWnd, SW_MAXIMIZE)。此时窗口会占据整个工作区即桌面减去任务栏的区域窗口仍然带有标题栏、边框和系统菜单。这种模式适用于绝大多数办公、工具类软件。它的实现由系统托管开发者干预较少但问题也最少。2.2 场景二真正的“独占式全屏”Fullscreen Exclusive Mode这是游戏、高清视频播放器、图形渲染工具的核心需求。目标是让应用程序独占整个屏幕或多个屏幕中的一个通常还会伴随显示模式分辨率、刷新率的切换。在这种模式下窗口边框和标题栏完全消失应用程序画面直接与显卡帧缓冲区对话。性能最高绕过了Windows桌面窗口管理器DWM的合成步骤减少了显示延迟对于高帧率应用至关重要。输入响应直接有时能获得更低的输入延迟。副作用明显切换时屏幕会黑屏闪烁AltTab切换出去再回来可能有问题多显示器管理复杂现代Windows系统Vista之后由于DWM的存在真正的“独占”已被弱化但模式依然存在。2.3 场景三“无边框窗口全屏”Borderless Fullscreen Window这是目前非常流行的一种折中方案尤其在游戏和流媒体软件中。它创建一个没有边框、标题栏但尺寸设置为覆盖整个屏幕的窗口。其特点是快速切换在应用和桌面间切换AltTab速度极快几乎没有黑屏。兼容性好仍然由DWM管理因此可以很好地与多显示器、屏幕录制软件如OBS、远程桌面协作。性能稍逊比独占式全屏多了一层DWM合成会引入极小的延迟和性能开销但对绝大多数应用而言感知不强。实现相对简单主要通过修改窗口样式Window Style和位置实现。我们的“C源码”项目将重点覆盖场景二独占式全屏和场景三无边框窗口全屏的实现因为这才是真正需要开发者精细控制、且容易出问题的部分。场景一过于基础系统已完美处理。3. 技术选型与底层原理剖析在Windows平台上实现全屏我们主要与两个核心API打交道Win32 API和DirectX/Graphics API如果你做游戏或高性能图形。这里我们先聚焦于通用的Win32 API方案。3.1 关键数据结构与函数实现全屏本质上是操作一个HWND窗口句柄及其关联的DEVMODE设备模式和MONITORINFO显示器信息。DEVMODE这个结构体定义了显示设备的详细设置是全屏切换特别是改变分辨率的核心。你需要关注的字段有dmPelsWidth 1920; // 屏幕宽度 dmPelsHeight 1080; // 屏幕高度 dmDisplayFrequency 60; // 刷新率 dmBitsPerPel 32; // 颜色深度 dmFields DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL; // 指明哪些字段有效通过ChangeDisplaySettings(devmode, CDS_FULLSCREEN)可以临时改变整个桌面的显示模式。MONITORINFO/MONITORINFOEX用于获取特定显示器监视器的信息比如它的屏幕坐标范围rcMonitor、工作区坐标范围rcWork。这是实现多显示器精准全屏的关键。通过MonitorFromWindow或MonitorFromPoint可以获取包含指定窗口或点的显示器句柄HMONITOR。窗口样式Window Styles这是实现“无边框窗口全屏”的钥匙。一个标准的重叠窗口Overlapped Window样式通常包含WS_OVERLAPPEDWINDOW它等于(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)。要实现无边框我们需要在创建窗口或动态修改时移除这些边框和标题栏相关的样式。3.2 两种全屏模式的实现路径对比特性独占式全屏 (Exclusive Fullscreen)无边框窗口全屏 (Borderless Windowed)核心APIChangeDisplaySettings,SetWindowLongPtr,SetWindowPosSetWindowLongPtr,SetWindowPos,AdjustWindowRectEx性能最优绕过DWM合成优秀但受DWM轻微影响切换速度慢有黑屏闪烁极快无缝切换AltTab体验可能卡顿或黑屏流畅近乎原生窗口多显示器支持复杂需指定目标显示器简单窗口可跨显示器或指定显示器屏幕录制/截图可能需特殊处理兼容性好普通方法即可实现复杂度高需处理分辨率切换、恢复中主要处理窗口样式和位置适用场景竞技游戏、专业图形渲染、VR大多数游戏、视频播放器、演示软件实操心得除非你的应用对那几毫秒的延迟和绝对性能有极致要求如电竞游戏否则无边框窗口全屏是更推荐的选择。它提供了95%的性能和好得多的系统兼容性与用户体验。现代游戏如《守望先锋》、《CS2》都提供了此模式。4. 核心源码实现与逐行解析下面我将提供一个健壮的、支持两种模式的C全屏管理类FullscreenManager。它封装了状态管理、模式切换和错误处理。4.1 头文件定义 (FullscreenManager.h)#pragma once #include windows.h #include vector class FullscreenManager { public: // 全屏模式枚举 enum class Mode { Windowed, // 窗口模式 Borderless, // 无边框窗口全屏 Exclusive // 独占式全屏 }; FullscreenManager(HWND hWnd); ~FullscreenManager(); // 切换到指定模式。targetMonitor 为 nullptr 时使用窗口当前所在的显示器。 bool SwitchToMode(Mode newMode, HMONITOR targetMonitor nullptr); // 切换回窗口模式 bool SwitchToWindowed(); // 在当前模式Borderless/Exclusive和Windowed模式间切换 bool ToggleFullscreen(); // 获取当前模式 Mode GetCurrentMode() const { return m_currentMode; } // 处理窗口消息如WM_SIZE, WM_DISPLAYCHANGE用于内部状态同步 void HandleWindowMessage(UINT msg, WPARAM wParam, LPARAM lParam); private: HWND m_hWnd; // 管理的窗口句柄 Mode m_currentMode; // 当前模式 // 用于恢复窗口模式的数据 struct WindowedState { LONG style; // 原窗口样式 LONG exStyle; // 原扩展样式 RECT rect; // 原窗口位置和大小窗口矩形 bool isMaximized; // 原最大化状态 } m_savedState; // 独占全屏相关的数据 DEVMODE m_originalDevMode; // 切换前的原始显示模式 HMONITOR m_hLastMonitor; // 最后一次全屏所在的显示器 // 内部方法 bool EnterBorderlessFullscreen(HMONITOR hMonitor); bool EnterExclusiveFullscreen(HMONITOR hMonitor); bool LeaveFullscreen(); HMONITOR GetTargetMonitor(HMONITOR hint) const; bool GetMonitorInfo(HMONITOR hMonitor, MONITORINFOEX info) const; };4.2 源文件实现 (FullscreenManager.cpp) - 核心部分解析#include FullscreenManager.h #include algorithm FullscreenManager::FullscreenManager(HWND hWnd) : m_hWnd(hWnd) , m_currentMode(Mode::Windowed) , m_hLastMonitor(nullptr) { ZeroMemory(m_originalDevMode, sizeof(m_originalDevMode)); m_originalDevMode.dmSize sizeof(m_originalDevMode); } FullscreenManager::~FullscreenManager() { // 析构时自动切换回窗口模式避免程序异常退出后屏幕设置混乱 if (m_currentMode ! Mode::Windowed) { SwitchToWindowed(); } } // 主切换函数 bool FullscreenManager::SwitchToMode(Mode newMode, HMONITOR targetMonitor) { if (m_hWnd nullptr || !IsWindow(m_hWnd)) return false; if (newMode m_currentMode) return true; // 模式相同无需切换 // 1. 如果当前已是全屏模式先退出 if (m_currentMode ! Mode::Windowed) { if (!LeaveFullscreen()) return false; } // 2. 根据目标模式进入 bool success false; HMONITOR hTarget GetTargetMonitor(targetMonitor); switch (newMode) { case Mode::Borderless: success EnterBorderlessFullscreen(hTarget); break; case Mode::Exclusive: success EnterExclusiveFullscreen(hTarget); break; case Mode::Windowed: // LeaveFullscreen 已处理直接返回成功 success true; break; } if (success) { m_currentMode newMode; if (newMode ! Mode::Windowed) { m_hLastMonitor hTarget; } } else { // 切换失败尝试恢复窗口模式 LeaveFullscreen(); m_currentMode Mode::Windowed; } return success; }4.2.1 实现无边框窗口全屏 (EnterBorderlessFullscreen)这是目前最实用、最推荐的方法。bool FullscreenManager::EnterBorderlessFullscreen(HMONITOR hMonitor) { MONITORINFOEX monitorInfo {}; if (!GetMonitorInfo(hMonitor, monitorInfo)) { return false; } // 1. 保存当前窗口状态仅第一次进入全屏时保存 if (m_currentMode Mode::Windowed) { m_savedState.style GetWindowLongPtr(m_hWnd, GWL_STYLE); m_savedState.exStyle GetWindowLongPtr(m_hWnd, GWL_EXSTYLE); GetWindowRect(m_hWnd, m_savedState.rect); m_savedState.isMaximized IsZoomed(m_hWnd); // 如果窗口是最大化状态先获取其“恢复”位置 if (m_savedState.isMaximized) { WINDOWPLACEMENT wp { sizeof(WINDOWPLACEMENT) }; GetWindowPlacement(m_hWnd, wp); m_savedState.rect wp.rcNormalPosition; } } // 2. 计算目标显示器的整个屏幕区域 RECT targetRect monitorInfo.rcMonitor; // 3. 移除窗口的边框和标题栏样式 LONG_PTR newStyle m_savedState.style; newStyle ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); // 可以根据需要添加其他样式例如 WS_POPUP 用于完全干净的窗口 // newStyle WS_POPUP | WS_VISIBLE; // 另一种更彻底的方式 LONG_PTR newExStyle m_savedState.exStyle; newExStyle ~(WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); // 4. 应用新的窗口样式 SetWindowLongPtr(m_hWnd, GWL_STYLE, newStyle); SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, newExStyle); // 5. 关键步骤设置窗口位置和大小覆盖整个显示器 // 使用 SetWindowPos 并带上 SWP_FRAMECHANGED 标志让系统立即重算窗口框架 SetWindowPos(m_hWnd, HWND_TOP, targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); // 6. 可选隐藏光标对于游戏常见 // ShowCursor(FALSE); return true; }注意事项SWP_FRAMECHANGED标志至关重要仅仅修改GWL_STYLE不足以让窗口框架立即改变。必须调用SetWindowPos并带上这个标志系统才会重新计算并应用新的非客户区边框、标题栏。保存恢复状态我们只在从Windowed模式进入全屏时保存状态。如果在Borderless和Exclusive之间切换不应覆盖原始窗口状态。处理最大化状态如果原窗口是最大化的GetWindowRect得到的是最大化窗口的屏幕坐标而不是“恢复”后的坐标。需要用GetWindowPlacement来获取正确的恢复位置。DPI与缩放在高DPI缩放100%的屏幕上rcMonitor的坐标已经是物理像素。如果你的窗口使用DPI感知需要确保逻辑坐标和物理坐标的转换正确。建议在应用程序清单中声明为DPI感知。4.2.2 实现独占式全屏 (EnterExclusiveFullscreen)这种方法更复杂且在现代Windows上需要更多小心。bool FullscreenManager::EnterExclusiveFullscreen(HMONITOR hMonitor) { MONITORINFOEX monitorInfo {}; if (!GetMonitorInfo(hMonitor, monitorInfo)) { return false; } // 1. 同样保存窗口状态 if (m_currentMode Mode::Windowed) { // ... 保存状态的代码与Borderless模式相同 ... } // 2. 获取当前显示器的当前设置 DEVMODE currentMode {}; currentMode.dmSize sizeof(currentMode); if (!EnumDisplaySettings(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, currentMode)) { return false; } // 3. 保存原始模式以便退出时恢复 m_originalDevMode currentMode; // 4. 准备我们想要切换到的模式例如改为 1920x1080144Hz DEVMODE targetMode currentMode; // 基于当前设置修改 // 示例切换到显示器的原生分辨率 targetMode.dmPelsWidth monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; targetMode.dmPelsHeight monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; // targetMode.dmDisplayFrequency 144; // 可以尝试设置更高刷新率 targetMode.dmFields DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; // 5. 尝试切换显示模式 LONG result ChangeDisplaySettingsEx(monitorInfo.szDevice, targetMode, nullptr, CDS_FULLSCREEN, nullptr); if (result ! DISP_CHANGE_SUCCESSFUL) { // 切换失败可以尝试回退到其他标准模式这里简单返回失败 return false; } // 6. 修改窗口样式为无边框与Borderless类似但通常用WS_POPUP LONG_PTR newStyle WS_POPUP | WS_VISIBLE; LONG_PTR newExStyle WS_EX_APPWINDOW; // 确保在任务栏显示 SetWindowLongPtr(m_hWnd, GWL_STYLE, newStyle); SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, newExStyle); // 7. 将窗口移动到目标显示器并设为全屏大小 SetWindowPos(m_hWnd, HWND_TOP, monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); // 8. 可选捕获鼠标防止移出窗口 // SetCapture(m_hWnd); return true; }4.2.3 退出全屏与恢复 (LeaveFullscreen)bool FullscreenManager::LeaveFullscreen() { if (m_currentMode Mode::Windowed) return true; bool success true; // 1. 如果是独占模式先恢复显示设置 if (m_currentMode Mode::Exclusive) { // 使用 CDS_RESET 标志让系统更新所有显示器设置 if (ChangeDisplaySettingsEx(nullptr, nullptr, nullptr, 0, nullptr) ! DISP_CHANGE_SUCCESSFUL) { // 更精确的做法恢复到保存的原始模式 // ChangeDisplaySettingsEx(nullptr, m_originalDevMode, nullptr, CDS_RESET, nullptr); success false; // 标记失败但继续尝试恢复窗口 } // 释放鼠标捕获 // ReleaseCapture(); } // 2. 恢复窗口样式 SetWindowLongPtr(m_hWnd, GWL_STYLE, m_savedState.style); SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, m_savedState.exStyle); // 3. 恢复窗口位置和大小 // 先设置回普通样式再调整位置 SetWindowPos(m_hWnd, nullptr, m_savedState.rect.left, m_savedState.rect.top, m_savedState.rect.right - m_savedState.rect.left, m_savedState.rect.bottom - m_savedState.rect.top, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); // 4. 如果之前是最大化状态则最大化窗口 if (m_savedState.isMaximized) { ShowWindow(m_hWnd, SW_MAXIMIZE); } // 5. 恢复光标显示 // ShowCursor(TRUE); return success; }4.3 在消息循环中集成全屏管理器需要与主窗口的消息循环配合以处理一些系统事件。void FullscreenManager::HandleWindowMessage(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_SIZE: // 当窗口大小改变时我们可以更新内部状态或UI布局。 // 但注意在独占全屏下我们可能不希望处理这个消息。 break; case WM_DISPLAYCHANGE: // 当显示器分辨率或数量发生变化时比如用户按了WinP // 独占全屏模式可能会被打断。这是一个安全恢复的时机。 if (m_currentMode Mode::Exclusive) { // 可以考虑自动切换回窗口模式或Borderless模式 // SwitchToMode(Mode::Borderless); } break; case WM_ACTIVATEAPP: // 当应用失去激活AltTab出去时某些游戏会选择自动退出独占全屏以减少问题。 // if (wParam FALSE m_currentMode Mode::Exclusive) { // SwitchToWindowed(); // } break; } }在你的WndProc函数中调用g_fullscreenManager.HandleWindowMessage(msg, wParam, lParam);g_fullscreenManager是你的全局或类成员管理器实例。5. 高级话题与避坑指南有了基础实现我们来看看那些容易踩坑的高级问题。5.1 多显示器处理的陷阱我们的代码通过HMONITOR来指定目标显示器。但如何获取正确的HMONITOR获取窗口所在显示器MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST)获取鼠标所在显示器MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST)获取所有显示器使用EnumDisplayMonitors回调函数。常见问题在无边框全屏模式下如果你将窗口设置为跨越多台显示器SetWindowPos的坐标覆盖多个显示器Windows可能会将其拉伸或限制在主显示器。行为不确定最好避免。明确指定单台显示器。5.2 DPI感知与高分辨率缩放现代4K、5K显示器很常见。如果你的应用不是DPI感知的系统会进行虚拟化缩放导致在全屏模式下画面模糊。解决方案在应用程序清单文件manifest或通过APISetProcessDpiAwarenessContext声明为DPI感知。Per-Monitor DPI更复杂的情况是窗口在不同DPI的显示器间移动。对于无边框全屏如果用户将窗口拖到另一个DPI的显示器上然后全屏你需要处理DPI变化消息WM_DPICHANGED并可能重新调整窗口大小。5.3 输入焦点与鼠标捕获独占全屏下的AltTab行为可能不理想。一种常见做法是在WM_ACTIVATEAPP消息中检测到应用失活时自动退出独占全屏模式如上文代码注释所示。鼠标锁定对于第一人称游戏你通常需要将鼠标锁定在窗口中心并隐藏。使用SetCapture可以捕获鼠标但更精细的控制需要SetCursorPos和ShowCursor(FALSE)并在每次WM_MOUSEMOVE消息中重置光标到窗口中心。注意无边框模式下鼠标移出屏幕边缘会触发离开消息。5.4 与DirectX/OpenGL/Vulkan的协作如果你使用图形API渲染交换链SwapChain重建在切换全屏模式尤其是独占式时DirectX 11/12或Vulkan的交换链通常需要销毁并重新创建。对于DirectX 11在调用IDXGISwapChain::SetFullscreenState前后有一系列复杂的资源绑定和释放操作。推荐流程销毁所有依赖于交换链缓冲区的资源如渲染目标视图。调用FullscreenManager切换显示模式。根据新的窗口大小或全屏分辨率调整后台缓冲区大小IDXGISwapChain::ResizeBuffers。重新创建渲染目标视图等资源。无边框模式的优势对于DirectX无边框窗口全屏通常不需要重建交换链只需在窗口大小改变时WM_SIZE调用ResizeBuffers即可大大简化了逻辑。5.5 性能考量与小技巧WS_EX_TOPMOST样式在无边框全屏时可以加上WS_EX_TOPMOST扩展样式确保窗口始终在最前防止其他窗口意外弹出覆盖。但要注意这可能会影响AltTab的顺序。禁用窗口动画系统默认的窗口最大化/最小化有动画效果在全屏切换时可能导致延迟。可以通过系统参数SystemParametersInfo(SPI_SETANIMATION, ...)或在创建窗口时指定WS_POPUP样式来减少影响。GDI vs. DirectX如果你的应用使用GDI绘图在独占全屏下性能提升可能不明显。但对于任何基于GPU的渲染正确的全屏模式能显著降低延迟。6. 完整示例集成到Win32窗口程序假设你有一个基本的Win32窗口程序框架集成全屏管理器非常简单。#include FullscreenManager.h FullscreenManager* g_pFullscreenMgr nullptr; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { if (g_pFullscreenMgr) { g_pFullscreenMgr-HandleWindowMessage(message, wParam, lParam); } switch (message) { case WM_KEYDOWN: if (wParam VK_F11) { // 按F11切换全屏 if (g_pFullscreenMgr) { g_pFullscreenMgr-ToggleFullscreen(); } return 0; } else if (wParam VK_ESCAPE) { // 按ESC退出全屏 if (g_pFullscreenMgr g_pFullscreenMgr-GetCurrentMode() ! FullscreenManager::Mode::Windowed) { g_pFullscreenMgr-SwitchToWindowed(); } return 0; } break; case WM_DESTROY: delete g_pFullscreenMgr; g_pFullscreenMgr nullptr; PostQuitMessage(0); break; // ... 其他消息处理 ... } return DefWindowProc(hWnd, message, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // ... 注册窗口类 ... HWND hWnd CreateWindow(...); // 初始化全屏管理器 g_pFullscreenMgr new FullscreenManager(hWnd); // ... 消息循环 ... return 0; }7. 常见问题排查速查表问题现象可能原因解决方案切换全屏后黑屏程序无响应1. 分辨率/刷新率设置不被显示器支持。2. 在独占全屏模式下消息循环阻塞或渲染未及时跟上。1. 使用EnumDisplaySettings枚举支持的显示模式选择最匹配的。2. 确保在切换后立即进行一轮渲染如InvalidateRect。检查消息循环是否健康。无边框全屏下任务栏仍然显示在最前窗口未设置为HWND_TOP或WS_EX_TOPMOST或者任务栏设置为“自动隐藏”且鼠标移到屏幕边缘。1. 在SetWindowPos中使用HWND_TOP。2. 考虑设置WS_EX_TOPMOST样式。3. 对于“自动隐藏”的任务栏这是一个系统行为较难完全覆盖。AltTab切出再切回全屏窗口变成后台或最小化消息处理不当或未正确处理WM_ACTIVATEAPP消息。在WM_ACTIVATEAPP(wParamTRUE) 时主动将窗口置前SetForegroundWindow。对于独占全屏可考虑失活时切换模式。全屏时鼠标移动卡顿或“漂移”鼠标被捕获但未正确处理WM_MOUSEMOVE消息或DPI缩放导致坐标计算错误。1. 检查鼠标捕获逻辑确保在WM_MOUSEMOVE中正确计算相对位移。2. 启用DPI感知使用物理坐标进行计算。在多显示器系统上全屏到了错误的显示器GetTargetMonitor逻辑错误或窗口创建时未指定正确位置。在进入全屏前使用MonitorFromWindow或MonitorFromPoint明确获取目标显示器句柄。提供UI让用户选择显示器。退出全屏后窗口位置或大小不对恢复窗口状态时RECT坐标是屏幕坐标但未考虑窗口边框大小或DPI缩放。1. 确保保存的m_savedState.rect是窗口的“客户区”坐标不应该是窗口矩形。使用AdjustWindowRectEx可以根据客户区大小计算窗口矩形。2. 恢复时如果系统DPI变了需要进行坐标转换。切换全屏瞬间窗口内容闪烁样式改变和大小改变分步进行中间有重绘。使用SetWindowPos时一次性设置好位置、大小和SWP_FRAMECHANGED标志。或者在修改样式前先隐藏窗口(ShowWindow(SW_HIDE))修改完成后再显示。实现一个健壮的窗口全屏功能远不止调用一个API那么简单。它要求你对Windows窗口系统的生命周期、消息流、图形管线有深入的理解。从简单的无边框窗口到复杂的多显示器独占模式每一层都有需要仔细处理的细节。我提供的这个FullscreenManager类是一个起点它封装了核心逻辑和常见的陷阱。在实际项目中你可能还需要根据具体的图形API如DirectX交换链管理、UI框架如Qt、wxWidgets内部的全屏机制以及应用程序的特殊需求如VR渲染、特殊比例拉伸进行调整。记住测试是关键尤其是在各种不同的硬件配置、DPI设置和多显示器环境下进行充分测试才能确保你的“全屏”功能真正稳定可靠。