Skip to content
Open
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
98 changes: 95 additions & 3 deletions src/MahApps.Metro/Behaviors/WindowsSettingBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
#if !NET462
using System.Text.Json;
#endif
using System.Windows;
using System.Windows.Interop;
using Windows.Win32;
Expand Down Expand Up @@ -127,18 +131,32 @@ private void LoadWindowState()
catch (Exception e)
{
Trace.TraceError($"{this}: The settings for {window} could not be reloaded! {e}");
return;
// Don't return yet — try JSON fallback below
}

// check for existing placement and prevent empty bounds
if (settings.Placement is null || settings.Placement.normalPosition.IsEmpty)
var placement = settings.Placement;
if (placement is null || placement.normalPosition.IsEmpty)
{
#if !NET462
// Fallback: try to load from JSON backup if settings has no valid placement
if (TryLoadFromJsonFallback(window, out var fallbackPlacement))
{
placement = fallbackPlacement;
settings.Placement = fallbackPlacement;
}
else
{
return;
}
#else
return;
#endif
}

try
{
var wp = settings.Placement.ToWINDOWPLACEMENT();
var wp = placement!.ToWINDOWPLACEMENT();
WinApiHelper.SetWindowPlacement(window, wp);
}
catch (Exception ex)
Expand Down Expand Up @@ -210,11 +228,85 @@ private void SaveWindowState()
try
{
settings.Save();

#if !NET462
// On successful save, also save to JSON fallback for .NET version resilience
SaveToJsonFallback(window, settings.Placement);
#endif
}
catch (Exception e)
{
Trace.TraceError($"{this}: The settings could not be saved! {e}");
#if !NET462
// Fallback: save to JSON file when ApplicationSettingsBase.Save() fails
// (e.g. .NET 9 < 9.0.3 bug where ClientConfigurationHost fails on UNC paths)
try
{
SaveToJsonFallback(window, settings.Placement);
Trace.TraceInformation($"{this}: Window placement saved to JSON fallback instead.");
}
catch (Exception fallbackEx)
{
Trace.TraceError($"{this}: The JSON fallback save also failed! {fallbackEx}");
}
#endif
}
}

#if !NET462
private static readonly JsonSerializerOptions JsonOptions = new()
{
IncludeFields = true
};

private void SaveToJsonFallback(Window window, WindowPlacementSetting? placement)
{
if (placement is null)
{
return;
}

var filePath = GetFallbackFilePath(window);
var directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}

var json = JsonSerializer.Serialize(placement, JsonOptions);
File.WriteAllText(filePath, json);
}

private bool TryLoadFromJsonFallback(Window window, out WindowPlacementSetting? placement)
{
placement = null;

try
{
var filePath = GetFallbackFilePath(window);
if (!File.Exists(filePath))
{
return false;
}

var json = File.ReadAllText(filePath);
placement = JsonSerializer.Deserialize<WindowPlacementSetting>(json, JsonOptions);
return placement != null;
}
catch (Exception ex)
{
Trace.TraceWarning($"{this}: Could not load JSON fallback for {window}: {ex.Message}");
return false;
}
}

private static string GetFallbackFilePath(Window window)
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? "MahApps.Metro";
var windowTypeName = window.GetType().FullName?.Replace('.', '_') ?? "MetroWindow";
return Path.Combine(localAppData, appName, "WindowPlacement", $"{windowTypeName}.json");
}
#endif
}
}