diff --git a/SpineViewer/Models/LastStateModel.cs b/SpineViewer/Models/UserStateModel.cs
similarity index 97%
rename from SpineViewer/Models/LastStateModel.cs
rename to SpineViewer/Models/UserStateModel.cs
index 17eb9d3..f353a62 100644
--- a/SpineViewer/Models/LastStateModel.cs
+++ b/SpineViewer/Models/UserStateModel.cs
@@ -8,7 +8,7 @@ using System.Windows.Media;
namespace SpineViewer.Models
{
- public class LastStateModel
+ public class UserStateModel
{
#region 画面布局状态
diff --git a/SpineViewer/Utils/PropertyWatcher.cs b/SpineViewer/Utils/PropertyWatcher.cs
new file mode 100644
index 0000000..6cbd471
--- /dev/null
+++ b/SpineViewer/Utils/PropertyWatcher.cs
@@ -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();
+ }
+ }
+}
diff --git a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs
index bdf235c..c86ee78 100644
--- a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs
+++ b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs
@@ -32,7 +32,11 @@ namespace SpineViewer.ViewModels.MainWindow
///
/// 指示是否通过托盘图标进行退出
///
- 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;
diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml
index 1a16917..b6c8c6e 100644
--- a/SpineViewer/Views/MainWindow.xaml
+++ b/SpineViewer/Views/MainWindow.xaml
@@ -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">
diff --git a/SpineViewer/Views/MainWindow.xaml.cs b/SpineViewer/Views/MainWindow.xaml.cs
index 3edb6f5..26adac4 100644
--- a/SpineViewer/Views/MainWindow.xaml.cs
+++ b/SpineViewer/Views/MainWindow.xaml.cs
@@ -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
///
/// 上一次状态文件保存路径
///
- 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 _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(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(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);
+ }
+
+ ///
+ /// 的延时版本, 避免一次性大量执行
+ ///
+ 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
}