增加实时状态保存

This commit is contained in:
ww-rm
2025-10-17 22:41:49 +08:00
parent b178e48e84
commit 02445d36e5
5 changed files with 191 additions and 84 deletions

View File

@@ -8,7 +8,7 @@ using System.Windows.Media;
namespace SpineViewer.Models
{
public class LastStateModel
public class UserStateModel
{
#region

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace SpineViewer.Utils
{
public static class PropertyWatcher
{
public static IDisposable Watch(DependencyObject target, DependencyProperty property, Action callback)
{
var dpd = DependencyPropertyDescriptor.FromProperty(property, target.GetType());
if (dpd == null) return null;
EventHandler handler = (s, e) => callback();
dpd.AddValueChanged(target, handler);
return new Unsubscriber(() => dpd.RemoveValueChanged(target, handler));
}
private class Unsubscriber : IDisposable
{
private readonly Action _dispose;
public Unsubscriber(Action dispose) => _dispose = dispose;
public void Dispose() => _dispose();
}
}
}

View File

@@ -32,7 +32,11 @@ namespace SpineViewer.ViewModels.MainWindow
/// <summary>
/// 指示是否通过托盘图标进行退出
/// </summary>
public bool IsShuttingDownFromTray => _isShuttingDownFromTray;
public bool IsShuttingDownFromTray
{
get => _isShuttingDownFromTray;
private set => SetProperty(ref _isShuttingDownFromTray, value);
}
private bool _isShuttingDownFromTray;
public bool CloseToTray
@@ -109,8 +113,7 @@ namespace SpineViewer.ViewModels.MainWindow
public RelayCommand Cmd_ExitFromTray => _cmd_ExitFromTray ??= new(() =>
{
_isShuttingDownFromTray = true;
OnPropertyChanged(nameof(IsShuttingDownFromTray));
IsShuttingDownFromTray = true;
App.Current.Shutdown();
});
private RelayCommand? _cmd_ExitFromTray;

View File

@@ -9,12 +9,12 @@
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
mc:Ignorable="d"
x:Name="_mainWindow"
d:DataContext="{d:DesignInstance Type={x:Type vm:MainWindowViewModel}}"
Title="{Binding Title}"
Background="{DynamicResource RegionBrush}"
Width="1500"
Height="800"
Background="{DynamicResource RegionBrush}"
WindowStartupLocation="CenterScreen"
d:DataContext="{d:DesignInstance Type={x:Type vm:MainWindowViewModel}}"
PreviewKeyDown="MainWindow_PreviewKeyDown"
LocationChanged="MainWindow_LocationChanged"
SizeChanged="MainWindow_SizeChanged">

View File

@@ -1,6 +1,4 @@
using NLog;
using NLog.Layouts;
using NLog.Targets;
using SFMLRenderer;
using Spine;
using SpineViewer.Models;
@@ -25,6 +23,7 @@ using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
namespace SpineViewer.Views;
@@ -36,7 +35,7 @@ public partial class MainWindow : Window
/// <summary>
/// 上一次状态文件保存路径
/// </summary>
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
public static readonly string UserStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "userstate.json");
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
@@ -46,6 +45,10 @@ public partial class MainWindow : Window
private readonly SFMLRenderWindow _wallpaperRenderWindow;
private readonly MainWindowViewModel _vm;
private readonly List<IDisposable> _userStateWatchers = [];
private DispatcherTimer _saveUserStateTimer;
private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(3);
public MainWindow()
{
InitializeComponent();
@@ -104,79 +107,6 @@ public partial class MainWindow : Window
LogManager.ReconfigExistingLoggers();
}
private void LoadLastState()
{
if (JsonHelper.Deserialize<LastStateModel>(LastStateFilePath, out var m, true))
{
Left = m.WindowLeft;
Top = m.WindowTop;
Width = m.WindowWidth;
Height = m.WindowHeight;
if (m.WindowState == WindowState.Maximized)
{
WindowState = WindowState.Maximized;
}
else
{
WindowState = WindowState.Normal;
}
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width, GridUnitType.Star);
_rootGrid.ColumnDefinitions[2].Width = new(m.RootGridCol2Width, GridUnitType.Star);
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height, GridUnitType.Star);
_modelListGrid.RowDefinitions[2].Height = new(m.ModelListRow2Height, GridUnitType.Star);
_explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height, GridUnitType.Star);
_explorerGrid.RowDefinitions[2].Height = new(m.ExplorerGridRow2Height, GridUnitType.Star);
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height, GridUnitType.Star);
_rightPanelGrid.RowDefinitions[2].Height = new(m.RightPanelGridRow2Height, GridUnitType.Star);
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
_vm.SFMLRendererViewModel.Speed = m.Speed;
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
}
}
private void SaveLastState()
{
var rb = RestoreBounds;
var m = new LastStateModel()
{
WindowLeft = rb.Left,
WindowTop = rb.Top,
WindowWidth = rb.Width,
WindowHeight = rb.Height,
WindowState = WindowState,
RootGridCol0Width = _rootGrid.ColumnDefinitions[0].Width.Value,
RootGridCol2Width = _rootGrid.ColumnDefinitions[2].Width.Value,
ModelListRow0Height = _modelListGrid.RowDefinitions[0].Height.Value,
ModelListRow2Height = _modelListGrid.RowDefinitions[2].Height.Value,
ExplorerGridRow0Height = _explorerGrid.RowDefinitions[0].Height.Value,
ExplorerGridRow2Height = _explorerGrid.RowDefinitions[2].Height.Value,
RightPanelGridRow0Height = _rightPanelGrid.RowDefinitions[0].Height.Value,
RightPanelGridRow2Height = _rightPanelGrid.RowDefinitions[2].Height.Value,
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
};
JsonHelper.Serialize(m, LastStateFilePath);
}
#region MainWindow
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
@@ -206,7 +136,29 @@ public partial class MainWindow : Window
// 加载首选项
_vm.PreferenceViewModel.LoadPreference();
LoadLastState();
// 还原上一次用户历史状态
LoadUserState();
// 添加用户状态监听器
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.LeftProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.TopProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(this, MainWindow.WindowStateProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[0], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rootGrid.ColumnDefinitions[2], ColumnDefinition.WidthProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_modelListGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_explorerGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[0], RowDefinition.HeightProperty, DelayedSaveUserState));
_userStateWatchers.Add(PropertyWatcher.Watch(_rightPanelGrid.RowDefinitions[2], RowDefinition.HeightProperty, DelayedSaveUserState));
_vm.SFMLRendererViewModel.PropertyChanged += SFMLRendererUserStateChanged;
}
private void MainWindow_ContentRendered(object? sender, EventArgs e)
@@ -246,7 +198,14 @@ public partial class MainWindow : Window
}
}
SaveLastState();
// 保存当前用户状态
SaveUserState();
// 撤除所有状态监听器
_vm.SFMLRendererViewModel.PropertyChanged -= SFMLRendererUserStateChanged;
foreach (var w in _userStateWatchers) w.Dispose();
_userStateWatchers.Clear();
_vm.SFMLRendererViewModel.StopRender();
}
@@ -255,6 +214,118 @@ public partial class MainWindow : Window
}
private void LoadUserState()
{
if (JsonHelper.Deserialize<UserStateModel>(UserStateFilePath, out var m, true))
{
Left = m.WindowLeft;
Top = m.WindowTop;
Width = m.WindowWidth;
Height = m.WindowHeight;
if (m.WindowState == WindowState.Maximized)
{
WindowState = WindowState.Maximized;
}
else
{
WindowState = WindowState.Normal;
}
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width, GridUnitType.Star);
_rootGrid.ColumnDefinitions[2].Width = new(m.RootGridCol2Width, GridUnitType.Star);
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height, GridUnitType.Star);
_modelListGrid.RowDefinitions[2].Height = new(m.ModelListRow2Height, GridUnitType.Star);
_explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height, GridUnitType.Star);
_explorerGrid.RowDefinitions[2].Height = new(m.ExplorerGridRow2Height, GridUnitType.Star);
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height, GridUnitType.Star);
_rightPanelGrid.RowDefinitions[2].Height = new(m.RightPanelGridRow2Height, GridUnitType.Star);
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
_vm.SFMLRendererViewModel.Speed = m.Speed;
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
}
}
private void SaveUserState()
{
var rb = RestoreBounds;
var m = new UserStateModel()
{
WindowLeft = rb.Left,
WindowTop = rb.Top,
WindowWidth = rb.Width,
WindowHeight = rb.Height,
WindowState = WindowState,
RootGridCol0Width = _rootGrid.ColumnDefinitions[0].Width.Value,
RootGridCol2Width = _rootGrid.ColumnDefinitions[2].Width.Value,
ModelListRow0Height = _modelListGrid.RowDefinitions[0].Height.Value,
ModelListRow2Height = _modelListGrid.RowDefinitions[2].Height.Value,
ExplorerGridRow0Height = _explorerGrid.RowDefinitions[0].Height.Value,
ExplorerGridRow2Height = _explorerGrid.RowDefinitions[2].Height.Value,
RightPanelGridRow0Height = _rightPanelGrid.RowDefinitions[0].Height.Value,
RightPanelGridRow2Height = _rightPanelGrid.RowDefinitions[2].Height.Value,
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
};
JsonHelper.Serialize(m, UserStateFilePath);
}
/// <summary>
/// <see cref="SaveUserState"/> 的延时版本, 避免一次性大量执行
/// </summary>
private void DelayedSaveUserState()
{
// 第一次调用时创建定时器
if (_saveUserStateTimer == null)
{
_saveUserStateTimer = new() { Interval = _saveTimerDelay };
_saveUserStateTimer.Tick += (s, e) =>
{
_saveUserStateTimer.Stop();
SaveUserState();
};
}
// 每次触发都重置间隔和计时
_saveUserStateTimer.Stop();
_saveUserStateTimer.Start();
}
private void SFMLRendererUserStateChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SFMLRendererViewModel.ResolutionX):
case nameof(SFMLRendererViewModel.ResolutionY):
case nameof(SFMLRendererViewModel.MaxFps):
case nameof(SFMLRendererViewModel.Speed):
case nameof(SFMLRendererViewModel.ShowAxis):
case nameof(SFMLRendererViewModel.BackgroundColor):
case nameof(SFMLRendererViewModel.BackgroundImageMode):
DelayedSaveUserState();
break;
default:
break;
}
}
#endregion
#region ColorPicker
@@ -741,6 +812,8 @@ public partial class MainWindow : Window
_logger.Warn("Warn");
_logger.Error("Error");
_logger.Fatal("Fatal");
var _tabContentHost = (ContentPresenter?)_mainTabControl.Template.FindName("PART_SelectedContentHost", _mainTabControl);
_mainTabControl.Visibility = Visibility.Collapsed;
return;
#endif
}