Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/WinUIEx.Tests/WindowManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.UI.Windowing;
using System.Collections.Generic;

namespace WinUIUnitTests
{
Expand Down Expand Up @@ -234,6 +235,94 @@ public async Task PersistenceId() => await UITestHelper.RunWindowTest(async (win
newWindow.Close();
}
});

[TestMethod]
public async Task PersistenceIgnoresFullScreenBounds() => await UITestHelper.RunWindowTest(async (window) =>
{
var previousStorage = WindowManager.PersistenceStorage;
WindowManager.PersistenceStorage = new Dictionary<string, object>();
try
{
window.Content = new Grid();
var manager = WindowManager.Get(window);
manager.PersistenceId = nameof(PersistenceIgnoresFullScreenBounds);
await window.Content.LoadAsync();
window.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(45, 67, 456, 345));
await Task.Delay(150);
window.AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
await Task.Delay(150);
Assert.AreEqual(AppWindowPresenterKind.FullScreen, window.AppWindow.Presenter.Kind);
window.Close();

Window newWindow = new Window();
var manager2 = WindowManager.Get(newWindow);
manager2.PersistenceId = manager.PersistenceId;
newWindow.Content = new Grid();
try
{
newWindow.Activate();
await newWindow.Content.LoadAsync();
Assert.AreEqual(AppWindowPresenterKind.Overlapped, newWindow.AppWindow.Presenter.Kind);
Assert.AreEqual(45, newWindow.AppWindow.Position.X);
Assert.AreEqual(67, newWindow.AppWindow.Position.Y);
Assert.AreEqual(456, newWindow.AppWindow.Size.Width);
Assert.AreEqual(345, newWindow.AppWindow.Size.Height);
}
finally
{
newWindow.Close();
}
}
finally
{
WindowManager.PersistenceStorage = previousStorage;
}
});

[TestMethod]
public async Task PersistenceRetainsMaximizedStateBeforeFullScreen() => await UITestHelper.RunWindowTest(async (window) =>
{
var previousStorage = WindowManager.PersistenceStorage;
WindowManager.PersistenceStorage = new Dictionary<string, object>();
try
{
window.Content = new Grid();
var manager = WindowManager.Get(window);
manager.PersistenceId = nameof(PersistenceRetainsMaximizedStateBeforeFullScreen);
await window.Content.LoadAsync();
window.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(45, 67, 456, 345));
((OverlappedPresenter)window.AppWindow.Presenter).Maximize();
await Task.Delay(150);
window.AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
await Task.Delay(150);
window.Close();

Window newWindow = new Window();
var manager2 = WindowManager.Get(newWindow);
manager2.PersistenceId = manager.PersistenceId;
newWindow.Content = new Grid();
try
{
newWindow.Activate();
await newWindow.Content.LoadAsync();
var presenter = (OverlappedPresenter)newWindow.AppWindow.Presenter;
Assert.AreEqual(OverlappedPresenterState.Maximized, presenter.State);
presenter.Restore();
Assert.AreEqual(45, newWindow.AppWindow.Position.X);
Assert.AreEqual(67, newWindow.AppWindow.Position.Y);
Assert.AreEqual(456, newWindow.AppWindow.Size.Width);
Assert.AreEqual(345, newWindow.AppWindow.Size.Height);
}
finally
{
newWindow.Close();
}
}
finally
{
WindowManager.PersistenceStorage = previousStorage;
}
});
}

}
1 change: 1 addition & 0 deletions src/WinUIEx/WinUIEx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageId>WinUIEx</PackageId>
<Product>WinUI Extensions</Product>
<PackageReleaseNotes>
- Preserve the pre-fullscreen window size, position, and maximized state when using `WindowManager` persistence (Issue #253).
- Add SVG file support to `TrayIcon.SetIcon(string)`, including relative path resolution and smart `.ico`/`.svg` detection.
</PackageReleaseNotes>
</PropertyGroup>
Expand Down
44 changes: 42 additions & 2 deletions src/WinUIEx/WindowManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public partial class WindowManager : IDisposable
private readonly WindowMessageMonitor _monitor;
private readonly Window _window;
private OverlappedPresenter overlappedPresenter;
private readonly Microsoft.UI.Dispatching.DispatcherQueueTimer _persistenceTimer;
private readonly static Dictionary<IntPtr, WeakReference<WindowManager>> managers = new Dictionary<IntPtr, WeakReference<WindowManager>>();
private bool _isInitialized; // Set to true on first activation. Used to track persistence restore

Expand Down Expand Up @@ -75,6 +76,11 @@ private WindowManager(Window window, WindowMessageMonitor monitor)
AppWindow.Changed += AppWindow_Changed;
AppWindow.Destroying += AppWindow_Destroying;

_persistenceTimer = _window.DispatcherQueue.CreateTimer();
// Presenter changes are reported after their resize, so wait until the resize burst settles.
_persistenceTimer.Interval = TimeSpan.FromMilliseconds(100);
_persistenceTimer.IsRepeating = false;
_persistenceTimer.Tick += PersistenceTimer_Tick;
overlappedPresenter = AppWindow.Presenter as OverlappedPresenter ?? Microsoft.UI.Windowing.OverlappedPresenter.Create();
managers[window.GetWindowHandle()] = new WeakReference<WindowManager>(this);
switch (overlappedPresenter.State)
Expand Down Expand Up @@ -103,6 +109,7 @@ private void Window_Activated(object sender, WindowActivatedEventArgs args)
private void Window_Closed(object sender, WindowEventArgs args)
{
CleanUpBackdrop();
_persistenceTimer.Stop();
SavePersistence();
_trayIcon?.Dispose();
_trayIcon = null;
Expand Down Expand Up @@ -134,6 +141,8 @@ private void Dispose(bool disposing)
_window.Activated -= Window_Activated;
_window.Closed -= Window_Closed;
_window.VisibilityChanged -= Window_VisibilityChanged;
_persistenceTimer.Stop();
_persistenceTimer.Tick -= PersistenceTimer_Tick;
_monitor.WindowMessageReceived -= OnWindowMessage;
_monitor.Dispose();
}
Expand Down Expand Up @@ -267,6 +276,8 @@ private unsafe void OnWindowMessage(object? sender, Messaging.WindowMessageEvent
{
_isInitialized = true;
LoadPersistence();
if (AppWindow.Presenter.Kind == AppWindowPresenterKind.Overlapped)
CacheOverlappedWindowPlacement();
_window.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
InitBackdrop();
Expand Down Expand Up @@ -404,6 +415,20 @@ public WindowState WindowState
public string? PersistenceId { get; set; }

private bool _restoringPersistence; // Flag used to avoid WinUI DPI adjustment
private WINDOWPLACEMENT? _overlappedWindowPlacement;

private void CacheOverlappedWindowPlacement()
{
var placement = new WINDOWPLACEMENT();
Windows.Win32.PInvoke.GetWindowPlacement(new Windows.Win32.Foundation.HWND(_window.GetWindowHandle()), ref placement);
_overlappedWindowPlacement = placement;
}

private void PersistenceTimer_Tick(Microsoft.UI.Dispatching.DispatcherQueueTimer sender, object args)
{
if (AppWindow.Presenter.Kind == AppWindowPresenterKind.Overlapped)
CacheOverlappedWindowPlacement();
}

/// <summary>
/// Gets or sets the persistence storage for maintaining window settings across application settings.
Expand Down Expand Up @@ -508,8 +533,16 @@ private void SavePersistence()
sw.Write(monitor.RectMonitor.Right);
sw.Write(monitor.RectMonitor.Bottom);
}
var placement = new WINDOWPLACEMENT();
Windows.Win32.PInvoke.GetWindowPlacement(new Windows.Win32.Foundation.HWND(_window.GetWindowHandle()), ref placement);
WINDOWPLACEMENT placement;
if (AppWindow.Presenter.Kind != AppWindowPresenterKind.Overlapped && _overlappedWindowPlacement.HasValue)
{
placement = _overlappedWindowPlacement.Value;
}
else
{
placement = new WINDOWPLACEMENT();
Windows.Win32.PInvoke.GetWindowPlacement(new Windows.Win32.Foundation.HWND(_window.GetWindowHandle()), ref placement);
}

int structSize = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
IntPtr buffer = Marshal.AllocHGlobal(structSize);
Expand All @@ -527,6 +560,11 @@ private void SavePersistence()

private void AppWindow_Changed(Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowChangedEventArgs args)
{
if ((args.DidPositionChange || args.DidSizeChange) && !string.IsNullOrEmpty(PersistenceId))
{
_persistenceTimer.Stop();
_persistenceTimer.Start();
}
if (args.DidPositionChange)
PositionChanged?.Invoke(this, sender.Position);
if (args.DidPresenterChange)
Expand All @@ -536,6 +574,8 @@ private void AppWindow_Changed(Microsoft.UI.Windowing.AppWindow sender, Microsof
overlappedPresenter = op;
_IsTitleBarVisible = op.HasTitleBar;
}
if (sender.Presenter.Kind == AppWindowPresenterKind.Overlapped)
CacheOverlappedWindowPlacement();
PresenterChanged?.Invoke(this, sender.Presenter);
}
if(args.DidZOrderChange)
Expand Down
Loading