From 854078067d9448dc28f7e3031438866f441e3385 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 16 Aug 2024 17:31:15 +0000 Subject: [PATCH] Win32 - Fix Window Frame Size and Position issues. (#16608) * add tests * win32 - ensure frame size and position doesn't change for client operations * remove forced position setting --------- Co-authored-by: Julien Lebosquain --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 16 +-- src/Windows/Avalonia.Win32/WindowImpl.cs | 102 +++++------------- .../WindowDecorationsTests.cs | 2 +- .../WindowTests.cs | 42 +++++++- 4 files changed, 79 insertions(+), 83 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index dbde23f712..9158c6db6c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.Automation.Peers; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Platform; using Avalonia.Threading; using Avalonia.Win32.Automation; using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; using Avalonia.Win32.Interop.Automation; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -643,6 +640,8 @@ namespace Avalonia.Win32 Resized(clientSize / RenderScaling, _resizeReason); } + if (IsWindowVisible(_hwnd) && !_shown) + _shown = true; if (stateChanged) { @@ -661,11 +660,17 @@ namespace Avalonia.Win32 if (_isClientAreaExtended) { - UpdateExtendMargins(); + ExtendClientArea(); ExtendClientAreaToDecorationsChanged?.Invoke(true); } } + else if (windowState == WindowState.Maximized && _isClientAreaExtended) + { + ExtendClientArea(); + + ExtendClientAreaToDecorationsChanged?.Invoke(true); + } return IntPtr.Zero; } @@ -676,8 +681,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_MOVE: { - PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), - (short)(ToInt32(lParam) >> 16))); + PositionChanged?.Invoke(Position); return IntPtr.Zero; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5e85b2f3e7..2acbd2a1db 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -263,14 +263,8 @@ namespace Avalonia.Win32 { get { - if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) - { - GetWindowRect(_hwnd, out var rcWindow); - return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling; - } - - DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf()); - return new Size(rect.Width, rect.Height) / RenderScaling; + GetWindowRect(_hwnd, out var rcWindow); + return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling; } } @@ -510,6 +504,7 @@ namespace Avalonia.Win32 var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); return result == 0; } + public IEnumerable Surfaces => _glSurface is null ? new object[] { Handle, _framebuffer } : @@ -520,15 +515,10 @@ namespace Avalonia.Win32 get { GetWindowRect(_hwnd, out var rc); - - var border = HiddenBorderSize; - return new PixelPoint(rc.left + border.Width, rc.top + border.Height); + return new PixelPoint(rc.left, rc.top); } set { - var border = HiddenBorderSize; - value = new PixelPoint(value.X - border.Width, value.Y - border.Height); - SetWindowPos( Handle.Handle, IntPtr.Zero, @@ -542,23 +532,6 @@ namespace Avalonia.Win32 private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full; - private PixelSize HiddenBorderSize - { - get - { - // Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing - if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations || GetStyle().HasFlag(WindowStyles.WS_POPUP)) - { - return PixelSize.Empty; - } - - DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var clientRect, Marshal.SizeOf()); - GetWindowRect(_hwnd, out var frameRect); - var borderWidth = GetSystemMetrics(SystemMetric.SM_CXBORDER); - - return new PixelSize(clientRect.left - frameRect.left - borderWidth, 0); - } - } public void Move(PixelPoint point) => Position = point; @@ -594,25 +567,29 @@ namespace Avalonia.Win32 GetWindowPlacement(_hwnd, out var windowPlacement); - var clientScreenOrigin = new POINT(); - ClientToScreen(_hwnd, ref clientScreenOrigin); - var requestedClientRect = new RECT { - left = clientScreenOrigin.X, - right = clientScreenOrigin.X + requestedClientWidth, - - top = clientScreenOrigin.Y, - bottom = clientScreenOrigin.Y + requestedClientHeight, + left = 0, + top = 0, + right = requestedClientWidth, + bottom = requestedClientHeight }; var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect); + var windowWidth = requestedWindowRect.Width; + var windowHeight = requestedWindowRect.Height; - if (requestedWindowRect.Width == windowPlacement.NormalPosition.Width && requestedWindowRect.Height == windowPlacement.NormalPosition.Height) + if (windowWidth == windowPlacement.NormalPosition.Width && windowHeight == windowPlacement.NormalPosition.Height) { return; } + var position = Position; + requestedWindowRect.left = position.X; + requestedWindowRect.top = position.Y; + requestedWindowRect.right = position.X + windowWidth; + requestedWindowRect.bottom = position.Y + windowHeight; + windowPlacement.NormalPosition = requestedWindowRect; windowPlacement.ShowCmd = !_shown ? ShowWindowCommand.Hide : _lastWindowState switch @@ -1025,14 +1002,8 @@ namespace Avalonia.Win32 if (fullscreen) { GetWindowRect(_hwnd, out var windowRect); - GetClientRect(_hwnd, out var clientRect); - - clientRect.left += windowRect.left; - clientRect.right += windowRect.left; - clientRect.top += windowRect.top; - clientRect.bottom += windowRect.top; - _savedWindowInfo.WindowRect = clientRect; + _savedWindowInfo.WindowRect = windowRect; var current = GetStyle(); var currentEx = GetExtendedStyle(); @@ -1141,16 +1112,8 @@ namespace Avalonia.Win32 _isClientAreaExtended = false; return; } - GetClientRect(_hwnd, out var rcClient); GetWindowRect(_hwnd, out var rcWindow); - // Inform the application of the frame change. - SetWindowPos(_hwnd, - IntPtr.Zero, - rcWindow.left, rcWindow.top, - rcClient.Width, rcClient.Height, - SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE); - if (_isClientAreaExtended && WindowState != WindowState.FullScreen) { var margins = UpdateExtendMargins(); @@ -1170,8 +1133,6 @@ namespace Avalonia.Win32 _offScreenMargin = new Thickness(); _extendedMargins = new Thickness(); - Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), WindowResizeReason.Layout); - unsafe { int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT; @@ -1189,6 +1150,13 @@ namespace Avalonia.Win32 DisableCloseButton(_hwnd); } + // Inform the application of the frame change. + SetWindowPos(_hwnd, + IntPtr.Zero, + rcWindow.left, rcWindow.top, + 0, 0, + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE); + ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); } @@ -1454,7 +1422,7 @@ namespace Avalonia.Win32 { style &= ~(fullDecorationFlags | WindowStyles.WS_THICKFRAME); - if (newProperties.Decorations == SystemDecorations.BorderOnly && newProperties.WindowState != WindowState.Maximized) + if (newProperties.Decorations == SystemDecorations.BorderOnly && newProperties.WindowState != WindowState.Maximized && newProperties.IsResizable) { style |= WindowStyles.WS_THICKFRAME | WindowStyles.WS_BORDER; } @@ -1483,8 +1451,6 @@ namespace Avalonia.Win32 if (!_isFullScreenActive && ((oldProperties.Decorations != newProperties.Decorations) || forceChanges)) { - var style = GetStyle(); - var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0; var margins = new MARGINS @@ -1497,23 +1463,11 @@ namespace Avalonia.Win32 DwmExtendFrameIntoClientArea(_hwnd, ref margins); - if (_shown || forceChanges) { - GetClientRect(_hwnd, out var oldClientRect); - var oldClientRectOrigin = new POINT(); - ClientToScreen(_hwnd, ref oldClientRectOrigin); - oldClientRect.Offset(oldClientRectOrigin); - - var newRect = oldClientRect; - - if (newProperties.Decorations == SystemDecorations.Full) - { - AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle()); - } - - SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, + SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0 ,0, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | + SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_FRAMECHANGED); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs index 8f0509233e..23045e612c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs @@ -12,7 +12,7 @@ public class WindowDecorationsTests : TestBase, IDisposable { } - [PlatformFact(TestPlatforms.MacOS)] // TODO fix me on Windows + [Fact] public void Window_Size_Should_Be_Consistent_Between_Toggles() { var window = Session.FindElementByAccessibilityId("MainWindow"); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index dd0768f302..1d7d767fb2 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -311,6 +311,44 @@ namespace Avalonia.IntegrationTests.Appium } } + [Fact] + public void Changing_SystemDecorations_Should_Not_Change_Frame_Size_And_Position() + { + using (OpenWindow(null, ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) + { + var info = GetWindowInfo(); + + Session.FindElementByAccessibilityId("CurrentSystemDecorations").Click(); + Session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick(); + var updatedInfo = GetWindowInfo(); + Assert.Equal(info.FrameSize, updatedInfo.FrameSize); + Assert.Equal(info.Position, updatedInfo.Position); + + Session.FindElementByAccessibilityId("CurrentSystemDecorations").Click(); + Session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick(); + updatedInfo = GetWindowInfo(); + Assert.Equal(info.FrameSize, updatedInfo.FrameSize); + Assert.Equal(info.Position, updatedInfo.Position); + } + } + + [Fact] + public void Changing_WindowState_Should_Not_Change_Frame_Size_And_Position() + { + using (OpenWindow()) + { + var info = GetWindowInfo(); + + Session.FindElementByAccessibilityId("CurrentWindowState").SendClick(); + Session.FindElementByAccessibilityId("WindowStateMaximized").SendClick(); + Session.FindElementByAccessibilityId("CurrentWindowState").SendClick(); + Session.FindElementByAccessibilityId("WindowStateNormal").SendClick(); + var updatedInfo = GetWindowInfo(); + Assert.Equal(info.FrameSize, updatedInfo.FrameSize); + Assert.Equal(info.Position, updatedInfo.Position); + } + } + public static TheoryData StartupLocationData() { var sizes = new Size?[] { null, new Size(400, 300) }; @@ -391,8 +429,8 @@ namespace Avalonia.IntegrationTests.Appium } private IDisposable OpenWindow( - Size? size, - ShowWindowMode mode, + Size? size = null, + ShowWindowMode mode = ShowWindowMode.NonOwned, WindowStartupLocation location = WindowStartupLocation.Manual, WindowState state = Controls.WindowState.Normal, bool canResize = true,