From 12c7fb1713f181763cac7039b00b0168bf7bb9c2 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Sun, 19 Apr 2026 08:35:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=E8=BF=81=E7=A7=BB=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml.cs | 290 +---------- Ink Canvas/MainWindow_cs/MW_AutoStart.cs | 75 +-- .../MainWindow_cs/MW_FloatingBarIcons.cs | 15 +- Ink Canvas/MainWindow_cs/MW_Settings.cs | 18 +- .../SettingsViews/Helpers/AutoStartHelper.cs | 48 ++ .../{ => Helpers}/MainWindowSettingsHelper.cs | 138 +----- .../SettingsViews/Helpers/SettingsManager.cs | 31 ++ .../TopMostModeTemplateSelector.cs | 2 +- .../Helpers/WindowSettingsHelper.cs | 463 ++++++++++++++++++ .../SettingsViews/Pages/StartupPage.xaml | 13 +- .../SettingsViews/Pages/StartupPage.xaml.cs | 122 ++--- 11 files changed, 651 insertions(+), 564 deletions(-) create mode 100644 Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs rename Ink Canvas/Windows/SettingsViews/{ => Helpers}/MainWindowSettingsHelper.cs (71%) create mode 100644 Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs rename Ink Canvas/Windows/SettingsViews/{Pages => Helpers}/TopMostModeTemplateSelector.cs (93%) create mode 100644 Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 7d909366..70077a9a 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using Ink_Canvas.Helpers; +using Ink_Canvas.Windows.SettingsViews.Helpers; using Ink_Canvas.Windows; using iNKORE.UI.WPF.Modern; using iNKORE.UI.WPF.Modern.Controls; @@ -66,8 +67,6 @@ namespace Ink_Canvas private WindowOverviewModel _windowOverviewModel; // 设置面板相关状态 - private bool isTemporarilyDisablingNoFocusMode = false; - private bool _isApplyingLanguageFromSettings; private bool _isReloadingForLanguageChange; @@ -202,6 +201,10 @@ namespace Ink_Canvas } InitTimers(); + + WindowSettingsHelper.OnStopKillProcessTimer = () => timerKillProcess.Stop(); + WindowSettingsHelper.OnStartKillProcessTimer = () => timerKillProcess.Start(); + WindowSettingsHelper.OnPptOnlyModeChanged = (enabled) => CheckMainWindowVisibility(); timeMachine.OnRedoStateChanged += TimeMachine_OnRedoStateChanged; timeMachine.OnUndoStateChanged += TimeMachine_OnUndoStateChanged; inkCanvas.Strokes.StrokesChanged += StrokesOnStrokesChanged; @@ -1161,8 +1164,8 @@ namespace Ink_Canvas #region Definations and Loading - public static Settings Settings = new Settings(); - public static string settingsFileName = Path.Combine("Configs", "Settings.json"); + public static Settings Settings { get => SettingsManager.Settings; set => SettingsManager.Settings = value; } + public static string settingsFileName => SettingsManager.SettingsFileName; private bool isLoaded; private bool _suppressChickenSoupSourceSelectionChanged; private bool forcePointEraser; @@ -1619,18 +1622,7 @@ namespace Ink_Canvas /// public void SetWindowMode() { - if (Settings.Advanced.WindowMode) - { - WindowState = WindowState.Normal; - Left = 0.0; - Top = 0.0; - Height = SystemParameters.PrimaryScreenHeight; - Width = SystemParameters.PrimaryScreenWidth; - } - else // 全屏 - { - WindowState = WindowState.Maximized; - } + WindowSettingsHelper.SetWindowMode(this); } private bool _allowCloseAfterExitVerification; @@ -2948,9 +2940,6 @@ namespace Ink_Canvas public IntPtr dwExtraInfo; } - private LowLevelKeyboardProc _keyboardProc; - private IntPtr _keyboardHookId = IntPtr.Zero; - private const int GWL_EXSTYLE = -20; private const int WS_EX_NOACTIVATE = 0x08000000; private const int WS_EX_TOPMOST = 0x00000008; @@ -2962,228 +2951,49 @@ namespace Ink_Canvas private const uint SWP_SHOWWINDOW = 0x0040; private const uint SWP_NOOWNERZORDER = 0x0200; - // 添加定时器来维护置顶状态 - private DispatcherTimer topmostMaintenanceTimer; private DispatcherTimer autoSaveStrokesTimer; - private bool isTopmostMaintenanceEnabled; - - private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) - { - return CallNextHookEx(_keyboardHookId, nCode, wParam, lParam); - } private void InstallKeyboardHook() { - if (_keyboardHookId == IntPtr.Zero) - { - _keyboardProc = KeyboardHookProc; - _keyboardHookId = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardProc, - GetModuleHandle(null), 0); - if (_keyboardHookId == IntPtr.Zero) - { - LogHelper.WriteLogToFile("安装低级键盘钩子失败", LogHelper.LogType.Error); - } - } + WindowSettingsHelper.InstallKeyboardHook(); } private void UninstallKeyboardHook() { - if (_keyboardHookId != IntPtr.Zero) - { - UnhookWindowsHookEx(_keyboardHookId); - _keyboardHookId = IntPtr.Zero; - _keyboardProc = null; - } + WindowSettingsHelper.UninstallKeyboardHook(); } public void ApplyNoFocusMode() { - var hwnd = new WindowInteropHelper(this).Handle; - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - - bool shouldBeNoFocus = isTemporarilyDisablingNoFocusMode ? - false : Settings.Advanced.IsNoFocusMode; - - if (shouldBeNoFocus) - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE); - InstallKeyboardHook(); - } - else - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_NOACTIVATE); - UninstallKeyboardHook(); - } + WindowSettingsHelper.ApplyNoFocusMode(this); } public void ApplyAlwaysOnTop() { - try - { - var hwnd = new WindowInteropHelper(this).Handle; - if (Settings.Advanced.IsAlwaysOnTop) - { - Topmost = true; - - // 1. 设置窗口样式为置顶 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - - // 2. 使用SetWindowPos确保窗口在最顶层 - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 3. 如果启用了无焦点模式且未启用UIA置顶,需要特殊处理 - if (Settings.Advanced.IsNoFocusMode && !Settings.Advanced.EnableUIAccessTopMost) - { - // 启动置顶维护定时器 - StartTopmostMaintenance(); - } - else - { - // 停止置顶维护定时器 - StopTopmostMaintenance(); - } - } - else - { - // 取消置顶时 - // 1. 先使用Win32 API取消置顶 - SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 2. 移除置顶窗口样式 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TOPMOST); - - // 3. 停止置顶维护定时器 - StopTopmostMaintenance(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用窗口置顶失败: {ex.Message}", LogHelper.LogType.Error); - } + WindowSettingsHelper.ApplyAlwaysOnTop(this); } - /// - /// 启动置顶维护定时器 - /// private void StartTopmostMaintenance() { - if (Settings.Advanced.EnableUIAccessTopMost) - { - return; - } - - if (isTopmostMaintenanceEnabled) return; - - if (topmostMaintenanceTimer == null) - { - topmostMaintenanceTimer = new DispatcherTimer(); - topmostMaintenanceTimer.Interval = TimeSpan.FromMilliseconds(500); // 每500ms检查一次 - topmostMaintenanceTimer.Tick += TopmostMaintenanceTimer_Tick; - } - - topmostMaintenanceTimer.Start(); - isTopmostMaintenanceEnabled = true; - LogHelper.WriteLogToFile("启动置顶维护定时器", LogHelper.LogType.Trace); + WindowSettingsHelper.PauseTopmostMaintenance(); } - /// - /// 停止置顶维护定时器 - /// private void StopTopmostMaintenance() { - if (topmostMaintenanceTimer != null && isTopmostMaintenanceEnabled) - { - topmostMaintenanceTimer.Stop(); - isTopmostMaintenanceEnabled = false; - LogHelper.WriteLogToFile("停止置顶维护定时器", LogHelper.LogType.Trace); - } + WindowSettingsHelper.PauseTopmostMaintenance(); } public void PauseTopmostMaintenance() { - if (topmostMaintenanceTimer != null && isTopmostMaintenanceEnabled) - { - topmostMaintenanceTimer.Stop(); - } + WindowSettingsHelper.PauseTopmostMaintenance(); } public void ResumeTopmostMaintenance() { - if (Settings.Advanced.IsAlwaysOnTop && - Settings.Advanced.IsNoFocusMode && - !Settings.Advanced.EnableUIAccessTopMost) - { - if (topmostMaintenanceTimer != null && !isTopmostMaintenanceEnabled) - { - topmostMaintenanceTimer.Start(); - isTopmostMaintenanceEnabled = true; - } - } + WindowSettingsHelper.ResumeTopmostMaintenance(this); } - /// - /// 置顶维护定时器事件 - /// - private void TopmostMaintenanceTimer_Tick(object sender, EventArgs e) - { - try - { - if (Settings.Advanced.EnableUIAccessTopMost) - { - StopTopmostMaintenance(); - return; - } - if (!Settings.Advanced.IsAlwaysOnTop || !Settings.Advanced.IsNoFocusMode) - { - StopTopmostMaintenance(); - return; - } - - var hwnd = new WindowInteropHelper(this).Handle; - if (hwnd == IntPtr.Zero) return; - - // 检查窗口是否仍然可见且不是最小化状态 - if (!IsWindow(hwnd) || !IsWindowVisible(hwnd) || IsIconic(hwnd)) - { - return; - } - - // 检查是否有子窗口在前景 - var foregroundWindow = GetForegroundWindow(); - if (foregroundWindow != hwnd) - { - // 检查前景窗口是否是当前应用程序的子窗口 - var foregroundWindowProcessId = GetWindowThreadProcessId(foregroundWindow, out uint processId); - var currentProcessId = GetCurrentProcessId(); - - if (processId == currentProcessId) - { - // 如果有子窗口在前景,暂停置顶维护 - return; - } - - // 如果窗口不在最顶层且没有子窗口,重新设置置顶 - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 确保窗口样式正确 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - if ((exStyle & WS_EX_TOPMOST) == 0) - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"置顶维护定时器出错: {ex.Message}", LogHelper.LogType.Error); - } - } /// /// 根据窗口置顶设置和当前模式设置窗口的Topmost属性 @@ -3191,21 +3001,7 @@ namespace Ink_Canvas /// 当前模式是否需要窗口置顶 public void SetTopmostBasedOnSettings(bool shouldBeTopmost) { - if (Settings.Advanced.IsAlwaysOnTop) - { - // 如果启用了窗口置顶设置,则始终置顶 - Topmost = true; - ApplyAlwaysOnTop(); - } - else - { - // 如果未启用窗口置顶设置,则根据当前模式决定 - Topmost = shouldBeTopmost; - if (!shouldBeTopmost) - { - ApplyAlwaysOnTop(); // 确保取消置顶 - } - } + WindowSettingsHelper.SetTopmostBasedOnSettings(this, shouldBeTopmost); } private void Window_Activated(object sender, EventArgs e) @@ -4586,57 +4382,7 @@ namespace Ink_Canvas /// public void ApplyUIAccessTopMost() { - try - { - if (Settings.Advanced.EnableUIAccessTopMost && Settings.Advanced.IsAlwaysOnTop) - { - // 检查是否以管理员权限运行 - var identity = WindowsIdentity.GetCurrent(); - var principal = new WindowsPrincipal(identity); - - if (principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - try - { - timerKillProcess.Stop(); - if (App.watchdogProcess != null && !App.watchdogProcess.HasExited) - { - App.watchdogProcess.Kill(); - App.watchdogProcess = null; - } - - App.StartWatchdogIfNeeded(); - - if (Environment.Is64BitProcess) - { - PrepareUIAccessX64(); - } - else - { - PrepareUIAccessX86(); - } - - timerKillProcess.Start(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - else - { - LogHelper.WriteLogToFile("UIA置顶功能需要管理员权限", LogHelper.LogType.Warning); - } - } - else - { - LogHelper.WriteLogToFile("UIA置顶功能已禁用", LogHelper.LogType.Trace); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error); - } + WindowSettingsHelper.ApplyUIAccessTopMost(this); } internal void OpenQuickDrawFromHotkey() diff --git a/Ink Canvas/MainWindow_cs/MW_AutoStart.cs b/Ink Canvas/MainWindow_cs/MW_AutoStart.cs index 5e0f77a1..148c5ea3 100644 --- a/Ink Canvas/MainWindow_cs/MW_AutoStart.cs +++ b/Ink Canvas/MainWindow_cs/MW_AutoStart.cs @@ -1,76 +1,11 @@ -using IWshRuntimeLibrary; -using System; -using System.Windows; -using Application = System.Windows.Forms.Application; -using File = System.IO.File; +using Ink_Canvas.Windows.SettingsViews.Helpers; namespace Ink_Canvas { - public partial class MainWindow : Window + public partial class MainWindow { - /// - /// 创建开机自启动快捷方式。 - /// - /// 可执行文件名,用于命名快捷方式。 - /// 创建成功返回true,失败返回false。 - /// - /// 操作包括: - /// 1. 创建Windows Shell对象 - /// 2. 在启动文件夹中创建快捷方式 - /// 3. 设置快捷方式的目标路径为当前可执行文件路径 - /// 4. 设置工作目录为当前目录 - /// 5. 设置窗口样式为普通窗口 - /// 6. 设置快捷方式描述 - /// 7. 保存快捷方式 - /// 8. 捕获可能的异常,确保方法不会因异常而崩溃 - /// - public static bool StartAutomaticallyCreate(string exeName) - { - try - { - var shell = new WshShell(); - var shortcut = (IWshShortcut)shell.CreateShortcut( - Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk"); - //设置快捷方式的目标所在的位置(源程序完整路径) - shortcut.TargetPath = Application.ExecutablePath; - //应用程序的工作目录 - //当用户没有指定一个具体的目录时,快捷方式的目标应用程序将使用该属性所指定的目录来装载或保存文件。 - shortcut.WorkingDirectory = Environment.CurrentDirectory; - //目标应用程序窗口类型(1.Normal window普通窗口,3.Maximized最大化窗口,7.Minimized最小化) - shortcut.WindowStyle = 1; - //快捷方式的描述 - shortcut.Description = exeName + "_Ink"; - //设置快捷键(如果有必要的话.) - //shortcut.Hotkey = "CTRL+ALT+D"; - shortcut.Save(); - return true; - } - catch (Exception) { } + public static bool StartAutomaticallyCreate(string exeName) => AutoStartHelper.StartAutomaticallyCreate(exeName); - return false; - } - - /// - /// 删除开机自启动快捷方式。 - /// - /// 可执行文件名,用于定位要删除的快捷方式。 - /// 删除成功返回true,失败返回false。 - /// - /// 操作包括: - /// 1. 在启动文件夹中删除指定名称的快捷方式 - /// 2. 捕获可能的异常,确保方法不会因异常而崩溃 - /// - public static bool StartAutomaticallyDel(string exeName) - { - try - { - File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + - ".lnk"); - return true; - } - catch (Exception) { } - - return false; - } + public static bool StartAutomaticallyDel(string exeName) => AutoStartHelper.StartAutomaticallyDel(exeName); } -} \ No newline at end of file +} diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 6c7c049f..21019d38 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -1,5 +1,6 @@ using Ink_Canvas.Controls; using Ink_Canvas.Helpers; +using Ink_Canvas.Windows.SettingsViews.Helpers; using iNKORE.UI.WPF.Modern; using System; using System.Diagnostics; @@ -416,9 +417,9 @@ namespace Ink_Canvas { BorderSettings.Visibility = Visibility.Collapsed; isOpeningOrHidingSettingsPane = false; - if (isTemporarilyDisablingNoFocusMode) + if (WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode) { - isTemporarilyDisablingNoFocusMode = false; + WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode = false; ApplyNoFocusMode(); } }; @@ -3080,9 +3081,9 @@ namespace Ink_Canvas // 如果当前在设置面板中,需要先恢复无焦点模式状态 if (BorderSettings.Visibility == Visibility.Visible) { - if (isTemporarilyDisablingNoFocusMode) + if (WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode) { - isTemporarilyDisablingNoFocusMode = false; + WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode = false; ApplyNoFocusMode(); } SaveSettingsToFile(); @@ -3102,9 +3103,9 @@ namespace Ink_Canvas { if (BorderSettings.Visibility == Visibility.Visible) { - if (isTemporarilyDisablingNoFocusMode) + if (WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode) { - isTemporarilyDisablingNoFocusMode = false; + WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode = false; ApplyNoFocusMode(); } SaveSettingsToFile(); @@ -3178,7 +3179,7 @@ namespace Ink_Canvas wasNoFocusModeBeforeSettings = Settings.Advanced.IsNoFocusMode; if (wasNoFocusModeBeforeSettings) { - isTemporarilyDisablingNoFocusMode = true; + WindowSettingsHelper.IsTemporarilyDisablingNoFocusMode = true; ApplyNoFocusMode(); } diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 8fba2a1a..edc5d3ba 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -1,5 +1,6 @@ using H.NotifyIcon; using Ink_Canvas.Helpers; +using Ink_Canvas.Windows.SettingsViews.Helpers; using Newtonsoft.Json; using OSVersionExtension; using System; @@ -5113,22 +5114,7 @@ namespace Ink_Canvas /// /// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。 /// - public static void SaveSettingsToFile() - { - var text = JsonConvert.SerializeObject(Settings, Formatting.Indented); - try - { - string configsDir = Path.Combine(App.RootPath, "Configs"); - if (!Directory.Exists(configsDir)) - { - ProcessProtectionManager.WithWriteAccess(configsDir, () => Directory.CreateDirectory(configsDir)); - } - - var path = App.RootPath + settingsFileName; - ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, text)); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } + public static void SaveSettingsToFile() => SettingsManager.SaveSettingsToFile(); private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e) { diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs b/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs new file mode 100644 index 00000000..bb71490b --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs @@ -0,0 +1,48 @@ +using IWshRuntimeLibrary; +using System; +using System.IO; +using File = System.IO.File; + +namespace Ink_Canvas.Windows.SettingsViews.Helpers +{ + public static class AutoStartHelper + { + public static bool StartAutomaticallyCreate(string exeName) + { + try + { + var shell = new WshShell(); + var shortcut = (IWshShortcut)shell.CreateShortcut( + Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk"); + shortcut.TargetPath = System.Windows.Forms.Application.ExecutablePath; + shortcut.WorkingDirectory = Environment.CurrentDirectory; + shortcut.WindowStyle = 1; + shortcut.Description = exeName + "_Ink"; + shortcut.Save(); + return true; + } + catch (Exception) { } + + return false; + } + + public static bool StartAutomaticallyDel(string exeName) + { + try + { + File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + + ".lnk"); + return true; + } + catch (Exception) { } + + return false; + } + + public static bool IsAutoStartEnabled(string exeName) + { + return File.Exists( + Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk"); + } + } +} diff --git a/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs b/Ink Canvas/Windows/SettingsViews/Helpers/MainWindowSettingsHelper.cs similarity index 71% rename from Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs rename to Ink Canvas/Windows/SettingsViews/Helpers/MainWindowSettingsHelper.cs index 44500cff..a8aacd5d 100644 --- a/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs +++ b/Ink Canvas/Windows/SettingsViews/Helpers/MainWindowSettingsHelper.cs @@ -6,7 +6,7 @@ using System.Windows.Controls; using System.Windows.Input; using Media = System.Windows.Media; -namespace Ink_Canvas.Windows.SettingsViews +namespace Ink_Canvas.Windows.SettingsViews.Helpers { public static class MainWindowSettingsHelper { @@ -15,9 +15,6 @@ namespace Ink_Canvas.Windows.SettingsViews return Application.Current.MainWindow as MainWindow; } - /// - /// 调用 MainWindow 中的方法 - /// public static void InvokeMainWindowMethod(string methodName, params object[] parameters) { try @@ -37,9 +34,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 调用 MainWindow 中的 ToggleSwitch 事件处理方法 - /// public static void InvokeToggleSwitchToggled(string toggleSwitchName, bool isOn) { try @@ -47,11 +41,9 @@ namespace Ink_Canvas.Windows.SettingsViews var mainWindow = GetMainWindow(); if (mainWindow == null) return; - // 获取 MainWindow 中的 ToggleSwitch 控件 var toggleSwitch = mainWindow.FindName(toggleSwitchName); if (toggleSwitch == null) { - // 如果找不到控件,尝试通过反射设置属性 var property = mainWindow.GetType().GetProperty(toggleSwitchName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (property != null && property.PropertyType.Name.Contains("ToggleSwitch")) { @@ -65,16 +57,13 @@ namespace Ink_Canvas.Windows.SettingsViews } } } - // 即使找不到控件,也尝试触发事件(可能通过反射调用) var toggledMethodName = toggleSwitchName + "_Toggled"; InvokeMainWindowMethod(toggledMethodName, null, new RoutedEventArgs()); - // 通知新设置面板同步状态 NotifySettingsPanelsSyncState(toggleSwitchName); return; } - // 设置 ToggleSwitch 的 IsOn 属性 var toggleSwitchType = toggleSwitch.GetType(); var isOnProp = toggleSwitchType.GetProperty("IsOn"); if (isOnProp != null) @@ -82,14 +71,11 @@ namespace Ink_Canvas.Windows.SettingsViews isOnProp.SetValue(toggleSwitch, isOn); } - // 触发 Toggled 事件 var toggledMethodName2 = toggleSwitchName + "_Toggled"; InvokeMainWindowMethod(toggledMethodName2, toggleSwitch, new RoutedEventArgs()); - // 通知新设置面板同步状态 NotifySettingsPanelsSyncState(toggleSwitchName); - // 检查是否需要更新主题 NotifyThemeUpdateIfNeeded(toggleSwitchName); } catch (Exception ex) @@ -98,9 +84,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 调用 MainWindow 中的 ComboBox 事件处理方法 - /// public static void InvokeComboBoxSelectionChanged(string comboBoxName, object selectedItem) { try @@ -108,13 +91,11 @@ namespace Ink_Canvas.Windows.SettingsViews var mainWindow = GetMainWindow(); if (mainWindow == null) return; - // 获取 MainWindow 中的 ComboBox 控件 var comboBox = mainWindow.FindName(comboBoxName) as System.Windows.Controls.ComboBox; if (comboBox != null) { comboBox.SelectedItem = selectedItem; - // 触发 SelectionChanged 事件 var selectionChangedMethodName = comboBoxName + "_SelectionChanged"; InvokeMainWindowMethod(selectionChangedMethodName, comboBox, new System.Windows.Controls.SelectionChangedEventArgs( System.Windows.Controls.Primitives.Selector.SelectionChangedEvent, @@ -122,10 +103,8 @@ namespace Ink_Canvas.Windows.SettingsViews new System.Collections.IList[0])); } - // 通知新设置面板同步状态 NotifySettingsPanelsSyncState(comboBoxName); - // 检查是否需要更新主题 NotifyThemeUpdateIfNeeded(comboBoxName); } catch (Exception ex) @@ -134,9 +113,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 调用 MainWindow 中的 Slider 事件处理方法 - /// public static void InvokeSliderValueChanged(string sliderName, double value) { try @@ -144,23 +120,19 @@ namespace Ink_Canvas.Windows.SettingsViews var mainWindow = GetMainWindow(); if (mainWindow == null) return; - // 获取 MainWindow 中的 Slider 控件 var slider = mainWindow.FindName(sliderName) as System.Windows.Controls.Slider; if (slider != null) { var oldValue = slider.Value; slider.Value = value; - // 触发 ValueChanged 事件 var valueChangedMethodName = sliderName + "_ValueChanged"; InvokeMainWindowMethod(valueChangedMethodName, slider, new System.Windows.RoutedPropertyChangedEventArgs(oldValue, value)); } - // 通知新设置面板同步状态 NotifySettingsPanelsSyncState(sliderName); - // 检查是否需要更新主题(某些Slider可能影响UI外观) NotifyThemeUpdateIfNeeded(sliderName); } catch (Exception ex) @@ -169,9 +141,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 调用 MainWindow 中的 CheckBox 事件处理方法 - /// public static void InvokeCheckBoxCheckedChanged(string checkBoxName, bool isChecked) { try @@ -179,13 +148,11 @@ namespace Ink_Canvas.Windows.SettingsViews var mainWindow = GetMainWindow(); if (mainWindow == null) return; - // 获取 MainWindow 中的 CheckBox 控件 var checkBox = mainWindow.FindName(checkBoxName) as System.Windows.Controls.CheckBox; if (checkBox != null) { checkBox.IsChecked = isChecked; - // 尝试多种可能的方法名 var methodNames = new[] { checkBoxName + "_IsCheckChanged", @@ -211,19 +178,10 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 安全地修改设置并保存,优先调用 MainWindow 中的事件处理方法 - /// 如果找不到对应的事件处理方法,则直接修改设置并保存 - /// - /// 设置修改的 Action,例如:() => MainWindow.Settings.Startup.IsAutoUpdate = true - /// 可选:要调用的 MainWindow 事件处理方法名(如 "ToggleSwitchIsAutoUpdate_Toggled") - /// 可选:控件名称,用于状态同步(如 "ToggleSwitchIsAutoUpdate") - /// 可选:事件处理方法的参数 public static void UpdateSettingSafely(Action action, string eventHandlerName = null, string controlName = null, params object[] eventHandlerParams) { try { - // 如果提供了事件处理方法名,优先调用 if (!string.IsNullOrEmpty(eventHandlerName)) { var mainWindow = GetMainWindow(); @@ -232,10 +190,8 @@ namespace Ink_Canvas.Windows.SettingsViews var method = mainWindow.GetType().GetMethod(eventHandlerName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (method != null) { - // 调用事件处理方法(它会自动保存设置并触发状态同步) method.Invoke(mainWindow, eventHandlerParams); - // 如果提供了控件名称,确保状态同步 if (!string.IsNullOrEmpty(controlName)) { NotifySettingsPanelsSyncState(controlName); @@ -245,11 +201,9 @@ namespace Ink_Canvas.Windows.SettingsViews } } - // 如果没有事件处理方法或调用失败,直接修改设置并保存 action?.Invoke(); MainWindow.SaveSettingsToFile(); - // 如果提供了控件名称,通知面板同步状态 if (!string.IsNullOrEmpty(controlName)) { NotifySettingsPanelsSyncState(controlName); @@ -261,11 +215,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 直接修改设置属性并保存(用于没有对应事件处理方法的设置项) - /// - /// 设置修改的 Action - /// 可选:控件名称,用于状态同步(如 "ToggleSwitchIsAutoUpdate") public static void UpdateSettingDirectly(Action action, string controlName = null) { try @@ -273,7 +222,6 @@ namespace Ink_Canvas.Windows.SettingsViews action?.Invoke(); MainWindow.SaveSettingsToFile(); - // 如果提供了控件名称,通知面板同步状态 if (!string.IsNullOrEmpty(controlName)) { NotifySettingsPanelsSyncState(controlName); @@ -285,9 +233,6 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 调用 MainWindow 中的 TextBox 事件处理方法 - /// public static void InvokeTextBoxTextChanged(string textBoxName, string text) { try @@ -295,20 +240,17 @@ namespace Ink_Canvas.Windows.SettingsViews var mainWindow = GetMainWindow(); if (mainWindow == null) return; - // 获取 MainWindow 中的 TextBox 控件 var textBox = mainWindow.FindName(textBoxName) as System.Windows.Controls.TextBox; if (textBox != null) { textBox.Text = text; - // 触发 TextChanged 事件 var textChangedMethodName = textBoxName + "_TextChanged"; InvokeMainWindowMethod(textChangedMethodName, textBox, new System.Windows.Controls.TextChangedEventArgs( System.Windows.Controls.Primitives.TextBoxBase.TextChangedEvent, System.Windows.Controls.UndoAction.None)); } - // 通知新设置面板同步状态 NotifySettingsPanelsSyncState(textBoxName); } catch (Exception ex) @@ -317,24 +259,18 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 通知所有新设置面板同步指定控件的状态 - /// public static void NotifySettingsPanelsSyncState(string controlName) { try { - // 延迟执行,确保设置已经保存 Application.Current.Dispatcher.BeginInvoke(new Action(() => { - // 查找所有打开的设置窗口 foreach (Window window in Application.Current.Windows) { if (window.GetType().Name == "SettingsWindow") { - // 同步所有面板(保守策略) SyncAllPanels(window); - break; // 通常只有一个设置窗口 + break; } } }), System.Windows.Threading.DispatcherPriority.Background); @@ -345,14 +281,10 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 同步所有面板的状态(保守策略) - /// private static void SyncAllPanels(Window settingsWindow) { try { - // 获取所有页面属性 var pageProperties = settingsWindow.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType.Name.EndsWith("Page") && p.PropertyType.IsSubclassOf(typeof(System.Windows.Controls.Page))); @@ -364,7 +296,6 @@ namespace Ink_Canvas.Windows.SettingsViews var page = pageProp.GetValue(settingsWindow) as System.Windows.Controls.Page; if (page != null) { - // 调用 LoadSettings 方法 var loadMethod = page.GetType().GetMethod("LoadSettings", BindingFlags.Public | BindingFlags.Instance); if (loadMethod != null) @@ -385,41 +316,34 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 检查并通知设置窗口更新主题(如果设置变化可能影响主题) - /// - /// 控件名称,用于判断是否是主题相关的设置 public static void NotifyThemeUpdateIfNeeded(string controlName) { try { if (string.IsNullOrEmpty(controlName)) return; - // 定义可能影响主题的控件名称列表(扩展版) var themeRelatedControls = new[] { - "ComboBoxTheme", // 主题选择 - "ToggleSwitchEnableTrayIcon", // 托盘图标(可能影响图标颜色) - "ComboBoxFloatingBarImg", // 浮动栏图标 - "ComboBoxUnFoldBtnImg", // 展开按钮图标 - "ComboBoxSplashScreenStyle", // 启动画面样式 - "ToggleSwitchEnableSplashScreen", // 启动画面开关 - "ViewboxFloatingBarScaleTransformValueSlider", // 浮动栏缩放(可能影响UI) - "ViewboxFloatingBarOpacityValueSlider", // 浮动栏透明度 - "ViewboxFloatingBarOpacityInPPTValueSlider", // PPT中浮动栏透明度 - "UnFoldBtnImg", // 展开按钮图标(选项按钮) - "FloatingBarImg", // 浮动栏图标(选项按钮) - "Theme" // 主题(选项按钮) + "ComboBoxTheme", + "ToggleSwitchEnableTrayIcon", + "ComboBoxFloatingBarImg", + "ComboBoxUnFoldBtnImg", + "ComboBoxSplashScreenStyle", + "ToggleSwitchEnableSplashScreen", + "ViewboxFloatingBarScaleTransformValueSlider", + "ViewboxFloatingBarOpacityValueSlider", + "ViewboxFloatingBarOpacityInPPTValueSlider", + "UnFoldBtnImg", + "FloatingBarImg", + "Theme" }; - // 检查是否是主题相关的设置(使用更灵活的匹配) bool isThemeRelated = false; string controlNameLower = controlName.ToLower(); foreach (var themeControl in themeRelatedControls) { string themeControlLower = themeControl.ToLower(); - // 检查是否包含或匹配 if (controlNameLower.Contains(themeControlLower) || themeControlLower.Contains(controlNameLower) || controlNameLower == themeControlLower) @@ -431,10 +355,8 @@ namespace Ink_Canvas.Windows.SettingsViews if (isThemeRelated) { - // 延迟通知,确保设置已保存 Application.Current.Dispatcher.BeginInvoke(new Action(() => { - // 通知设置窗口更新主题 NotifySettingsWindowThemeUpdate(); }), System.Windows.Threading.DispatcherPriority.Background); } @@ -445,20 +367,14 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 通知设置窗口更新所有页面的主题 - /// private static void NotifySettingsWindowThemeUpdate() { try { - // 查找所有打开的设置窗口 foreach (Window window in Application.Current.Windows) { - // 使用类型名称匹配,因为 SettingsWindow 在不同的命名空间中 if (window.GetType().Name == "SettingsWindow") { - // 同时调用 ApplyTheme 方法更新窗口本身 var applyThemeMethod = window.GetType().GetMethod("ApplyTheme", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (applyThemeMethod != null) @@ -466,7 +382,7 @@ namespace Ink_Canvas.Windows.SettingsViews applyThemeMethod.Invoke(window, null); } - break; // 通常只有一个设置窗口 + break; } } } @@ -476,36 +392,23 @@ namespace Ink_Canvas.Windows.SettingsViews } } - /// - /// 强制更新所有设置页面的主题(公共方法,可在外部调用) - /// public static void ForceUpdateAllPanelsTheme() { NotifySettingsWindowThemeUpdate(); } - /// - /// 调用 MainWindow 中的 ComboBox 事件处理方法(增强版,支持主题更新通知) - /// public static void InvokeComboBoxSelectionChangedWithThemeCheck(string comboBoxName, object selectedItem) { InvokeComboBoxSelectionChanged(comboBoxName, selectedItem); NotifyThemeUpdateIfNeeded(comboBoxName); } - /// - /// 调用 MainWindow 中的 ToggleSwitch 事件处理方法(增强版,支持主题更新通知) - /// public static void InvokeToggleSwitchToggledWithThemeCheck(string toggleSwitchName, bool isOn) { InvokeToggleSwitchToggled(toggleSwitchName, isOn); NotifyThemeUpdateIfNeeded(toggleSwitchName); } - /// - /// 为控件树中的所有交互控件启用触摸支持(公共方法,可在外部调用) - /// - /// 父控件 public static void EnableTouchSupportForControls(DependencyObject parent) { if (parent == null) return; @@ -514,15 +417,12 @@ namespace Ink_Canvas.Windows.SettingsViews { var child = Media.VisualTreeHelper.GetChild(parent, i); - // 为 Border 控件(ToggleSwitch、选项按钮等)启用触摸支持 if (child is Border border) { - // 检查是否是交互控件(有 Tag 或 Cursor 为 Hand) if (border.Tag != null || border.Cursor == Cursors.Hand) { border.IsManipulationEnabled = true; - // 添加触摸事件支持,将触摸事件转换为鼠标事件 border.TouchDown += (s, e) => { var touchPoint = e.GetTouchPoint(border); @@ -536,7 +436,6 @@ namespace Ink_Canvas.Windows.SettingsViews e.Handled = true; }; - // 添加触摸释放事件 border.TouchUp += (s, e) => { border.ReleaseTouchCapture(e.TouchDevice); @@ -544,38 +443,31 @@ namespace Ink_Canvas.Windows.SettingsViews }; } } - // 为 Button 控件启用触摸支持 else if (child is Button button) { button.IsManipulationEnabled = true; } - // 为 ComboBox 启用触摸支持 else if (child is ComboBox comboBox) { comboBox.IsManipulationEnabled = true; } - // 为 Slider 启用触摸支持 else if (child is Slider slider) { slider.IsManipulationEnabled = true; } - // 为 TextBox 启用触摸支持 else if (child is TextBox textBox) { textBox.IsManipulationEnabled = true; } - // 为 CheckBox 启用触摸支持 else if (child is CheckBox checkBox) { checkBox.IsManipulationEnabled = true; } - // 为 RadioButton 启用触摸支持 else if (child is RadioButton radioButton) { radioButton.IsManipulationEnabled = true; } - // 递归处理子元素 EnableTouchSupportForControls(child); } } diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs b/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs new file mode 100644 index 00000000..95b26d4f --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using System; +using System.IO; +using ProcessProtectionManager = Ink_Canvas.Helpers.ProcessProtectionManager; + +namespace Ink_Canvas.Windows.SettingsViews.Helpers +{ + public static class SettingsManager + { + public static Settings Settings { get; set; } = new Settings(); + + public static string SettingsFileName { get; } = Path.Combine("Configs", "Settings.json"); + + public static void SaveSettingsToFile() + { + var text = JsonConvert.SerializeObject(Settings, Formatting.Indented); + try + { + string configsDir = Path.Combine(App.RootPath, "Configs"); + if (!Directory.Exists(configsDir)) + { + ProcessProtectionManager.WithWriteAccess(configsDir, () => Directory.CreateDirectory(configsDir)); + } + + var path = App.RootPath + SettingsFileName; + ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, text)); + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + } +} diff --git a/Ink Canvas/Windows/SettingsViews/Pages/TopMostModeTemplateSelector.cs b/Ink Canvas/Windows/SettingsViews/Helpers/TopMostModeTemplateSelector.cs similarity index 93% rename from Ink Canvas/Windows/SettingsViews/Pages/TopMostModeTemplateSelector.cs rename to Ink Canvas/Windows/SettingsViews/Helpers/TopMostModeTemplateSelector.cs index 3786f924..5aa1a7b4 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/TopMostModeTemplateSelector.cs +++ b/Ink Canvas/Windows/SettingsViews/Helpers/TopMostModeTemplateSelector.cs @@ -1,7 +1,7 @@ using System.Windows; using System.Windows.Controls; -namespace Ink_Canvas.Windows.SettingsViews.Pages +namespace Ink_Canvas.Windows.SettingsViews.Helpers { public class TopMostModeSelectionItem { diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs b/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs new file mode 100644 index 00000000..64675c95 --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs @@ -0,0 +1,463 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Threading; +using Ink_Canvas.Helpers; + +namespace Ink_Canvas.Windows.SettingsViews.Helpers +{ + public static class WindowSettingsHelper + { + #region Win32 API + + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); + + [DllImport("UIAccessDLL_x86.dll", EntryPoint = "PrepareUIAccess", CallingConvention = CallingConvention.Cdecl)] + private static extern Int32 PrepareUIAccessX86(); + + [DllImport("UIAccessDLL_x64.dll", EntryPoint = "PrepareUIAccess", CallingConvention = CallingConvention.Cdecl)] + private static extern Int32 PrepareUIAccessX64(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentProcessId(); + + private const int GWL_EXSTYLE = -20; + private const int WS_EX_NOACTIVATE = 0x08000000; + private const int WS_EX_TOPMOST = 0x00000008; + private const int WH_KEYBOARD_LL = 13; + + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_NOACTIVATE = 0x0010; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOOWNERZORDER = 0x0200; + + private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); + + #endregion + + #region Keyboard Hook + + private static LowLevelKeyboardProc _keyboardProc; + private static IntPtr _keyboardHookId = IntPtr.Zero; + + private static IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) + { + return CallNextHookEx(_keyboardHookId, nCode, wParam, lParam); + } + + public static void InstallKeyboardHook() + { + if (_keyboardHookId == IntPtr.Zero) + { + _keyboardProc = KeyboardHookProc; + _keyboardHookId = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardProc, + GetModuleHandle(null), 0); + if (_keyboardHookId == IntPtr.Zero) + { + LogHelper.WriteLogToFile("安装低级键盘钩子失败", LogHelper.LogType.Error); + } + } + } + + public static void UninstallKeyboardHook() + { + if (_keyboardHookId != IntPtr.Zero) + { + UnhookWindowsHookEx(_keyboardHookId); + _keyboardHookId = IntPtr.Zero; + _keyboardProc = null; + } + } + + #endregion + + #region Timer Callbacks + + public static Action OnStopKillProcessTimer { get; set; } + public static Action OnStartKillProcessTimer { get; set; } + + #endregion + + #region Topmost Maintenance Timer + + private static DispatcherTimer _topmostMaintenanceTimer; + private static bool _isTopmostMaintenanceEnabled; + private static Window _maintainedWindow; + + #endregion + + #region PPT Only Mode + + private static DispatcherTimer _pptOnlyVisibilityProbeTimer; + private static Window _pptModeWindow; + private const int PptOnlyVisibilityProbeIntervalMs = 800; + + public static Action OnPptOnlyModeChanged { get; set; } + + public static void ApplyPptOnlyMode(Window window, bool isEnabled) + { + try + { + SettingsManager.Settings.ModeSettings.IsPPTOnlyMode = isEnabled; + SettingsManager.SaveSettingsToFile(); + + if (isEnabled) + { + window.Hide(); + LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); + EnsurePptOnlyVisibilityProbeTimer(window); + } + else + { + StopPptOnlyVisibilityProbeTimer(); + window.Show(); + LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换模式时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + private static void EnsurePptOnlyVisibilityProbeTimer(Window window) + { + try + { + if (!SettingsManager.Settings.ModeSettings.IsPPTOnlyMode) + { + StopPptOnlyVisibilityProbeTimer(); + return; + } + + _pptModeWindow = window; + + if (_pptOnlyVisibilityProbeTimer == null) + { + _pptOnlyVisibilityProbeTimer = new DispatcherTimer + { + Interval = TimeSpan.FromMilliseconds(PptOnlyVisibilityProbeIntervalMs) + }; + _pptOnlyVisibilityProbeTimer.Tick += PptOnlyVisibilityProbeTimer_Tick; + } + + if (!_pptOnlyVisibilityProbeTimer.IsEnabled) + _pptOnlyVisibilityProbeTimer.Start(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"仅PPT可见性探测计时器启动失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + + private static void StopPptOnlyVisibilityProbeTimer() + { + try + { + _pptOnlyVisibilityProbeTimer?.Stop(); + } + catch { } + } + + private static void PptOnlyVisibilityProbeTimer_Tick(object sender, EventArgs e) + { + OnPptOnlyModeChanged?.Invoke(true); + } + + #endregion + + #region Window Settings Methods + + public static bool IsTemporarilyDisablingNoFocusMode { get; set; } + + public static void ApplyNoFocusMode(Window window) + { + var hwnd = new WindowInteropHelper(window).Handle; + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + + bool shouldBeNoFocus = !IsTemporarilyDisablingNoFocusMode && SettingsManager.Settings.Advanced.IsNoFocusMode; + + if (shouldBeNoFocus) + { + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE); + InstallKeyboardHook(); + } + else + { + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_NOACTIVATE); + UninstallKeyboardHook(); + } + } + + public static void SetWindowMode(Window window) + { + if (SettingsManager.Settings.Advanced.WindowMode) + { + window.WindowState = WindowState.Normal; + window.Left = 0.0; + window.Top = 0.0; + window.Height = SystemParameters.PrimaryScreenHeight; + window.Width = SystemParameters.PrimaryScreenWidth; + } + else + { + window.WindowState = WindowState.Maximized; + } + } + + public static void ApplyAlwaysOnTop(Window window) + { + try + { + var hwnd = new WindowInteropHelper(window).Handle; + if (SettingsManager.Settings.Advanced.IsAlwaysOnTop) + { + window.Topmost = true; + + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + if (SettingsManager.Settings.Advanced.IsNoFocusMode && !SettingsManager.Settings.Advanced.EnableUIAccessTopMost) + { + StartTopmostMaintenance(window); + } + else + { + StopTopmostMaintenance(); + } + } + else + { + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TOPMOST); + + StopTopmostMaintenance(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用窗口置顶失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + public static void ApplyUIAccessTopMost(Window window) + { + try + { + if (SettingsManager.Settings.Advanced.EnableUIAccessTopMost && SettingsManager.Settings.Advanced.IsAlwaysOnTop) + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + try + { + OnStopKillProcessTimer?.Invoke(); + + if (App.watchdogProcess != null && !App.watchdogProcess.HasExited) + { + App.watchdogProcess.Kill(); + App.watchdogProcess = null; + } + + App.StartWatchdogIfNeeded(); + + if (Environment.Is64BitProcess) + { + PrepareUIAccessX64(); + } + else + { + PrepareUIAccessX86(); + } + + OnStartKillProcessTimer?.Invoke(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + else + { + LogHelper.WriteLogToFile("UIA置顶功能需要管理员权限", LogHelper.LogType.Warning); + } + } + else + { + LogHelper.WriteLogToFile("UIA置顶功能已禁用", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + public static void SetTopmostBasedOnSettings(Window window, bool shouldBeTopmost) + { + if (SettingsManager.Settings.Advanced.IsAlwaysOnTop) + { + window.Topmost = true; + ApplyAlwaysOnTop(window); + } + else + { + window.Topmost = shouldBeTopmost; + if (!shouldBeTopmost) + { + ApplyAlwaysOnTop(window); + } + } + } + + public static void PauseTopmostMaintenance() + { + if (_topmostMaintenanceTimer != null && _isTopmostMaintenanceEnabled) + { + _topmostMaintenanceTimer.Stop(); + } + } + + public static void ResumeTopmostMaintenance(Window window) + { + if (SettingsManager.Settings.Advanced.IsAlwaysOnTop && + SettingsManager.Settings.Advanced.IsNoFocusMode && + !SettingsManager.Settings.Advanced.EnableUIAccessTopMost) + { + if (_topmostMaintenanceTimer != null && !_isTopmostMaintenanceEnabled) + { + _topmostMaintenanceTimer.Start(); + _isTopmostMaintenanceEnabled = true; + } + } + } + + private static void StartTopmostMaintenance(Window window) + { + if (SettingsManager.Settings.Advanced.EnableUIAccessTopMost) return; + if (_isTopmostMaintenanceEnabled) return; + + _maintainedWindow = window; + + if (_topmostMaintenanceTimer == null) + { + _topmostMaintenanceTimer = new DispatcherTimer(); + _topmostMaintenanceTimer.Interval = TimeSpan.FromMilliseconds(500); + _topmostMaintenanceTimer.Tick += TopmostMaintenanceTimer_Tick; + } + + _topmostMaintenanceTimer.Start(); + _isTopmostMaintenanceEnabled = true; + LogHelper.WriteLogToFile("启动置顶维护定时器", LogHelper.LogType.Trace); + } + + private static void StopTopmostMaintenance() + { + if (_topmostMaintenanceTimer != null && _isTopmostMaintenanceEnabled) + { + _topmostMaintenanceTimer.Stop(); + _isTopmostMaintenanceEnabled = false; + LogHelper.WriteLogToFile("停止置顶维护定时器", LogHelper.LogType.Trace); + } + } + + private static void TopmostMaintenanceTimer_Tick(object sender, EventArgs e) + { + try + { + if (SettingsManager.Settings.Advanced.EnableUIAccessTopMost) + { + StopTopmostMaintenance(); + return; + } + + if (!SettingsManager.Settings.Advanced.IsAlwaysOnTop || !SettingsManager.Settings.Advanced.IsNoFocusMode) + { + StopTopmostMaintenance(); + return; + } + + var window = _maintainedWindow; + if (window == null) return; + + var hwnd = new WindowInteropHelper(window).Handle; + if (hwnd == IntPtr.Zero) return; + + if (!IsWindow(hwnd) || !IsWindowVisible(hwnd) || IsIconic(hwnd)) return; + + var foregroundWindow = GetForegroundWindow(); + if (foregroundWindow != hwnd) + { + GetWindowThreadProcessId(foregroundWindow, out uint processId); + var currentProcessId = GetCurrentProcessId(); + + if (processId == currentProcessId) return; + + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if ((exStyle & WS_EX_TOPMOST) == 0) + { + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"置顶维护定时器出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion + } +} diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml index f5001078..462f691c 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Ink_Canvas.Windows.SettingsViews.Pages" + xmlns:helpers="clr-namespace:Ink_Canvas.Windows.SettingsViews.Helpers" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf" xmlns:i18n="clr-namespace:Ink_Canvas.MarkupExtensions" @@ -62,8 +63,8 @@ OffContent="{DynamicResource Common_Off}" Toggled="ToggleSwitchAlwaysOnTop_Toggled" /> - - + + @@ -79,8 +80,8 @@ - - + +