diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 0678793..1ad1a97 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -18,7 +18,7 @@
Morten Nielsen - https://xaml.devMorten Nielsen - https://xaml.devlogo.png
- 2.0.1
+ 2.1.02.0.0
diff --git a/src/WinUIEx/WinUIEx.csproj b/src/WinUIEx/WinUIEx.csproj
index c72d5e0..84fc7fe 100644
--- a/src/WinUIEx/WinUIEx.csproj
+++ b/src/WinUIEx/WinUIEx.csproj
@@ -26,6 +26,7 @@
- Don't attempt to use window persistence in un-packaged applications.
- WebAuthenticator: now supports cancellation tokens.
- WebAuthenticator: Avoids an issue where state parameters are not always correctly handled/preserved correctly by OAuth services (reported in PR #92).
+ - Persistence: Add support for custom Window state persistence storage, for use by unpackaged applications (Issue #61).
diff --git a/src/WinUIEx/WindowManager.cs b/src/WinUIEx/WindowManager.cs
index 3998b54..df3b2ea 100644
--- a/src/WinUIEx/WindowManager.cs
+++ b/src/WinUIEx/WindowManager.cs
@@ -5,7 +5,6 @@
using Windows.Storage;
using WinUIEx.Messaging;
using Windows.Win32.UI.WindowsAndMessaging;
-using Microsoft.UI;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -22,7 +21,7 @@ public partial class WindowManager : IDisposable
private readonly Window _window;
private OverlappedPresenter overlappedPresenter;
private readonly static Dictionary> managers = new Dictionary>();
- private bool _isInitialized; // Set to true on first activation. Used to track persistance restore
+ private bool _isInitialized; // Set to true on first activation. Used to track persistence restore
private static bool TryGetWindowManager(Window window, [MaybeNullWhen(false)] out WindowManager manager)
{
@@ -254,7 +253,7 @@ private unsafe void OnWindowMessage(object? sender, Messaging.WindowMessageEvent
{
case WindowsMessages.WM_GETMINMAXINFO:
{
- if (_restoringPersistance)
+ if (_restoringPersistence)
break;
// Restrict min-size
MINMAXINFO* rect2 = (MINMAXINFO*)e.Message.LParam;
@@ -269,7 +268,7 @@ private unsafe void OnWindowMessage(object? sender, Messaging.WindowMessageEvent
break;
case WindowsMessages.WM_DPICHANGED:
{
- if (_restoringPersistance)
+ if (_restoringPersistence)
e.Handled = true; // Don't let WinUI resize the window due to a dpi change caused by restoring window position - we got this.
break;
}
@@ -294,34 +293,64 @@ private struct MINMAXINFO
#region Persistence
- ///
- /// Gets or sets a unique ID used for saving and restoring window size and position
- /// across sessions.
- ///
///
/// The ID must be set before the window activates. The window size and position
/// will only be restored if the monitor layout hasn't changed between application settings.
/// The property uses ApplicationData storage, and therefore is currently only functional for
/// packaged applications.
+ /// By default the property uses storage, and therefore is currently only functional for
+ /// packaged applications. If you're using an unpackaged application, you must also set the
+ /// property and manage persisting this across application settings.
///
+ ///
public string? PersistenceId { get; set; }
- private bool _restoringPersistance; // Flag used to avoid WinUI DPI adjustment
+ private bool _restoringPersistence; // Flag used to avoid WinUI DPI adjustment
+
+ ///
+ /// Gets or sets the persistence storage for maintaining window settings across application settings.
+ ///
+ ///
+ /// For a packaged application, this will be initialized automatically for you, and saved with the application identity using .
+ /// However for an unpackaged application, you will need to set this and serialize the property to/from disk between
+ /// application sessions. The provided dictionary is automatically written to when the window closes, and should be initialized
+ /// before any window with persistence opens.
+ ///
+ ///
+ public static IDictionary? PersistenceStorage { get; set; }
+
+ private static IDictionary? GetPersistenceStorage(bool createIfMissing)
+ {
+ if (PersistenceStorage is not null)
+ return PersistenceStorage;
+ if (Helpers.IsApplicationDataSupported)
+ {
+ try
+ {
+ if(ApplicationData.Current?.LocalSettings.Containers.TryGetValue("WinUIEx", out var container) == true)
+ return container.Values!;
+ else if (createIfMissing)
+ return ApplicationData.Current?.LocalSettings?.CreateContainer("WinUIEx", ApplicationDataCreateDisposition.Always)?.Values;
+
+ }
+ catch { }
+ }
+ return null;
+ }
private void LoadPersistence()
{
- if (!string.IsNullOrEmpty(PersistenceId) && Helpers.IsApplicationDataSupported)
+ if (!string.IsNullOrEmpty(PersistenceId))
{
try
{
- if (ApplicationData.Current?.LocalSettings?.Containers is null ||
- !ApplicationData.Current.LocalSettings.Containers.ContainsKey("WinUIEx"))
+ var winuiExSettings = GetPersistenceStorage(false);
+ if (winuiExSettings is null)
return;
byte[]? data = null;
- var winuiExSettings = ApplicationData.Current.LocalSettings.CreateContainer("WinUIEx", ApplicationDataCreateDisposition.Existing);
- if (winuiExSettings is not null && winuiExSettings.Values.ContainsKey($"WindowPersistance_{PersistenceId}"))
+ if (winuiExSettings.ContainsKey($"WindowPersistance_{PersistenceId}"))
{
- var base64 = winuiExSettings.Values[$"WindowPersistance_{PersistenceId}"] as string;
+ var base64 = winuiExSettings[$"WindowPersistance_{PersistenceId}"] as string;
if(base64 != null)
data = Convert.FromBase64String(base64);
}
@@ -354,9 +383,9 @@ private void LoadPersistence()
retobj.showCmd = SHOW_WINDOW_CMD.SW_MAXIMIZE;
else if (retobj.showCmd != SHOW_WINDOW_CMD.SW_MAXIMIZE)
retobj.showCmd = SHOW_WINDOW_CMD.SW_NORMAL;
- _restoringPersistance = true;
+ _restoringPersistence = true;
Windows.Win32.PInvoke.SetWindowPlacement(new Windows.Win32.Foundation.HWND(_window.GetWindowHandle()), in retobj);
- _restoringPersistance = false;
+ _restoringPersistence = false;
}
catch { }
}
@@ -364,35 +393,37 @@ private void LoadPersistence()
private void SavePersistence()
{
- if (!string.IsNullOrEmpty(PersistenceId) && Helpers.IsApplicationDataSupported)
+ if (!string.IsNullOrEmpty(PersistenceId))
{
- // Store monitor info - we won't restore on original screen if original monitor layout has changed
- using var data = new System.IO.MemoryStream();
- using var sw = new System.IO.BinaryWriter(data);
- var monitors = MonitorInfo.GetDisplayMonitors();
- sw.Write(monitors.Count);
- foreach (var monitor in monitors)
+ var winuiExSettings = GetPersistenceStorage(true);
+ if (winuiExSettings is not null)
{
- sw.Write(monitor.Name);
- sw.Write(monitor.RectMonitor.Left);
- sw.Write(monitor.RectMonitor.Top);
- sw.Write(monitor.RectMonitor.Right);
- sw.Write(monitor.RectMonitor.Bottom);
+ // Store monitor info - we won't restore on original screen if original monitor layout has changed
+ using var data = new System.IO.MemoryStream();
+ using var sw = new System.IO.BinaryWriter(data);
+ var monitors = MonitorInfo.GetDisplayMonitors();
+ sw.Write(monitors.Count);
+ foreach (var monitor in monitors)
+ {
+ sw.Write(monitor.Name);
+ sw.Write(monitor.RectMonitor.Left);
+ sw.Write(monitor.RectMonitor.Top);
+ 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);
+
+ int structSize = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
+ IntPtr buffer = Marshal.AllocHGlobal(structSize);
+ Marshal.StructureToPtr(placement, buffer, false);
+ byte[] placementData = new byte[structSize];
+ Marshal.Copy(buffer, placementData, 0, structSize);
+ Marshal.FreeHGlobal(buffer);
+ sw.Write(placementData);
+ sw.Flush();
+ winuiExSettings[$"WindowPersistance_{PersistenceId}"] = Convert.ToBase64String(data.ToArray());
}
- var 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);
- Marshal.StructureToPtr(placement, buffer, false);
- byte[] placementData = new byte[structSize];
- Marshal.Copy(buffer, placementData, 0, structSize);
- Marshal.FreeHGlobal(buffer);
- sw.Write(placementData);
- sw.Flush();
- var winuiExSettings = ApplicationData.Current?.LocalSettings?.CreateContainer("WinUIEx", ApplicationDataCreateDisposition.Always);
- if (winuiExSettings != null)
- winuiExSettings.Values[$"WindowPersistance_{PersistenceId}"] = Convert.ToBase64String(data.ToArray());
}
}
#endregion
diff --git a/src/WinUIExSample/App.xaml.cs b/src/WinUIExSample/App.xaml.cs
index fc6b38d..38dd38c 100644
--- a/src/WinUIExSample/App.xaml.cs
+++ b/src/WinUIExSample/App.xaml.cs
@@ -1,20 +1,13 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
+using System.Text.Json.Nodes;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-using Microsoft.UI.Xaml.Shapes;
-using Windows.ApplicationModel;
-using Windows.ApplicationModel.Activation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
+using System.Runtime.InteropServices;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -33,6 +26,14 @@ public partial class App : Application
public App()
{
this.InitializeComponent();
+ int length = 0;
+ var sb = new System.Text.StringBuilder(0);
+ int result = GetCurrentPackageFullName(ref length, sb);
+ if(result == 15700L)
+ {
+ // Not a packaged app. Configure file-based persistence instead
+ WinUIEx.WindowManager.PersistenceStorage = new FilePersistence("WinUIExPersistence.json");
+ }
}
///
@@ -48,5 +49,82 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
}
private Window m_window;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, System.Text.StringBuilder packageFullName);
+
+ private class FilePersistence : IDictionary
+ {
+ private readonly Dictionary _data = new Dictionary();
+ private readonly string _file;
+
+ public FilePersistence(string filename)
+ {
+ _file = filename;
+ try
+ {
+ if (File.Exists(filename))
+ {
+ var jo = System.Text.Json.Nodes.JsonObject.Parse(File.ReadAllText(filename)) as JsonObject;
+ foreach(var node in jo)
+ {
+ if (node.Value is JsonValue jvalue && jvalue.TryGetValue(out string value))
+ _data[node.Key] = value;
+ }
+ }
+ }
+ catch { }
+ }
+ private void Save()
+ {
+ JsonObject jo = new JsonObject();
+ foreach(var item in _data)
+ {
+ if (item.Value is string s) // In this case we only need string support. TODO: Support other types
+ jo.Add(item.Key, s);
+ }
+ File.WriteAllText(_file, jo.ToJsonString());
+ }
+ public object this[string key] { get => _data[key]; set { _data[key] = value; Save();} }
+
+ public ICollection Keys => _data.Keys;
+
+ public ICollection