From dd9217ee9db26db057bcccd0ca7f9dcd567b1fb2 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 27 Apr 2026 09:47:48 +0200 Subject: [PATCH] Win32: fix BorderOnly maximized position on secondary screens in more cases (#21229) * Update IntegrationTests.Win32 to test all screens * Win32: use GetWindowPlacement to determine the screen to maximize to --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 6 ++-- .../ExtendClientAreaWindowTests.cs | 35 +++++++++++-------- .../StandardWindowTests.cs | 34 +++++++++--------- .../UnmanagedMethods.cs | 5 +++ .../WindowExtensions.cs | 8 ++--- 5 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f003da9296..d9be50a967 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -852,9 +852,11 @@ namespace Avalonia.Win32 !_isFullScreenActive && !flags.HasAllFlags(SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE)) { - // Prefer ScreenFromRect as it contains the new position. + GetWindowPlacement(Hwnd, out var placement); + + // Prefer ScreenFromRect with the window's restored bounds. // If the window was minimized, ScreenFromHwnd won't return the correct monitor at this point. - var screen = Screen.ScreenFromRect(new PixelRect(pos->x, pos->y, pos->cx, pos->cy)) + var screen = Screen.ScreenFromRect(placement.NormalPosition.ToPixelRect()) ?? Screen.ScreenFromHwnd(Hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); if (screen is not null) diff --git a/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs b/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs index 7c7d34ac14..2e94734d17 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs @@ -3,17 +3,17 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.VisualTree; using Xunit; +using static Avalonia.IntegrationTests.Win32.UnmanagedMethods; namespace Avalonia.IntegrationTests.Win32; public abstract class ExtendClientAreaWindowTests : IDisposable { - private const double ClientWidth = 200; - private const double ClientHeight = 200; + private const int ClientWidth = 200; + private const int ClientHeight = 200; private Window? _window; @@ -28,10 +28,13 @@ public abstract class ExtendClientAreaWindowTests : IDisposable protected abstract WindowDecorations Decorations { get; } - public static MatrixTheoryData States - => new([true, false], Enum.GetValues()); + public static MatrixTheoryData States + => new( + Enumerable.Range(0, GetSystemMetrics(SM_CMONITORS)), + Enum.GetValues(), + [true, false]); - private async Task InitWindowAsync(WindowState state, bool canResize) + private async Task InitWindowAsync(int screenIndex, WindowState state, bool canResize) { Assert.Null(_window); @@ -44,7 +47,6 @@ public abstract class ExtendClientAreaWindowTests : IDisposable Width = ClientWidth, Height = ClientHeight, WindowStartupLocation = WindowStartupLocation.Manual, - Position = new PixelPoint(50, 50), Content = new Border { Background = Brushes.DodgerBlue, @@ -53,6 +55,9 @@ public abstract class ExtendClientAreaWindowTests : IDisposable } }; + var screenCenter = _window.Screens.All[screenIndex].Bounds.Center; + _window.Position = new PixelPoint(screenCenter.X - ClientWidth / 2, screenCenter.Y - ClientHeight / 2); + _window.Show(); await Window.WhenLoadedAsync(); @@ -60,9 +65,9 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Normal_State_Respects_Client_Size(bool canResize, WindowState initialState) + public async Task Normal_State_Respects_Client_Size(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Normal) Window.WindowState = WindowState.Normal; @@ -79,16 +84,16 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Maximized_State_Fills_Screen_Working_Area(bool canResize, WindowState initialState) + public async Task Maximized_State_Fills_Screen_Working_Area(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Maximized) Window.WindowState = WindowState.Maximized; // The client size should match the screen working area var clientSize = Window.GetWin32ClientSize(); - var screenWorkingArea = Window.GetScreen().WorkingArea; + var screenWorkingArea = Window.GetScreenAtIndex(screenIndex).WorkingArea; Assert.Equal(screenWorkingArea.Size, clientSize); VerifyMaximizedState(); @@ -98,16 +103,16 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task FullScreen_State_Fills_Screen(bool canResize, WindowState initialState) + public async Task FullScreen_State_Fills_Screen(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.FullScreen) Window.WindowState = WindowState.FullScreen; // The client size should match the screen bounds var clientSize = Window.GetWin32ClientSize(); - var screenBounds = Window.GetScreen().Bounds; + var screenBounds = Window.GetScreenAtIndex(screenIndex).Bounds; Assert.Equal(screenBounds.Width, clientSize.Width); Assert.Equal(screenBounds.Height, clientSize.Height); diff --git a/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs b/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs index 5971c4dc6c..233f69122b 100644 --- a/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs @@ -2,19 +2,16 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.Chrome; -using Avalonia.Interactivity; using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.VisualTree; using Xunit; +using static Avalonia.IntegrationTests.Win32.UnmanagedMethods; namespace Avalonia.IntegrationTests.Win32; public abstract class StandardWindowTests : IDisposable { - private const double ClientWidth = 200; - private const double ClientHeight = 200; + private const int ClientWidth = 200; + private const int ClientHeight = 200; private Window? _window; @@ -31,10 +28,13 @@ public abstract class StandardWindowTests : IDisposable protected abstract bool HasCaption { get; } - public static MatrixTheoryData States - => new([true, false], Enum.GetValues()); + public static MatrixTheoryData States + => new( + Enumerable.Range(0, GetSystemMetrics(SM_CMONITORS)), + Enum.GetValues(), + [true, false]); - private async Task InitWindowAsync(WindowState state, bool canResize) + private async Task InitWindowAsync(int screenIndex, WindowState state, bool canResize) { Assert.Null(_window); @@ -47,7 +47,6 @@ public abstract class StandardWindowTests : IDisposable Width = ClientWidth, Height = ClientHeight, WindowStartupLocation = WindowStartupLocation.Manual, - Position = new PixelPoint(50, 50), Content = new Border { Background = Brushes.DodgerBlue, @@ -56,6 +55,9 @@ public abstract class StandardWindowTests : IDisposable } }; + var screenCenter = _window.Screens.All[screenIndex].Bounds.Center; + _window.Position = new PixelPoint(screenCenter.X - ClientWidth / 2, screenCenter.Y - ClientHeight / 2); + _window.Show(); await Window.WhenLoadedAsync(); @@ -63,16 +65,16 @@ public abstract class StandardWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Maximized_State_Fills_Screen_Working_Area(bool canResize, WindowState initialState) + public async Task Maximized_State_Fills_Screen_Working_Area(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Maximized) Window.WindowState = WindowState.Maximized; // The client size should match the screen working area var clientSize = Window.GetWin32ClientSize(); - var screenWorkingArea = Window.GetScreen().WorkingArea; + var screenWorkingArea = Window.GetScreenAtIndex(screenIndex).WorkingArea; if (HasCaption) { @@ -85,16 +87,16 @@ public abstract class StandardWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task FullScreen_State_Fills_Screen(bool canResize, WindowState initialState) + public async Task FullScreen_State_Fills_Screen(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.FullScreen) Window.WindowState = WindowState.FullScreen; // The client size should match the screen bounds var clientSize = Window.GetWin32ClientSize(); - var screenBounds = Window.GetScreen().Bounds; + var screenBounds = Window.GetScreenAtIndex(screenIndex).Bounds; Assert.Equal(screenBounds.Width, clientSize.Width); Assert.Equal(screenBounds.Height, clientSize.Height); diff --git a/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs b/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs index 591ed182c4..4f4f4c03d0 100644 --- a/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs +++ b/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs @@ -13,6 +13,9 @@ internal static partial class UnmanagedMethods [return: MarshalAs(UnmanagedType.Bool)] public static partial bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + [LibraryImport("user32.dll")] + public static partial int GetSystemMetrics(int nIndex); + public struct RECT { public int left; @@ -20,4 +23,6 @@ internal static partial class UnmanagedMethods public int right; public int bottom; } + + public const int SM_CMONITORS = 80; } diff --git a/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs b/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs index 1d92324961..acf154ddde 100644 --- a/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs @@ -27,12 +27,8 @@ internal static class WindowExtensions } } - public static Screen GetScreen(this Window window) - { - var screen = window.Screens.ScreenFromWindow(window); - Assert.NotNull(screen); - return screen; - } + public static Screen GetScreenAtIndex(this Window window, int index) + => window.Screens.All[index]; public static PixelSize GetWin32ClientSize(this Window window) {