增加实时状态保存
This commit is contained in:
@@ -8,7 +8,7 @@ using System.Windows.Media;
|
|||||||
|
|
||||||
namespace SpineViewer.Models
|
namespace SpineViewer.Models
|
||||||
{
|
{
|
||||||
public class LastStateModel
|
public class UserStateModel
|
||||||
{
|
{
|
||||||
#region 画面布局状态
|
#region 画面布局状态
|
||||||
|
|
||||||
31
SpineViewer/Utils/PropertyWatcher.cs
Normal file
31
SpineViewer/Utils/PropertyWatcher.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 指示是否通过托盘图标进行退出
|
/// 指示是否通过托盘图标进行退出
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsShuttingDownFromTray => _isShuttingDownFromTray;
|
public bool IsShuttingDownFromTray
|
||||||
|
{
|
||||||
|
get => _isShuttingDownFromTray;
|
||||||
|
private set => SetProperty(ref _isShuttingDownFromTray, value);
|
||||||
|
}
|
||||||
private bool _isShuttingDownFromTray;
|
private bool _isShuttingDownFromTray;
|
||||||
|
|
||||||
public bool CloseToTray
|
public bool CloseToTray
|
||||||
@@ -109,8 +113,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
|||||||
|
|
||||||
public RelayCommand Cmd_ExitFromTray => _cmd_ExitFromTray ??= new(() =>
|
public RelayCommand Cmd_ExitFromTray => _cmd_ExitFromTray ??= new(() =>
|
||||||
{
|
{
|
||||||
_isShuttingDownFromTray = true;
|
IsShuttingDownFromTray = true;
|
||||||
OnPropertyChanged(nameof(IsShuttingDownFromTray));
|
|
||||||
App.Current.Shutdown();
|
App.Current.Shutdown();
|
||||||
});
|
});
|
||||||
private RelayCommand? _cmd_ExitFromTray;
|
private RelayCommand? _cmd_ExitFromTray;
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
|
xmlns:SFMLRenderer="clr-namespace:SFMLRenderer;assembly=SFMLRenderer"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Name="_mainWindow"
|
x:Name="_mainWindow"
|
||||||
|
d:DataContext="{d:DesignInstance Type={x:Type vm:MainWindowViewModel}}"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Background="{DynamicResource RegionBrush}"
|
|
||||||
Width="1500"
|
Width="1500"
|
||||||
Height="800"
|
Height="800"
|
||||||
|
Background="{DynamicResource RegionBrush}"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
d:DataContext="{d:DesignInstance Type={x:Type vm:MainWindowViewModel}}"
|
|
||||||
PreviewKeyDown="MainWindow_PreviewKeyDown"
|
PreviewKeyDown="MainWindow_PreviewKeyDown"
|
||||||
LocationChanged="MainWindow_LocationChanged"
|
LocationChanged="MainWindow_LocationChanged"
|
||||||
SizeChanged="MainWindow_SizeChanged">
|
SizeChanged="MainWindow_SizeChanged">
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Layouts;
|
|
||||||
using NLog.Targets;
|
|
||||||
using SFMLRenderer;
|
using SFMLRenderer;
|
||||||
using Spine;
|
using Spine;
|
||||||
using SpineViewer.Models;
|
using SpineViewer.Models;
|
||||||
@@ -25,6 +23,7 @@ using System.Windows.Documents;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace SpineViewer.Views;
|
namespace SpineViewer.Views;
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ public partial class MainWindow : Window
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 上一次状态文件保存路径
|
/// 上一次状态文件保存路径
|
||||||
/// </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();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
@@ -46,6 +45,10 @@ public partial class MainWindow : Window
|
|||||||
private readonly SFMLRenderWindow _wallpaperRenderWindow;
|
private readonly SFMLRenderWindow _wallpaperRenderWindow;
|
||||||
private readonly MainWindowViewModel _vm;
|
private readonly MainWindowViewModel _vm;
|
||||||
|
|
||||||
|
private readonly List<IDisposable> _userStateWatchers = [];
|
||||||
|
private DispatcherTimer _saveUserStateTimer;
|
||||||
|
private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -104,79 +107,6 @@ public partial class MainWindow : Window
|
|||||||
LogManager.ReconfigExistingLoggers();
|
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 事件处理
|
#region MainWindow 事件处理
|
||||||
|
|
||||||
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
|
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
|
||||||
@@ -206,7 +136,29 @@ public partial class MainWindow : Window
|
|||||||
// 加载首选项
|
// 加载首选项
|
||||||
_vm.PreferenceViewModel.LoadPreference();
|
_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)
|
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();
|
_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
|
#endregion
|
||||||
|
|
||||||
#region ColorPicker 弹窗事件处理
|
#region ColorPicker 弹窗事件处理
|
||||||
@@ -741,6 +812,8 @@ public partial class MainWindow : Window
|
|||||||
_logger.Warn("Warn");
|
_logger.Warn("Warn");
|
||||||
_logger.Error("Error");
|
_logger.Error("Error");
|
||||||
_logger.Fatal("Fatal");
|
_logger.Fatal("Fatal");
|
||||||
|
var _tabContentHost = (ContentPresenter?)_mainTabControl.Template.FindName("PART_SelectedContentHost", _mainTabControl);
|
||||||
|
_mainTabControl.Visibility = Visibility.Collapsed;
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user