diff --git a/Ink Canvas/Helpers/ConfigProfileManager.cs b/Ink Canvas/Helpers/ConfigProfileManager.cs
new file mode 100644
index 00000000..00575196
--- /dev/null
+++ b/Ink Canvas/Helpers/ConfigProfileManager.cs
@@ -0,0 +1,161 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 提供多配置文件保存、切换与热重载支持。
+ /// 方案保存在 Configs/Profiles 目录下,当前生效的配置仍为 Configs/Settings.json。
+ ///
+ public static class ConfigProfileManager
+ {
+ private static readonly string ProfilesDir = Path.Combine(App.RootPath, "Configs", "Profiles");
+ private static readonly string SettingsFilePath = Path.Combine(App.RootPath, "Configs", "Settings.json");
+ private const string ProfileExtension = ".json";
+
+ /// 将配置文件名称转为安全文件名(去掉非法字符)。
+ private static string ToSafeFileName(string profileName)
+ {
+ if (string.IsNullOrWhiteSpace(profileName)) return "未命名";
+ var invalid = Path.GetInvalidFileNameChars();
+ var name = string.Join("_", profileName.Trim().Split(invalid, StringSplitOptions.RemoveEmptyEntries));
+ return string.IsNullOrEmpty(name) ? "未命名" : name;
+ }
+
+ /// 确保配置文件目录存在。
+ public static void EnsureProfilesDirectory()
+ {
+ try
+ {
+ if (!Directory.Exists(ProfilesDir))
+ {
+ ProcessProtectionManager.WithWriteAccess(ProfilesDir, () => Directory.CreateDirectory(ProfilesDir));
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"创建配置文件目录失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ /// 获取所有配置文件名称(不含扩展名),按名称排序。
+ public static IReadOnlyList ListProfileNames()
+ {
+ try
+ {
+ EnsureProfilesDirectory();
+ if (!Directory.Exists(ProfilesDir)) return Array.Empty();
+ var files = Directory.GetFiles(ProfilesDir, "*" + ProfileExtension);
+ return files
+ .Select(f => Path.GetFileNameWithoutExtension(f))
+ .Where(n => !string.IsNullOrEmpty(n))
+ .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"列举配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ return Array.Empty();
+ }
+ }
+
+ /// 获取某配置文件对应的文件路径。
+ public static string GetProfilePath(string profileName)
+ {
+ var safe = ToSafeFileName(profileName);
+ return Path.Combine(ProfilesDir, safe + ProfileExtension);
+ }
+
+ /// 将当前配置的 JSON 内容保存为指定名称的配置文件。
+ /// 配置文件显示名称(会转为安全文件名)。
+ /// 已序列化好的 Settings JSON 字符串。
+ /// 成功返回 true。
+ public static bool SaveAsProfile(string profileName, string settingsJson)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(settingsJson))
+ {
+ LogHelper.WriteLogToFile("配置文件保存失败:内容为空", LogHelper.LogType.Warning);
+ return false;
+ }
+ EnsureProfilesDirectory();
+ var path = GetProfilePath(profileName);
+ ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, settingsJson));
+ LogHelper.WriteLogToFile($"配置文件已保存: {ToSafeFileName(profileName)}", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ /// 将指定配置文件应用到当前配置(覆盖 Configs/Settings.json),供主窗口随后热重载。
+ /// 配置文件名称(与 ListProfileNames 中一致,或与保存时使用的显示名一致)。
+ /// 成功返回 true;文件不存在或复制失败返回 false。
+ public static bool ApplyProfile(string profileName)
+ {
+ try
+ {
+ var path = GetProfilePath(profileName);
+ if (!File.Exists(path))
+ {
+ LogHelper.WriteLogToFile($"配置文件文件不存在: {path}", LogHelper.LogType.Warning);
+ return false;
+ }
+ var json = File.ReadAllText(path);
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ LogHelper.WriteLogToFile("配置文件内容为空", LogHelper.LogType.Warning);
+ return false;
+ }
+ // 可选:校验是否为合法 Settings JSON
+ try
+ {
+ JsonConvert.DeserializeObject(json);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"配置文件格式无效: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ var configsDir = Path.GetDirectoryName(SettingsFilePath);
+ if (!string.IsNullOrEmpty(configsDir) && !Directory.Exists(configsDir))
+ {
+ ProcessProtectionManager.WithWriteAccess(configsDir, () => Directory.CreateDirectory(configsDir));
+ }
+ ProcessProtectionManager.WithWriteAccess(SettingsFilePath, () => File.WriteAllText(SettingsFilePath, json));
+ LogHelper.WriteLogToFile($"已应用配置文件: {profileName}(请热重载以生效)", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ /// 删除指定名称的配置文件。
+ public static bool DeleteProfile(string profileName)
+ {
+ try
+ {
+ var path = GetProfilePath(profileName);
+ if (!File.Exists(path)) return true;
+ ProcessProtectionManager.WithWriteAccess(path, () => File.Delete(path));
+ LogHelper.WriteLogToFile($"已删除配置文件: {profileName}", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"删除配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+ }
+}
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index 03a1f3b4..4e87fbed 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -2873,6 +2873,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index 6e20a3da..55f384a7 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -4289,6 +4289,115 @@ namespace Ink_Canvas
}
}
+ private void RefreshConfigProfileList()
+ {
+ try
+ {
+ if (ComboBoxConfigProfile == null) return;
+ var names = ConfigProfileManager.ListProfileNames();
+ var selected = ComboBoxConfigProfile.SelectedItem as string;
+ ComboBoxConfigProfile.ItemsSource = names;
+ if (selected != null && names.Contains(selected))
+ ComboBoxConfigProfile.SelectedItem = selected;
+ else if (names.Count > 0 && ComboBoxConfigProfile.SelectedIndex < 0)
+ ComboBoxConfigProfile.SelectedIndex = 0;
+ if (BtnDeleteConfigProfile != null)
+ BtnDeleteConfigProfile.IsEnabled = ComboBoxConfigProfile.SelectedItem != null;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"刷新配置文件列表失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ComboBoxConfigProfile_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (BtnDeleteConfigProfile != null)
+ BtnDeleteConfigProfile.IsEnabled = ComboBoxConfigProfile?.SelectedItem != null;
+ }
+
+ private void BtnSaveAsConfigProfile_Click(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ var name = TextBoxNewProfileName?.Text?.Trim();
+ if (string.IsNullOrEmpty(name))
+ {
+ MessageBox.Show("请输入配置文件名称后再保存。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+ try
+ {
+ var json = JsonConvert.SerializeObject(Settings, Formatting.Indented);
+ if (ConfigProfileManager.SaveAsProfile(name, json))
+ {
+ RefreshConfigProfileList();
+ if (TextBoxNewProfileName != null) TextBoxNewProfileName.Clear();
+ ShowNotification($"已另存为配置文件:{name}");
+ }
+ else
+ MessageBox.Show("保存配置文件失败,请查看日志。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"另存为配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ MessageBox.Show($"保存配置文件失败: {ex.Message}", "配置文件", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void BtnApplyConfigProfile_Click(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ var name = ComboBoxConfigProfile?.SelectedItem as string;
+ if (string.IsNullOrEmpty(name))
+ {
+ MessageBox.Show("请先选择要应用的配置文件。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+ try
+ {
+ if (ConfigProfileManager.ApplyProfile(name))
+ {
+ ReloadSettingsFromFile();
+ ShowNotification($"已应用配置文件「{name}」并热重载");
+ }
+ else
+ MessageBox.Show("应用配置文件失败,请查看日志。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ MessageBox.Show($"应用配置文件失败: {ex.Message}", "配置文件", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void BtnDeleteConfigProfile_Click(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ var name = ComboBoxConfigProfile?.SelectedItem as string;
+ if (string.IsNullOrEmpty(name))
+ {
+ MessageBox.Show("请先选择要删除的配置文件。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+ try
+ {
+ if (MessageBox.Show($"确定要删除配置文件「{name}」吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
+ return;
+ if (ConfigProfileManager.DeleteProfile(name))
+ {
+ RefreshConfigProfileList();
+ ShowNotification($"已删除配置文件:{name}");
+ }
+ else
+ MessageBox.Show("删除配置文件失败,请查看日志。", "配置文件", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"删除配置文件失败: {ex.Message}", LogHelper.LogType.Error);
+ MessageBox.Show($"删除配置文件失败: {ex.Message}", "配置文件", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
#endregion
#region RandSettings
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index 78b98ec0..7cb2f8ab 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -25,6 +25,15 @@ namespace Ink_Canvas
/// 从配置文件加载用户设置并将其应用到主窗口和相关控件的状态(包括启动、外观、画布、手势、PPT、自动化等各项配置)。
///
/// 指示当前为应用启动阶段;为 true 时按启动流程应用启动相关设置(例如触发启动专用动作和启动时的行为)。
+ ///
+ /// 从当前配置文件重新加载设置并应用到界面(热重载),不触发启动逻辑与自动更新检查。
+ /// 用于配置文件切换后立即生效。
+ ///
+ public void ReloadSettingsFromFile()
+ {
+ LoadSettings(false, skipAutoUpdateCheck: true);
+ }
+
/// 指示是否跳过自动更新检查;为 true 时不会在加载设置后执行自动更新检测。
private void LoadSettings(bool isStartup = false, bool skipAutoUpdateCheck = false)
{
@@ -1250,6 +1259,9 @@ namespace Ink_Canvas
// 加载画笔自动恢复设置
LoadBrushAutoRestoreSettings();
+
+ // 刷新配置文件列表
+ try { RefreshConfigProfileList(); } catch (Exception ex) { LogHelper.WriteLogToFile($"刷新配置文件列表失败: {ex.Message}", LogHelper.LogType.Warning); }
}
///