diff --git a/SpineViewer/App.xaml.cs b/SpineViewer/App.xaml.cs index c0a342f..bbd70a0 100644 --- a/SpineViewer/App.xaml.cs +++ b/SpineViewer/App.xaml.cs @@ -20,7 +20,8 @@ namespace SpineViewer { public const string ProgId = "SpineViewer.skel"; - public static readonly string ExeFilePath = Environment.ProcessPath; + public static readonly string ProcessPath = Environment.ProcessPath; + public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath); public static readonly string ProcessName = Process.GetCurrentProcess().ProcessName; public static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion; diff --git a/SpineViewer/Models/PreferenceModel.cs b/SpineViewer/Models/PreferenceModel.cs index afe9d3f..da94a82 100644 --- a/SpineViewer/Models/PreferenceModel.cs +++ b/SpineViewer/Models/PreferenceModel.cs @@ -76,6 +76,9 @@ namespace SpineViewer.Models [ObservableProperty] private bool _renderSelectedOnly; + [ObservableProperty] + private bool _associateFileSuffix; + [ObservableProperty] private AppLanguage _appLanguage; diff --git a/SpineViewer/Resources/Images/skel.ico b/SpineViewer/Resources/Images/skel.ico new file mode 100644 index 0000000..1bc0a1d Binary files /dev/null and b/SpineViewer/Resources/Images/skel.ico differ diff --git a/SpineViewer/Resources/Images/skel.png b/SpineViewer/Resources/Images/skel.png new file mode 100644 index 0000000..c8b006a Binary files /dev/null and b/SpineViewer/Resources/Images/skel.png differ diff --git a/SpineViewer/appicon.ico b/SpineViewer/Resources/Images/spineviewer.ico similarity index 57% rename from SpineViewer/appicon.ico rename to SpineViewer/Resources/Images/spineviewer.ico index f269c18..a74705d 100644 Binary files a/SpineViewer/appicon.ico and b/SpineViewer/Resources/Images/spineviewer.ico differ diff --git a/SpineViewer/Resources/Images/spineviewer.png b/SpineViewer/Resources/Images/spineviewer.png new file mode 100644 index 0000000..c1baefc Binary files /dev/null and b/SpineViewer/Resources/Images/spineviewer.png differ diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index 085b860..130bea1 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -233,6 +233,7 @@ Preview Options Application Options + Associate File Extension Language diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index c9b71bc..ff01c69 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -233,6 +233,7 @@ プレビュー画面オプション アプリケーションプション + ファイル拡張子を関連付ける 言語 diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index 7f04789..c254bd8 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -233,6 +233,7 @@ 预览画面选项 应用程序选项 + 关联文件后缀 语言 \ No newline at end of file diff --git a/SpineViewer/SpineViewer.csproj b/SpineViewer/SpineViewer.csproj index 4e75716..d638485 100644 --- a/SpineViewer/SpineViewer.csproj +++ b/SpineViewer/SpineViewer.csproj @@ -14,12 +14,15 @@ $(NoWarn);NETSDK1206 - appicon.ico + Resources\Images\spineviewer.ico app.manifest - + - + + PreserveNewest + + diff --git a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs index 19ce571..950c304 100644 --- a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs @@ -1,13 +1,16 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Microsoft.Win32; using NLog; using Spine.SpineWrappers; using SpineViewer.Models; +using SpineViewer.Natives; using SpineViewer.Services; using SpineViewer.Utils; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -23,6 +26,10 @@ namespace SpineViewer.ViewModels.MainWindow /// public static readonly string PreferenceFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "preference.json"); + private static readonly string SkelFileDescription = "SpineViewer File"; + private static readonly string SkelIconFilePath = Path.Combine(App.ProcessDirectory, "Resources\\Images\\skel.ico"); + private static readonly string ShellOpenCommand = $"\"{App.ProcessPath}\" \"%1\""; + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly MainWindowViewModel _vmMain; @@ -63,8 +70,19 @@ namespace SpineViewer.ViewModels.MainWindow /// public void LoadPreference() { - if (JsonHelper.Deserialize(PreferenceFilePath, out var obj, true)) - Preference = obj; + if (JsonHelper.Deserialize(PreferenceFilePath, out var obj, true)) + { + try + { + Preference = obj; + } + catch (Exception ex) + { + + _logger.Trace(ex.ToString()); + _logger.Error("Failed to load some prefereneces, {0}", ex.Message); + } + } } /// @@ -94,6 +112,7 @@ namespace SpineViewer.ViewModels.MainWindow DebugClippings = DebugClippings, RenderSelectedOnly = RenderSelectedOnly, + AssociateFileSuffix = AssociateFileSuffix, AppLanguage = AppLanguage, }; } @@ -118,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow DebugClippings = value.DebugClippings; RenderSelectedOnly = value.RenderSelectedOnly; + AssociateFileSuffix = value.AssociateFileSuffix; AppLanguage = value.AppLanguage; } } @@ -230,6 +250,71 @@ namespace SpineViewer.ViewModels.MainWindow set => SetProperty(_vmMain.SFMLRendererViewModel.RenderSelectedOnly, value, v => _vmMain.SFMLRendererViewModel.RenderSelectedOnly = v); } + public bool AssociateFileSuffix + { + get + { + try + { + // 检查 .skel 的 ProgID + using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Classes\.skel")) + { + var progIdValue = key?.GetValue("") as string; + if (!string.Equals(progIdValue, App.ProgId, StringComparison.OrdinalIgnoreCase)) + return false; + } + + // 检查 command 指令是否相同 + using (var key = Registry.CurrentUser.OpenSubKey($@"Software\Classes\{App.ProgId}\shell\open\command")) + { + var command = key?.GetValue("") as string; + if (string.IsNullOrWhiteSpace(command)) + return false; + return command == ShellOpenCommand; + } + } + catch + { + return false; + } + } + set + { + SetProperty(AssociateFileSuffix, value, v => + { + if (v) + { + // 文件关联 + using (var key = Registry.CurrentUser.CreateSubKey(@"Software\Classes\.skel")) + { + key?.SetValue("", App.ProgId); + } + + using (var key = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{App.ProgId}")) + { + key?.SetValue("", SkelFileDescription); + using (var iconKey = key?.CreateSubKey("DefaultIcon")) + { + iconKey?.SetValue("", $"\"{SkelIconFilePath}\""); + } + using (var shellKey = key?.CreateSubKey(@"shell\open\command")) + { + shellKey?.SetValue("", ShellOpenCommand); + } + } + } + else + { + // 删除关联 + Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\.skel", false); + Registry.CurrentUser.DeleteSubKeyTree($@"Software\Classes\{App.ProgId}", false); + } + + Shell32.SHChangeNotify(Shell32.SHCNE_ASSOCCHANGED, Shell32.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero); + }); + } + } + public AppLanguage AppLanguage { get => ((App)App.Current).Language; diff --git a/SpineViewer/Views/PreferenceDialog.xaml b/SpineViewer/Views/PreferenceDialog.xaml index b3c7dfb..37723e4 100644 --- a/SpineViewer/Views/PreferenceDialog.xaml +++ b/SpineViewer/Views/PreferenceDialog.xaml @@ -143,13 +143,17 @@ +