From 973149a136e8c6098b70aa57ce17d5bd0cffb0f1 Mon Sep 17 00:00:00 2001 From: Zakariathr22 Date: Thu, 5 Feb 2026 22:16:07 +0100 Subject: [PATCH 1/2] Add window state save/restore feature Adds support for saving and restoring main window size, position, scale, and maximized state between sessions. Introduces new settings properties and UI toggle for enabling/disabling this feature. Implements `WindowStateHelper` and `WindowState` classes for state management. Window state is restored on load and saved on close if enabled. --- .../Helpers/SettingsHelper/SettingsHelper.cs | 42 +++++++ WinUIGallery/Helpers/WindowStateHelper.cs | 109 ++++++++++++++++++ WinUIGallery/MainWindow.xaml.cs | 18 +++ WinUIGallery/Models/WindowState.cs | 14 +++ WinUIGallery/Pages/SettingsPage.xaml | 9 ++ WinUIGallery/Pages/SettingsPage.xaml.cs | 4 +- 6 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 WinUIGallery/Helpers/WindowStateHelper.cs create mode 100644 WinUIGallery/Models/WindowState.cs diff --git a/WinUIGallery/Helpers/SettingsHelper/SettingsHelper.cs b/WinUIGallery/Helpers/SettingsHelper/SettingsHelper.cs index 7e432895e..31dc9a13a 100644 --- a/WinUIGallery/Helpers/SettingsHelper/SettingsHelper.cs +++ b/WinUIGallery/Helpers/SettingsHelper/SettingsHelper.cs @@ -63,4 +63,46 @@ public void UpdateRecentlyVisited(Action> updater) updater(list); RecentlyVisited = list; } + + public int MainWindowPositionX + { + get => GetOrCreateDefault(0); + set => Set(value); + } + + public int MainWindowPositionY + { + get => GetOrCreateDefault(0); + set => Set(value); + } + + public int MainWindowWidth + { + get => GetOrCreateDefault(0); + set => Set(value); + } + + public int MainWindowHeight + { + get => GetOrCreateDefault(0); + set => Set(value); + } + + public bool IsMainWindowMaximized + { + get => GetOrCreateDefault(false); + set => Set(value); + } + + public double MainWindowScale + { + get => GetOrCreateDefault(1.0); + set => Set(value); + } + + public bool SaveWindowState + { + get => GetOrCreateDefault(false); + set => Set(value); + } } diff --git a/WinUIGallery/Helpers/WindowStateHelper.cs b/WinUIGallery/Helpers/WindowStateHelper.cs new file mode 100644 index 000000000..64d7c49a6 --- /dev/null +++ b/WinUIGallery/Helpers/WindowStateHelper.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using System; +using Windows.Graphics; +using WinUIGallery.Models; + +namespace WinUIGallery.Helpers; + +public static class WindowStateHelper +{ + private static void SaveWindowState(WindowState state) + { + var s = SettingsHelper.Current; + + s.MainWindowPositionX = state.PositionX; + s.MainWindowPositionY = state.PositionY; + s.MainWindowWidth = state.Width; + s.MainWindowHeight = state.Height; + s.MainWindowScale = state.Scale; + s.IsMainWindowMaximized = state.IsMaximized; + } + + private static WindowState? LoadWindowState() + { + var s = SettingsHelper.Current; + + bool isInvalid = + s.MainWindowWidth <= 0 || + s.MainWindowHeight <= 0 || + ( + s.MainWindowPositionX == 0 && + s.MainWindowPositionY == 0 && + s.MainWindowWidth == 0 && + s.MainWindowHeight == 0 && + s.MainWindowScale == 1.0 && + !s.IsMainWindowMaximized + ); + + if (isInvalid) + return null; + + return new WindowState + { + PositionX = s.MainWindowPositionX, + PositionY = s.MainWindowPositionY, + Width = s.MainWindowWidth, + Height = s.MainWindowHeight, + Scale = s.MainWindowScale, + IsMaximized = s.IsMainWindowMaximized + }; + } + + public static void Save(Window window) + { + var appWindow = window.AppWindow; + + double scale = window.Content.XamlRoot.RasterizationScale; + bool isMaximized = appWindow.Presenter is OverlappedPresenter p && + p.State == OverlappedPresenterState.Maximized; + + // Restore before saving accurate dimensions + (appWindow.Presenter as OverlappedPresenter)?.Restore(); + + var state = new WindowState + { + PositionX = appWindow.Position.X, + PositionY = appWindow.Position.Y, + Width = appWindow.Size.Width, + Height = appWindow.Size.Height, + Scale = scale, + IsMaximized = isMaximized + }; + + SaveWindowState(state); + } + + public static void ApplySavedState(Window window) + { + if (window.Content is FrameworkElement content) + { + double currentScale = content.XamlRoot.RasterizationScale; + var state = LoadWindowState(); + if (state is null) return; + + // adjust for DPI/scaling change + double scaleFactor = currentScale / state.Scale; + int width = (int)(state.Width * scaleFactor); + int height = (int)(state.Height * scaleFactor); + int x = state.PositionX; + int y = state.PositionY; + + // ensure window fits display area + var displayArea = DisplayArea.GetFromWindowId(window.AppWindow.Id, DisplayAreaFallback.Primary).WorkArea; + width = Math.Min(width, displayArea.Width); + height = Math.Min(height, displayArea.Height); + x = Math.Clamp(x, displayArea.X, displayArea.X + displayArea.Width - width); + y = Math.Clamp(y, displayArea.Y, displayArea.Y + displayArea.Height - height); + + window.AppWindow.Move(new PointInt32(x, y)); + window.AppWindow.Resize(new SizeInt32(width, height)); + + if (state.IsMaximized) + (window.AppWindow.Presenter as OverlappedPresenter)?.Maximize(); + } + } +} diff --git a/WinUIGallery/MainWindow.xaml.cs b/WinUIGallery/MainWindow.xaml.cs index ca9117756..e237b5fe7 100644 --- a/WinUIGallery/MainWindow.xaml.cs +++ b/WinUIGallery/MainWindow.xaml.cs @@ -55,6 +55,24 @@ public MainWindow() AdjustNavigationViewMargin(force: true); AppWindow.Changed += (_, _) => AdjustNavigationViewMargin(); } + + // Restore window state on load + if (SettingsHelper.Current.SaveWindowState) + { + if (Content is FrameworkElement content) + { + content.Loaded += (_, _) => WindowStateHelper.ApplySavedState(this); + } + } + + // Save window state on close + Closed += (_, _) => + { + if (SettingsHelper.Current.SaveWindowState) + { + WindowStateHelper.Save(this); + } + }; } // Adjusts the NavigationView margin based on the window state diff --git a/WinUIGallery/Models/WindowState.cs b/WinUIGallery/Models/WindowState.cs new file mode 100644 index 000000000..0b82f0679 --- /dev/null +++ b/WinUIGallery/Models/WindowState.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace WinUIGallery.Models; + +public class WindowState +{ + public int PositionX { get; set; } + public int PositionY { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public double Scale { get; set; } + public bool IsMaximized { get; set; } +} diff --git a/WinUIGallery/Pages/SettingsPage.xaml b/WinUIGallery/Pages/SettingsPage.xaml index e5d95315b..0449c6e7d 100644 --- a/WinUIGallery/Pages/SettingsPage.xaml +++ b/WinUIGallery/Pages/SettingsPage.xaml @@ -103,6 +103,15 @@ + + + + + + diff --git a/WinUIGallery/Pages/SettingsPage.xaml.cs b/WinUIGallery/Pages/SettingsPage.xaml.cs index 3b2011cfc..8829b0abb 100644 --- a/WinUIGallery/Pages/SettingsPage.xaml.cs +++ b/WinUIGallery/Pages/SettingsPage.xaml.cs @@ -28,7 +28,9 @@ public string Version } public string WinAppSdkRuntimeDetails => VersionHelper.WinAppSdkRuntimeDetails; - private int lastNavigationSelectionMode = 0; + private int lastNavigationSelectionMode = 0; + + private SettingsHelper Settings => SettingsHelper.Current; public SettingsPage() { From c2996aa22ca34f1ce81a248bee4a4088ed087b35 Mon Sep 17 00:00:00 2001 From: Zakariathr22 Date: Thu, 19 Feb 2026 07:50:47 +0100 Subject: [PATCH 2/2] Update window display area logic to use position, not ID --- WinUIGallery/Helpers/WindowStateHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WinUIGallery/Helpers/WindowStateHelper.cs b/WinUIGallery/Helpers/WindowStateHelper.cs index 64d7c49a6..cf6cec978 100644 --- a/WinUIGallery/Helpers/WindowStateHelper.cs +++ b/WinUIGallery/Helpers/WindowStateHelper.cs @@ -93,7 +93,7 @@ public static void ApplySavedState(Window window) int y = state.PositionY; // ensure window fits display area - var displayArea = DisplayArea.GetFromWindowId(window.AppWindow.Id, DisplayAreaFallback.Primary).WorkArea; + var displayArea = DisplayArea.GetFromPoint(new PointInt32(x,y), DisplayAreaFallback.Primary).WorkArea; width = Math.Min(width, displayArea.Width); height = Math.Min(height, displayArea.Height); x = Math.Clamp(x, displayArea.X, displayArea.X + displayArea.Width - width);