diff --git a/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs b/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs index f3ee4110993c..06cd14058648 100644 --- a/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs +++ b/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs @@ -164,6 +164,7 @@ await SetupWindowForTests(window, async () => await OnLoadedAsync(content as VisualElement); +#if !WINDOWS if (window is Window controlsWindow) { if (!controlsWindow.IsActivated) @@ -174,6 +175,8 @@ await SetupWindowForTests(window, async () => controlsWindow = null; window.Activated(); } +#endif + #if WINDOWS await Task.Delay(10); #endif @@ -187,6 +190,7 @@ await SetupWindowForTests(window, async () => throw new Exception($"I can't work with {typeof(THandler)}"); +#if !WINDOWS bool isActivated = controlsWindow?.IsActivated ?? false; bool isDestroyed = controlsWindow?.IsDestroyed ?? false; @@ -195,6 +199,8 @@ await SetupWindowForTests(window, async () => if (!isDestroyed) window.Destroying(); +#endif + }, mauiContext); } finally diff --git a/src/Controls/tests/DeviceTests/Elements/Window/WindowTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Window/WindowTests.Windows.cs index 399ae76b6656..d0329b6bbc71 100644 --- a/src/Controls/tests/DeviceTests/Elements/Window/WindowTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Window/WindowTests.Windows.cs @@ -5,6 +5,7 @@ using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Graphics.Win2D; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; using Xunit; using WPanel = Microsoft.UI.Xaml.Controls.Panel; @@ -134,5 +135,39 @@ await CreateHandlerAndAddToWindow(window, async (handler) => } }); } + + + [Collection(ControlsHandlerTestBase.RunInNewWindowCollection)] + public class WindowTestsRunInNewWindowCollection : ControlsHandlerTestBase + { + [Fact] + public async Task MinimizeAndThenMaximizingWorks() + { + var window = new Window(new ContentPage()); + + int activated = 0; + int deactivated = 0; + + window.Activated += (_, __) => activated++; + window.Deactivated += (_, __) => deactivated++; + + await CreateHandlerAndAddToWindow(window, async (handler) => + { + var platformWindow = window.Handler.PlatformView as UI.Xaml.Window; + + await Task.Yield(); + + for (int i = 0; i < 2; i++) + { + platformWindow.Restore(); + await Task.Yield(); + platformWindow.Minimize(); + } + }); + + Assert.Equal(2, activated); + Assert.Equal(2, deactivated); + } + } } } diff --git a/src/Controls/tests/DeviceTests/Stubs/MauiAppNewWindowStub.Windows.cs b/src/Controls/tests/DeviceTests/Stubs/MauiAppNewWindowStub.Windows.cs index 256dc028501c..05e078cd6927 100644 --- a/src/Controls/tests/DeviceTests/Stubs/MauiAppNewWindowStub.Windows.cs +++ b/src/Controls/tests/DeviceTests/Stubs/MauiAppNewWindowStub.Windows.cs @@ -88,14 +88,17 @@ void OnClosed(object sender, UI.Xaml.WindowEventArgs args) void OnActivated(object sender, UI.Xaml.WindowActivatedEventArgs args) { - if (Window is not null) + if (args.WindowActivationState != UI.Xaml.WindowActivationState.Deactivated) { - if (!Window.IsActivated) + if (Window is not null) + { + if (!Window.IsActivated) + _window.Activated(); + } + else + { _window.Activated(); - } - else - { - _window.Activated(); + } } } diff --git a/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.Windows.cs b/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.Windows.cs index 69457787f52c..7648d44ad421 100644 --- a/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.Windows.cs +++ b/src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.Windows.cs @@ -29,6 +29,7 @@ static void OnConfigureLifeCycle(IWindowsLifecycleBuilder windows) { case UI.Xaml.WindowActivationState.CodeActivated: case UI.Xaml.WindowActivationState.PointerActivated: + System.Diagnostics.Debug.WriteLine($"{window.GetHashCode()} {args.WindowActivationState}"); window.GetWindow()?.Activated(); break; case UI.Xaml.WindowActivationState.Deactivated: diff --git a/src/Core/src/Platform/Windows/ApplicationExtensions.cs b/src/Core/src/Platform/Windows/ApplicationExtensions.cs index cbfaf3821547..6d3a39cac028 100644 --- a/src/Core/src/Platform/Windows/ApplicationExtensions.cs +++ b/src/Core/src/Platform/Windows/ApplicationExtensions.cs @@ -13,9 +13,9 @@ public static void CreatePlatformWindow(this UI.Xaml.Application platformApplica if (application.Handler?.MauiContext is not IMauiContext applicationContext) return; - var winuiWndow = new MauiWinUIWindow(); + var winuiWindow = new MauiWinUIWindow(); - var mauiContext = applicationContext!.MakeWindowScope(winuiWndow, out var windowScope); + var mauiContext = applicationContext!.MakeWindowScope(winuiWindow, out var windowScope); applicationContext.Services.InvokeLifecycleEvents(del => del(mauiContext)); @@ -25,11 +25,11 @@ public static void CreatePlatformWindow(this UI.Xaml.Application platformApplica var window = application.CreateWindow(activationState); - winuiWndow.SetWindowHandler(window, mauiContext); + winuiWindow.SetWindowHandler(window, mauiContext); - applicationContext.Services.InvokeLifecycleEvents(del => del(winuiWndow)); - - winuiWndow.Activate(); + applicationContext.Services.InvokeLifecycleEvents(del => del(winuiWindow)); + winuiWindow.SetWindow(window); + winuiWindow.Activate(); } } } \ No newline at end of file diff --git a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs index fe1e0f05c209..113195b2a077 100644 --- a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs +++ b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs @@ -19,6 +19,7 @@ public class MauiWinUIWindow : UI.Xaml.Window, IPlatformSizeRestrictedWindow IntPtr _windowIcon; bool _enableResumeEvent; + bool _isActivated; UI.Xaml.UIElement? _customTitleBar; public MauiWinUIWindow() @@ -42,13 +43,25 @@ protected virtual void OnActivated(object sender, UI.Xaml.WindowActivatedEventAr { if (args.WindowActivationState != UI.Xaml.WindowActivationState.Deactivated) { + // We have to track isActivated calls because WinUI will call OnActivated Twice + // when maximizing a window + // https://github.com/microsoft/microsoft-ui-xaml/issues/7343 + if (_isActivated) + return; + + _isActivated = true; + if (_enableResumeEvent) - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents(del => del(this)); + Services?.InvokeLifecycleEvents(del => del(this)); else _enableResumeEvent = true; } + else + { + _isActivated = false; + } - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents(del => del(this, args)); + Services?.InvokeLifecycleEvents(del => del(this, args)); } private void OnClosedPrivate(object sender, UI.Xaml.WindowEventArgs args) @@ -60,23 +73,25 @@ private void OnClosedPrivate(object sender, UI.Xaml.WindowEventArgs args) DestroyIcon(_windowIcon); _windowIcon = IntPtr.Zero; } + + Window = null; } protected virtual void OnClosed(object sender, UI.Xaml.WindowEventArgs args) { - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents(del => del(this, args)); + Services?.InvokeLifecycleEvents(del => del(this, args)); } protected virtual void OnVisibilityChanged(object sender, UI.Xaml.WindowVisibilityChangedEventArgs args) { - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents(del => del(this, args)); + Services?.InvokeLifecycleEvents(del => del(this, args)); } public IntPtr WindowHandle => _windowManager.WindowHandle; void SubClassingWin32() { - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + Services?.InvokeLifecycleEvents( del => del(this, new WindowsPlatformWindowSubclassedEventArgs(WindowHandle))); _windowManager.WindowMessage += OnWindowMessage; @@ -120,7 +135,7 @@ void OnWindowMessage(object? sender, WindowMessageEventArgs e) } } - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + Services?.InvokeLifecycleEvents( m => m.Invoke(this, new WindowsPlatformMessageEventArgs(e.Hwnd, e.MessageId, e.WParam, e.LParam))); } } @@ -153,6 +168,12 @@ void SetIcon() SizeInt32 IPlatformSizeRestrictedWindow.MaximumSize { get; set; } = DefaultMaximumSize; + internal IWindow? Window { get; private set; } + + internal IServiceProvider? Services => + Window?.Handler?.GetServiceProvider() ?? + MauiWinUIApplication.Current.Services; + internal UI.Xaml.UIElement? MauiCustomTitleBar { get => _customTitleBar; @@ -178,6 +199,11 @@ internal void UpdateTitleOnCustomTitleBar() [DllImport("user32.dll", SetLastError = true)] static extern int DestroyIcon(IntPtr hIcon); + + internal void SetWindow(IWindow window) + { + Window = window; + } } interface IPlatformSizeRestrictedWindow diff --git a/src/Core/src/Platform/Windows/WindowExtensions.cs b/src/Core/src/Platform/Windows/WindowExtensions.cs index 69ea9ae2842a..a54d2c2212b6 100644 --- a/src/Core/src/Platform/Windows/WindowExtensions.cs +++ b/src/Core/src/Platform/Windows/WindowExtensions.cs @@ -175,6 +175,9 @@ public static void UpdateMaximumSize(this UI.Xaml.Window platformWindow, IWindow return window; } + if (platformWindow is MauiWinUIWindow mauiWindow) + return mauiWindow?.Window; + return null; } @@ -198,6 +201,27 @@ public static float GetDisplayDensity(this UI.Xaml.Window platformWindow) return PlatformMethods.GetDpiForWindow(hwnd) / DeviceDisplay.BaseLogicalDpi; } + internal static void Minimize(this UI.Xaml.Window platformWindow) + { + PlatformMethods + .ShowWindow(platformWindow.GetWindowHandle(), + PlatformMethods.ShowWindowFlags.SW_MINIMIZE); + } + + internal static void Maximize(this UI.Xaml.Window platformWindow) + { + PlatformMethods + .ShowWindow(platformWindow.GetWindowHandle(), + PlatformMethods.ShowWindowFlags.SW_MAXIMIZE); + } + + internal static void Restore(this UI.Xaml.Window platformWindow) + { + PlatformMethods + .ShowWindow(platformWindow.GetWindowHandle(), + PlatformMethods.ShowWindowFlags.SW_RESTORE); + } + public static UI.Windowing.AppWindow? GetAppWindow(this UI.Xaml.Window platformWindow) { var hwnd = platformWindow.GetWindowHandle(); diff --git a/src/Essentials/src/Platform/PlatformMethods.uwp.cs b/src/Essentials/src/Platform/PlatformMethods.uwp.cs index 73a5ab97397e..e3b5eb542ed1 100644 --- a/src/Essentials/src/Platform/PlatformMethods.uwp.cs +++ b/src/Essentials/src/Platform/PlatformMethods.uwp.cs @@ -79,6 +79,9 @@ public static long GetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex) [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, SpecialWindowHandles hWndInsertAfter, int x, int y, int width, int height, SetWindowPosFlags uFlags); + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, ShowWindowFlags uFlags); + [DllImport("comctl32.dll", CharSet = CharSet.Auto)] public static extern IntPtr DefSubclassProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); @@ -145,6 +148,23 @@ public enum SetWindowPosFlags : uint SWP_SHOWWINDOW = 0x0040, } + [Flags] + public enum ShowWindowFlags : uint + { + SW_HIDE = 0, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_FORCEMINIMIZE = 11, + } + [Flags] public enum ExtendedWindowStyles : uint { diff --git a/src/TestUtils/src/DeviceTests.Runners/VisualRunner/ViewModels/TestAssemblyViewModel.cs b/src/TestUtils/src/DeviceTests.Runners/VisualRunner/ViewModels/TestAssemblyViewModel.cs index ff33a392a6dd..4b0b1e2b9a6f 100644 --- a/src/TestUtils/src/DeviceTests.Runners/VisualRunner/ViewModels/TestAssemblyViewModel.cs +++ b/src/TestUtils/src/DeviceTests.Runners/VisualRunner/ViewModels/TestAssemblyViewModel.cs @@ -25,7 +25,7 @@ public class TestAssemblyViewModel : ViewModelBase TestState _result; TestState _resultFilter; RunStatus _runStatus; - string? _searchQuery; + string? _searchQuery = "MinimizeAndThenMaximizingWorks"; string? _detailText; string? _displayName;