优化窗口框架 (#773)

* fix: 修复 Windows10 中最大化和还原窗口时闪烁的问题

* fix: 修复 Win11 中窗口化状态下标题栏上部被边框遮挡的问题
This commit is contained in:
Xu 2023-12-08 20:42:54 +08:00 committed by GitHub
commit 0a9cabdad3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -140,9 +140,28 @@ protected:
{
_currentDpi = GetDpiForWindow(_hWnd);
_UpdateFrameMargins();
if (!Win32Utils::GetOSVersion().IsWin11()) {
// 在 Win10 中,移除标题栏时上边框也被没了。我们的解决方案是:使用 DwmExtendFrameIntoClientArea
// 将边框扩展到客户区,然后在顶部绘制了一个黑色实线来显示系统原始边框(这种情况下操作系统将黑色视
// 为透明)。因此我们有**完美**的上边框!
// 见 https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame
//
// 有的软件自己绘制了假的上边框,如 Chromium 系、WinUI 3 等,但窗口失去焦点时边框是半透明的,无法
// 完美模拟。
//
// 我们选择扩展到标题栏高度,这是最好的选择。一个自然的想法是,既然上边框只有一个像素高,我们扩展一
// 个像素即可,可惜因为 DWM 的 bug这会使窗口失去焦点时上边框变为透明。那么能否传一个负值让边框
// 扩展到整个客户区?这大部分情况下可以工作,有一个小 bug不显示边框颜色的设置下深色模式的边框会变
// 为纯黑而不是半透明。
//
// 最大化时不要调用 DwmExtendFrameIntoClientArea 还原边框,会导致窗口闪烁。
RECT frame{};
AdjustWindowRectExForDpi(&frame, GetWindowStyle(_hWnd), FALSE, 0, _currentDpi);
MARGINS margins{
.cyTopHeight = -frame.top
};
DwmExtendFrameIntoClientArea(_hWnd, &margins);
// 初始化双缓冲绘图
static const int _ = []() {
BufferedPaintInit();
@ -259,7 +278,7 @@ protected:
const int topBorderHeight = (int)_GetTopBorderHeight();
// 在顶部绘制黑色实线以显示系统原始边框,见 _UpdateFrameMargins
// 在顶部绘制黑色实线以显示系统原始边框,见 WM_CREATE
if (ps.rcPaint.top < topBorderHeight) {
RECT rcTopBorder = ps.rcPaint;
rcTopBorder.bottom = topBorderHeight;
@ -410,8 +429,6 @@ protected:
}
}
_UpdateFrameMargins();
return 0;
}
case WM_DESTROY:
@ -441,10 +458,17 @@ protected:
}
uint32_t _GetTopBorderHeight() const noexcept {
static constexpr uint32_t TOP_BORDER_HEIGHT = 1;
// 最大化时没有上边框
if (_isMaximized) {
return 0;
}
// Win11 或最大化时没有上边框
return (Win32Utils::GetOSVersion().IsWin11() || _isMaximized) ? 0 : TOP_BORDER_HEIGHT;
// Win10 中窗口边框始终只有一个像素宽Win11 中的窗口边框宽度和 DPI 缩放有关
if (Win32Utils::GetOSVersion().IsWin11()) {
return (_currentDpi + USER_DEFAULT_SCREEN_DPI / 2) / USER_DEFAULT_SCREEN_DPI;
} else {
return 1;
}
}
int _GetResizeHandleHeight() noexcept {
@ -478,7 +502,9 @@ private:
}
}
int topBorderHeight = _GetTopBorderHeight();
// Win10 中上边框被涂黑来显示系统原始边框Win11 中 DWM 绘制的上边框也位于客户区内,
// 很可能是为了和 Win10 兼容。XAML Islands 不应该和上边框重叠。
const int topBorderHeight = (int)_GetTopBorderHeight();
// SWP_NOZORDER 确保 XAML Islands 窗口始终在标题栏窗口下方,否则主窗口在调整大小时会闪烁
SetWindowPos(
@ -499,32 +525,6 @@ private:
}
}
void _UpdateFrameMargins() const noexcept {
if (Win32Utils::GetOSVersion().IsWin11()) {
return;
}
MARGINS margins{};
if (_GetTopBorderHeight() > 0) {
// 在 Win10 中,移除标题栏时上边框也被没了。我们的解决方案是:使用 DwmExtendFrameIntoClientArea
// 将边框扩展到客户区,然后在顶部绘制了一个黑色实线来显示系统原始边框(这种情况下操作系统将黑色视
// 为透明)。因此我们有**完美**的上边框!
// 见 https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame
//
// 有的软件自己绘制了假的上边框,如 Chromium 系、WinUI 3 等,但窗口失去焦点时边框是半透明的,无法
// 完美模拟。
//
// 我们选择扩展到标题栏高度,这是最好的选择。一个自然的想法是,既然上边框只有一个像素高,我们扩展一
// 个像素即可,可惜因为 DWM 的 bug这会使窗口失去焦点时上边框变为透明。那么能否传一个负值让边框
// 扩展到整个客户区?这大部分情况下可以工作,有一个小 bug不显示边框颜色的设置下深色模式的边框会变
// 为纯黑而不是半透明。
RECT frame{};
AdjustWindowRectExForDpi(&frame, GetWindowStyle(_hWnd), FALSE, 0, _currentDpi);
margins.cyTopHeight = -frame.top;
}
DwmExtendFrameIntoClientArea(_hWnd, &margins);
}
winrt::event<winrt::delegate<>> _destroyedEvent;
HWND _hwndXamlIsland = NULL;