增加最小化至托盘图标功能

This commit is contained in:
ww-rm
2025-09-30 01:53:14 +08:00
parent 8c3be98b54
commit bb2862ed4f
13 changed files with 90 additions and 30 deletions

View File

@@ -92,6 +92,9 @@ namespace SpineViewer.Models
[ObservableProperty]
private bool _wallpaperView;
[ObservableProperty]
private bool? _closeToTray = null;
[ObservableProperty]
private bool _autoRun;

View File

@@ -33,6 +33,7 @@ namespace SpineViewer.Resources
public static string Str_TooManyItemsToAddQuest => Get<string>("Str_TooManyItemsToAddQuest");
public static string Str_RemoveItemsQuest => Get<string>("Str_RemoveItemsQuest");
public static string Str_DeleteItemsQuest => Get<string>("Str_DeleteItemsQuest");
public static string Str_CloseToTrayQuest => Get<string>("Str_CloseToTrayQuest");
public static string Str_FrameExporterTitle => Get<string>("Str_FrameExporterTitle");
public static string Str_FrameSequenceExporterTitle => Get<string>("Str_FrameSequenceExporterTitle");

View File

@@ -17,11 +17,6 @@
<s:String x:Key="Str_PreferenceWithDots">Preferences...</s:String>
<s:String x:Key="Str_Exit">Exit</s:String>
<!-- 首选项 -->
<s:String x:Key="Str_AutoRun_Short">Auto Start</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">Auto-load Workspace File on Startup</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">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.</s:String>
<!-- 标签页 -->
<s:String x:Key="Str_Explorer">Explorer</s:String>
<s:String x:Key="Str_SpineObject">Model</s:String>
@@ -154,6 +149,7 @@
<s:String x:Key="Str_TooManyItemsToAddQuest">{0} items total, add all at once?</s:String>
<s:String x:Key="Str_RemoveItemsQuest">Remove {0} items?</s:String>
<s:String x:Key="Str_DeleteItemsQuest">Delete {0} items?</s:String>
<s:String x:Key="Str_CloseToTrayQuest" xml:space="preserve">You clicked the close button. Do you want to minimize to the tray icon instead of closing the application directly?&#x0A;(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.)</s:String>
<!-- 导出对话框弹窗文本 -->
<s:String x:Key="Str_FrameExporterTitle">Export Single Frame</s:String>
@@ -242,7 +238,11 @@
<s:String x:Key="Str_RendererPreference">Preview Options</s:String>
<s:String x:Key="Str_AppPreference">Application Options</s:String>
<s:String x:Key="Str_AssociateFileSuffix">Associate File Extension</s:String>
<s:String x:Key="Str_Language">Language</s:String>
<s:String x:Key="Str_CloseToTray">Minimize to tray when closing</s:String>
<s:String x:Key="Str_AutoRun">Auto Start</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">Auto-load Workspace File on Startup</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">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.</s:String>
<s:String x:Key="Str_AssociateFileSuffix">Associate File Extension</s:String>
</ResourceDictionary>

View File

@@ -17,11 +17,6 @@
<s:String x:Key="Str_PreferenceWithDots">設定...</s:String>
<s:String x:Key="Str_Exit">終了</s:String>
<!-- 首选项 -->
<s:String x:Key="Str_AutoRun_Short" xml:lang="ja">自動起動</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">起動時にワークスペースファイルを自動読み込み</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">プログラムが Windows 起動と同時に自動起動した場合に、自動的に読み込むワークスペース設定ファイルを指定します。自動起動が有効な場合にのみ適用されます。</s:String>
<!-- 标签页 -->
<s:String x:Key="Str_Explorer">エクスプローラー</s:String>
<s:String x:Key="Str_SpineObject">モデル</s:String>
@@ -154,6 +149,7 @@
<s:String x:Key="Str_TooManyItemsToAddQuest">全{0}件、一度に追加しますか?</s:String>
<s:String x:Key="Str_RemoveItemsQuest">{0}件を削除してもよろしいですか?</s:String>
<s:String x:Key="Str_DeleteItemsQuest">{0}件を削除してもよろしいですか?</s:String>
<s:String x:Key="Str_CloseToTrayQuest" xml:space="preserve">閉じるボタンをクリックしました。アプリケーションを直接終了するのではなく、通知領域のアイコンに最小化しますか?&#x0A;(「はい」を選択すると、ウィンドウは通知領域に最小化され、アイコンをダブルクリックすると復元できます。この設定は後でアプリケーションの環境設定から変更できます。)</s:String>
<!-- 导出对话框弹窗文本 -->
<s:String x:Key="Str_FrameExporterTitle">単一フレームをエクスポート</s:String>
@@ -242,8 +238,12 @@
<s:String x:Key="Str_RendererPreference">プレビュー画面オプション</s:String>
<s:String x:Key="Str_AppPreference">アプリケーションプション</s:String>
<s:String x:Key="Str_AssociateFileSuffix">ファイル拡張子を関連付ける</s:String>
<s:String x:Key="Str_Language">言語</s:String>
<s:String x:Key="Str_CloseToTray">閉じるときにトレイに最小化する</s:String>
<s:String x:Key="Str_AutoRun">自動起動</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">起動時にワークスペースファイルを自動読み込み</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">プログラムが Windows 起動と同時に自動起動した場合に、自動的に読み込むワークスペース設定ファイルを指定します。自動起動が有効な場合にのみ適用されます。</s:String>
<s:String x:Key="Str_AssociateFileSuffix">ファイル拡張子を関連付ける</s:String>
</ResourceDictionary>

View File

@@ -16,11 +16,6 @@
<s:String x:Key="Str_Preference">首选项</s:String>
<s:String x:Key="Str_PreferenceWithDots">首选项...</s:String>
<s:String x:Key="Str_Exit">退出</s:String>
<!-- 首选项 -->
<s:String x:Key="Str_AutoRun">开机自启</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">自启动加载工作区文件</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">设置程序开机自启后自动加载的工作区配置文件,仅在启用开机自启时生效</s:String>
<!-- 标签页 -->
<s:String x:Key="Str_Explorer">浏览</s:String>
@@ -154,6 +149,7 @@
<s:String x:Key="Str_TooManyItemsToAddQuest">共 {0} 项,是否一次性添加?</s:String>
<s:String x:Key="Str_RemoveItemsQuest">确定移除 {0} 项?</s:String>
<s:String x:Key="Str_DeleteItemsQuest">确定删除 {0} 项?</s:String>
<s:String x:Key="Str_CloseToTrayQuest" xml:space="preserve">您点击了关闭按钮,是否需要最小化至托盘图标而不是直接关闭?&#x0A;(选是则最小化至托盘图标,可以通过双击图标还原窗口,以后也可以在程序首选项中重新设置该选项)</s:String>
<!-- 导出对话框弹窗文本 -->
<s:String x:Key="Str_FrameExporterTitle">导出单帧</s:String>
@@ -242,7 +238,11 @@
<s:String x:Key="Str_RendererPreference">预览画面选项</s:String>
<s:String x:Key="Str_AppPreference">应用程序选项</s:String>
<s:String x:Key="Str_AssociateFileSuffix">关联文件后缀</s:String>
<s:String x:Key="Str_Language">语言</s:String>
<s:String x:Key="Str_CloseToTray">关闭时最小化至托盘图标</s:String>
<s:String x:Key="Str_AutoRun">开机自启</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPath">自启动加载工作区文件</s:String>
<s:String x:Key="Str_AutoRunWorkspaceConfigPathTooltip">设置程序开机自启后自动加载的工作区配置文件,仅在启用开机自启时生效</s:String>
<s:String x:Key="Str_AssociateFileSuffix">关联文件后缀</s:String>
</ResourceDictionary>

View File

@@ -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;
}
}
}

View File

@@ -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)
{

View File

@@ -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<SpineObjectModel> SpineObjects => _spineObjectModels;
private readonly ObservableCollectionWithLock<SpineObjectModel> _spineObjectModels = [];
/// <summary>
/// 首选项 ViewModel
/// </summary>
public PreferenceViewModel PreferenceViewModel => _preferenceViewModel;
private readonly PreferenceViewModel _preferenceViewModel;

View File

@@ -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;

View File

@@ -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(

View File

@@ -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();
}

View File

@@ -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

View File

@@ -229,6 +229,16 @@
IsChecked="{Binding WallpaperView}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_CloseToTray}"/>
<ToggleButton Grid.Column="1"
IsChecked="{Binding CloseToTray}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>