diff --git a/SpineViewer/Models/PreferenceModel.cs b/SpineViewer/Models/PreferenceModel.cs index 4439b11..c937a65 100644 --- a/SpineViewer/Models/PreferenceModel.cs +++ b/SpineViewer/Models/PreferenceModel.cs @@ -92,6 +92,9 @@ namespace SpineViewer.Models [ObservableProperty] private bool _wallpaperView; + [ObservableProperty] + private bool? _closeToTray = null; + [ObservableProperty] private bool _autoRun; diff --git a/SpineViewer/Resources/AppResource.cs b/SpineViewer/Resources/AppResource.cs index 8768b8d..aa6926b 100644 --- a/SpineViewer/Resources/AppResource.cs +++ b/SpineViewer/Resources/AppResource.cs @@ -33,6 +33,7 @@ namespace SpineViewer.Resources public static string Str_TooManyItemsToAddQuest => Get("Str_TooManyItemsToAddQuest"); public static string Str_RemoveItemsQuest => Get("Str_RemoveItemsQuest"); public static string Str_DeleteItemsQuest => Get("Str_DeleteItemsQuest"); + public static string Str_CloseToTrayQuest => Get("Str_CloseToTrayQuest"); public static string Str_FrameExporterTitle => Get("Str_FrameExporterTitle"); public static string Str_FrameSequenceExporterTitle => Get("Str_FrameSequenceExporterTitle"); diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index 2099061..a63f3a4 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -17,11 +17,6 @@ Preferences... Exit - - Auto Start - Auto-load Workspace File on Startup - Specifies the workspace configuration file to be automatically loaded when the program starts with Windows startup. This takes effect only if auto-startup is enabled. - Explorer Model @@ -154,6 +149,7 @@ {0} items total, add all at once? Remove {0} items? Delete {0} items? + You clicked the close button. Do you want to minimize to the tray icon instead of closing the application directly? (If you choose Yes, the window will be minimized to the tray and can be restored by double-clicking the icon. You can change this option later in the application preferences.) Export Single Frame @@ -242,7 +238,11 @@ Preview Options Application Options - Associate File Extension Language + Minimize to tray when closing + Auto Start + Auto-load Workspace File on Startup + Specifies the workspace configuration file to be automatically loaded when the program starts with Windows startup. This takes effect only if auto-startup is enabled. + Associate File Extension diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index 3612f9d..168cc59 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -17,11 +17,6 @@ 設定... 終了 - - 自動起動 - 起動時にワークスペースファイルを自動読み込み - プログラムが Windows 起動と同時に自動起動した場合に、自動的に読み込むワークスペース設定ファイルを指定します。自動起動が有効な場合にのみ適用されます。 - エクスプローラー モデル @@ -154,6 +149,7 @@ 全{0}件、一度に追加しますか? {0}件を削除してもよろしいですか? {0}件を削除してもよろしいですか? + 閉じるボタンをクリックしました。アプリケーションを直接終了するのではなく、通知領域のアイコンに最小化しますか? (「はい」を選択すると、ウィンドウは通知領域に最小化され、アイコンをダブルクリックすると復元できます。この設定は後でアプリケーションの環境設定から変更できます。) 単一フレームをエクスポート @@ -242,8 +238,12 @@ プレビュー画面オプション アプリケーションプション - ファイル拡張子を関連付ける 言語 + 閉じるときにトレイに最小化する + 自動起動 + 起動時にワークスペースファイルを自動読み込み + プログラムが Windows 起動と同時に自動起動した場合に、自動的に読み込むワークスペース設定ファイルを指定します。自動起動が有効な場合にのみ適用されます。 + ファイル拡張子を関連付ける diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index d4df843..5884c6e 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -16,11 +16,6 @@ 首选项 首选项... 退出 - - - 开机自启 - 自启动加载工作区文件 - 设置程序开机自启后自动加载的工作区配置文件,仅在启用开机自启时生效 浏览 @@ -154,6 +149,7 @@ 共 {0} 项,是否一次性添加? 确定移除 {0} 项? 确定删除 {0} 项? + 您点击了关闭按钮,是否需要最小化至托盘图标而不是直接关闭? (选是则最小化至托盘图标,可以通过双击图标还原窗口,以后也可以在程序首选项中重新设置该选项) 导出单帧 @@ -242,7 +238,11 @@ 预览画面选项 应用程序选项 - 关联文件后缀 语言 + 关闭时最小化至托盘图标 + 开机自启 + 自启动加载工作区文件 + 设置程序开机自启后自动加载的工作区配置文件,仅在启用开机自启时生效 + 关联文件后缀 \ No newline at end of file diff --git a/SpineViewer/Services/MessagePopupService.cs b/SpineViewer/Services/MessagePopupService.cs index 598a62d..d9348ee 100644 --- a/SpineViewer/Services/MessagePopupService.cs +++ b/SpineViewer/Services/MessagePopupService.cs @@ -28,10 +28,16 @@ namespace SpineViewer.Services MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Error); } - public static bool Quest(string text, string? title = null) + public static bool OKCancel(string text, string? title = null) { title ??= AppResource.Str_QuestPopup; return MessageBox.Show(text, title, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK; } + + public static bool YesNo(string text, string? title = null) + { + title ??= AppResource.Str_QuestPopup; + return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes; + } } } diff --git a/SpineViewer/ViewModels/MainWindow/ExplorerListViewModel.cs b/SpineViewer/ViewModels/MainWindow/ExplorerListViewModel.cs index 1f836ba..0f85b90 100644 --- a/SpineViewer/ViewModels/MainWindow/ExplorerListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/ExplorerListViewModel.cs @@ -249,7 +249,7 @@ namespace SpineViewer.ViewModels.MainWindow private void DeletePreview_Execute(IList? args) { if (args is null || args.Count <= 0) return; - if (!MessagePopupService.Quest(string.Format(AppResource.Str_DeleteItemsQuest, args.Count))) return; + if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_DeleteItemsQuest, args.Count))) return; if (args.Count <= 10) { diff --git a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs index 45b2486..b7402e0 100644 --- a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs @@ -29,6 +29,13 @@ namespace SpineViewer.ViewModels.MainWindow public string Title => $"SpineViewer - v{App.Version}"; + public bool? CloseToTray + { + get => _closeToTray; + set => SetProperty(ref _closeToTray, value); + } + private bool? _closeToTray = null; + public string AutoRunWorkspaceConfigPath { get => _autoRunWorkspaceConfigPath; @@ -57,6 +64,9 @@ namespace SpineViewer.ViewModels.MainWindow public ObservableCollectionWithLock SpineObjects => _spineObjectModels; private readonly ObservableCollectionWithLock _spineObjectModels = []; + /// + /// 首选项 ViewModel + /// public PreferenceViewModel PreferenceViewModel => _preferenceViewModel; private readonly PreferenceViewModel _preferenceViewModel; diff --git a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs index 949cfcd..11c79c0 100644 --- a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs @@ -110,6 +110,7 @@ namespace SpineViewer.ViewModels.MainWindow AppLanguage = AppLanguage, RenderSelectedOnly = RenderSelectedOnly, WallpaperView = WallpaperView, + CloseToTray = CloseToTray, AutoRun = AutoRun, AutoRunWorkspaceConfigPath = AutoRunWorkspaceConfigPath, AssociateFileSuffix = AssociateFileSuffix, @@ -138,6 +139,7 @@ namespace SpineViewer.ViewModels.MainWindow AppLanguage = value.AppLanguage; RenderSelectedOnly = value.RenderSelectedOnly; WallpaperView = value.WallpaperView; + CloseToTray = value.CloseToTray; AutoRun = value.AutoRun; AutoRunWorkspaceConfigPath = value.AutoRunWorkspaceConfigPath; AssociateFileSuffix = value.AssociateFileSuffix; @@ -264,6 +266,12 @@ namespace SpineViewer.ViewModels.MainWindow set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperView, value, v => _vmMain.SFMLRendererViewModel.WallpaperView = v); } + public bool? CloseToTray + { + get => _vmMain.CloseToTray; + set => SetProperty(_vmMain.CloseToTray, value, v => _vmMain.CloseToTray = v); + } + public bool AutoRun { get => ((App)App.Current).AutoRun; diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs index a1b93a9..8aec5dc 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs @@ -127,7 +127,7 @@ namespace SpineViewer.ViewModels.MainWindow if (args.Count > 1) { - if (!MessagePopupService.Quest(string.Format(AppResource.Str_RemoveItemsQuest, args.Count))) + if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_RemoveItemsQuest, args.Count))) return; } @@ -159,7 +159,7 @@ namespace SpineViewer.ViewModels.MainWindow { if (!RemoveAllSpineObject_CanExecute(args)) return; - if (!MessagePopupService.Quest(string.Format(AppResource.Str_RemoveItemsQuest, args.Count))) + if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_RemoveItemsQuest, args.Count))) return; lock (_spineObjectModels.Lock) @@ -469,7 +469,7 @@ namespace SpineViewer.ViewModels.MainWindow { if (validPaths.Count > 100) { - if (!MessagePopupService.Quest(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count))) + if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count))) return; } ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask( diff --git a/SpineViewer/ViewModels/ProgressDialogViewModel.cs b/SpineViewer/ViewModels/ProgressDialogViewModel.cs index a677704..7bc3f9f 100644 --- a/SpineViewer/ViewModels/ProgressDialogViewModel.cs +++ b/SpineViewer/ViewModels/ProgressDialogViewModel.cs @@ -64,7 +64,7 @@ namespace SpineViewer.ViewModels private void Cancel_Execute() { if (!Cancel_CanExecute()) return; - if (!MessagePopupService.Quest(AppResource.Str_CancelQuest)) return; + if (!MessagePopupService.OKCancel(AppResource.Str_CancelQuest)) return; _cts.Cancel(); Cmd_Cancel.NotifyCanExecuteChanged(); } diff --git a/SpineViewer/Views/MainWindow.xaml.cs b/SpineViewer/Views/MainWindow.xaml.cs index f0077a4..df637c5 100644 --- a/SpineViewer/Views/MainWindow.xaml.cs +++ b/SpineViewer/Views/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using Spine; using SpineViewer.Models; using SpineViewer.Natives; using SpineViewer.Resources; +using SpineViewer.Services; using SpineViewer.Utils; using SpineViewer.ViewModels.MainWindow; using System.Collections.Specialized; @@ -65,10 +66,12 @@ public partial class MainWindow : Window Loaded += MainWindow_Loaded; ContentRendered += MainWindow_ContentRendered; + Closing += MainWindow_Closing; Closed += MainWindow_Closed; _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; + _vm.SFMLRendererViewModel.PropertyChanged += SFMLRendererViewModel_PropertyChanged; } @@ -221,6 +224,20 @@ public partial class MainWindow : Window _vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths); } + private void MainWindow_Closing(object? sender, CancelEventArgs e) + { + if (_vm.CloseToTray is null) + { + _vm.PreferenceViewModel.CloseToTray = MessagePopupService.YesNo(AppResource.Str_CloseToTrayQuest); + _vm.PreferenceViewModel.SavePreference(); + } + if (_vm.CloseToTray is true) + { + Hide(); + e.Cancel = true; + } + } + private void MainWindow_Closed(object? sender, EventArgs e) { SaveLastState(); @@ -231,13 +248,14 @@ public partial class MainWindow : Window #endregion - #region SFMLRendererViewModel 事件处理 + #region ViewModel PropertyChanged 事件处理 private void SFMLRendererViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(PreferenceViewModel.WallpaperView)) + if (e.PropertyName == nameof(SFMLRendererViewModel.WallpaperView)) { - if (_vm.PreferenceViewModel.WallpaperView) + var wnd = _wallpaperRenderWindow; + if (_vm.SFMLRendererViewModel.WallpaperView) { var workerw = User32.GetWorkerW(); if (workerw == IntPtr.Zero) @@ -245,7 +263,6 @@ public partial class MainWindow : Window _logger.Error("Failed to enable wallpaper view, WorkerW not found"); return; } - var wnd = _wallpaperRenderWindow; var handle = wnd.SystemHandle; User32.GetPrimaryScreenResolution(out var sw, out var sh); @@ -261,7 +278,7 @@ public partial class MainWindow : Window } else { - _wallpaperRenderWindow.SetVisible(false); + wnd.SetVisible(false); } } } @@ -416,7 +433,12 @@ public partial class MainWindow : Window private void _notifyIcon_MouseDoubleClick(object sender, RoutedEventArgs e) { - + Show(); + if (WindowState == WindowState.Minimized) + { + WindowState = WindowState.Normal; + } + Activate(); } #endregion diff --git a/SpineViewer/Views/PreferenceDialog.xaml b/SpineViewer/Views/PreferenceDialog.xaml index 0717f1b..8c4c8e2 100644 --- a/SpineViewer/Views/PreferenceDialog.xaml +++ b/SpineViewer/Views/PreferenceDialog.xaml @@ -229,6 +229,16 @@ IsChecked="{Binding WallpaperView}"/> + + + + + + +