From 02445d36e55ae7af5492205d9b85e0dafd455491 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Fri, 17 Oct 2025 22:41:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=9E=E6=97=B6=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{LastStateModel.cs => UserStateModel.cs} | 2 +- SpineViewer/Utils/PropertyWatcher.cs | 31 +++ .../MainWindow/MainWindowViewModel.cs | 9 +- SpineViewer/Views/MainWindow.xaml | 4 +- SpineViewer/Views/MainWindow.xaml.cs | 229 ++++++++++++------ 5 files changed, 191 insertions(+), 84 deletions(-) rename SpineViewer/Models/{LastStateModel.cs => UserStateModel.cs} (97%) create mode 100644 SpineViewer/Utils/PropertyWatcher.cs 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 }