From 10a0fbb6318f8b1c44cc56d0db0ca948cc61ed6b Mon Sep 17 00:00:00 2001
From: HAILVa1kyrie <280684694+HAILVa1kyrie@users.noreply.github.com>
Date: Sat, 10 Oct 2026 14:22:45 +0800
Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=E6=96=87=E4=BB=B6=E5=90=8D?=
=?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
去掉文件名中的 &。纯改名,无逻辑改动。
---
.../{MW_Save&OpenStrokes.cs => MW_SaveAndOpenStrokes.cs} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename Ink Canvas/MainWindow_cs/{MW_Save&OpenStrokes.cs => MW_SaveAndOpenStrokes.cs} (100%)
diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs
similarity index 100%
rename from Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
rename to Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs
From 376f8615fc5517cc6a2413665f326e25d4755e92 Mon Sep 17 00:00:00 2001
From: HAILVa1kyrie <280684694+HAILVa1kyrie@users.noreply.github.com>
Date: Tue, 29 Sep 2026 22:08:52 +0800
Subject: [PATCH 2/6] =?UTF-8?q?build:=20csproj=20=E8=AF=AD=E8=A8=80?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=AF=B9=E9=BD=90=E5=88=B0=20C#=2012?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
InkCanvas.Controls 的 LangVersion 对齐到 12。
---
InkCanvas.Controls/InkCanvas.Controls.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/InkCanvas.Controls/InkCanvas.Controls.csproj b/InkCanvas.Controls/InkCanvas.Controls.csproj
index de4777a0..922ac1c7 100644
--- a/InkCanvas.Controls/InkCanvas.Controls.csproj
+++ b/InkCanvas.Controls/InkCanvas.Controls.csproj
@@ -8,7 +8,7 @@
InkCanvas.Controls
true
true
- 10
+ 12
CA1416;CS1591
false
From 6f7ef02133c88028b65095938560ba8832e0a4cc Mon Sep 17 00:00:00 2001
From: HAILVa1kyrie <280684694+HAILVa1kyrie@users.noreply.github.com>
Date: Wed, 30 Sep 2026 21:36:18 +0800
Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20Helpers=20=E6=8C=89=E5=9F=9F?=
=?UTF-8?q?=E5=88=86=E7=BB=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增 Ink/Ppt/Update/Security/UiShell 五个子目录,文件纯移动。
---
Ink Canvas/Helpers/{ => Ink}/AdvancedBezierSmoothing.cs | 0
Ink Canvas/Helpers/{ => Ink}/PerformanceMonitorHelper.cs | 0
Ink Canvas/Helpers/{ => Ink}/PerformanceTransparentWin.cs | 0
Ink Canvas/Helpers/{ => Ink}/RealtimeInkFrameScheduler.cs | 0
Ink Canvas/Helpers/{ => Ink}/RealtimeInkPerformanceMonitor.cs | 0
Ink Canvas/Helpers/{ => Ppt}/ComPPTLinkManager.cs | 0
Ink Canvas/Helpers/{ => Ppt}/PPTPageKeyHook.cs | 0
Ink Canvas/Helpers/{ => Ppt}/PPTROTConnectionHelper.cs | 0
Ink Canvas/Helpers/{ => Ppt}/PPTUIManager.cs | 0
Ink Canvas/Helpers/{ => Ppt}/ROTPPTManager.cs | 0
Ink Canvas/Helpers/{ => Security}/FeedbackSanitizer.cs | 0
Ink Canvas/Helpers/{ => Security}/SafeZipExtractor.cs | 0
Ink Canvas/Helpers/{ => Security}/SecurityManager.cs | 0
Ink Canvas/Helpers/{ => Security}/UsbSecurityManager.cs | 0
Ink Canvas/Helpers/{ => UiShell}/AnimationsHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/AvoidFullScreenHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/DwmCompositionHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/ExceptionHandler.cs | 0
Ink Canvas/Helpers/{ => UiShell}/FullScreenHelper.Win32.cs | 0
Ink Canvas/Helpers/{ => UiShell}/FullScreenHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/PopupManagerHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/ScreenDetectionHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/SliderTouchHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/ThemeHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/TouchAwareDragDropHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/WindowBackdropHelper.cs | 0
Ink Canvas/Helpers/{ => UiShell}/WindowOverviewModel.cs | 0
Ink Canvas/Helpers/{ => UiShell}/WindowTopmostManager.cs | 0
Ink Canvas/Helpers/{ => Update}/AutoUpdateHelper.cs | 0
Ink Canvas/Helpers/{ => Update}/BaseUploadQueue.cs | 0
Ink Canvas/Helpers/{ => Update}/DlassApiClient.cs | 0
Ink Canvas/Helpers/{ => Update}/DlassUploadQueue.cs | 0
Ink Canvas/Helpers/{ => Update}/TelemetryUploader.cs | 0
Ink Canvas/Helpers/{ => Update}/UploadHelper.cs | 0
Ink Canvas/Helpers/{ => Update}/UploadQueueHelper.cs | 0
Ink Canvas/Helpers/{ => Update}/WebDavUploadQueue.cs | 0
Ink Canvas/Helpers/{ => Update}/WebDavUploader.cs | 0
37 files changed, 0 insertions(+), 0 deletions(-)
rename Ink Canvas/Helpers/{ => Ink}/AdvancedBezierSmoothing.cs (100%)
rename Ink Canvas/Helpers/{ => Ink}/PerformanceMonitorHelper.cs (100%)
rename Ink Canvas/Helpers/{ => Ink}/PerformanceTransparentWin.cs (100%)
rename Ink Canvas/Helpers/{ => Ink}/RealtimeInkFrameScheduler.cs (100%)
rename Ink Canvas/Helpers/{ => Ink}/RealtimeInkPerformanceMonitor.cs (100%)
rename Ink Canvas/Helpers/{ => Ppt}/ComPPTLinkManager.cs (100%)
rename Ink Canvas/Helpers/{ => Ppt}/PPTPageKeyHook.cs (100%)
rename Ink Canvas/Helpers/{ => Ppt}/PPTROTConnectionHelper.cs (100%)
rename Ink Canvas/Helpers/{ => Ppt}/PPTUIManager.cs (100%)
rename Ink Canvas/Helpers/{ => Ppt}/ROTPPTManager.cs (100%)
rename Ink Canvas/Helpers/{ => Security}/FeedbackSanitizer.cs (100%)
rename Ink Canvas/Helpers/{ => Security}/SafeZipExtractor.cs (100%)
rename Ink Canvas/Helpers/{ => Security}/SecurityManager.cs (100%)
rename Ink Canvas/Helpers/{ => Security}/UsbSecurityManager.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/AnimationsHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/AvoidFullScreenHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/DwmCompositionHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/ExceptionHandler.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/FullScreenHelper.Win32.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/FullScreenHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/PopupManagerHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/ScreenDetectionHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/SliderTouchHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/ThemeHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/TouchAwareDragDropHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/WindowBackdropHelper.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/WindowOverviewModel.cs (100%)
rename Ink Canvas/Helpers/{ => UiShell}/WindowTopmostManager.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/AutoUpdateHelper.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/BaseUploadQueue.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/DlassApiClient.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/DlassUploadQueue.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/TelemetryUploader.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/UploadHelper.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/UploadQueueHelper.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/WebDavUploadQueue.cs (100%)
rename Ink Canvas/Helpers/{ => Update}/WebDavUploader.cs (100%)
diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/Ink/AdvancedBezierSmoothing.cs
similarity index 100%
rename from Ink Canvas/Helpers/AdvancedBezierSmoothing.cs
rename to Ink Canvas/Helpers/Ink/AdvancedBezierSmoothing.cs
diff --git a/Ink Canvas/Helpers/PerformanceMonitorHelper.cs b/Ink Canvas/Helpers/Ink/PerformanceMonitorHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/PerformanceMonitorHelper.cs
rename to Ink Canvas/Helpers/Ink/PerformanceMonitorHelper.cs
diff --git a/Ink Canvas/Helpers/PerformanceTransparentWin.cs b/Ink Canvas/Helpers/Ink/PerformanceTransparentWin.cs
similarity index 100%
rename from Ink Canvas/Helpers/PerformanceTransparentWin.cs
rename to Ink Canvas/Helpers/Ink/PerformanceTransparentWin.cs
diff --git a/Ink Canvas/Helpers/RealtimeInkFrameScheduler.cs b/Ink Canvas/Helpers/Ink/RealtimeInkFrameScheduler.cs
similarity index 100%
rename from Ink Canvas/Helpers/RealtimeInkFrameScheduler.cs
rename to Ink Canvas/Helpers/Ink/RealtimeInkFrameScheduler.cs
diff --git a/Ink Canvas/Helpers/RealtimeInkPerformanceMonitor.cs b/Ink Canvas/Helpers/Ink/RealtimeInkPerformanceMonitor.cs
similarity index 100%
rename from Ink Canvas/Helpers/RealtimeInkPerformanceMonitor.cs
rename to Ink Canvas/Helpers/Ink/RealtimeInkPerformanceMonitor.cs
diff --git a/Ink Canvas/Helpers/ComPPTLinkManager.cs b/Ink Canvas/Helpers/Ppt/ComPPTLinkManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/ComPPTLinkManager.cs
rename to Ink Canvas/Helpers/Ppt/ComPPTLinkManager.cs
diff --git a/Ink Canvas/Helpers/PPTPageKeyHook.cs b/Ink Canvas/Helpers/Ppt/PPTPageKeyHook.cs
similarity index 100%
rename from Ink Canvas/Helpers/PPTPageKeyHook.cs
rename to Ink Canvas/Helpers/Ppt/PPTPageKeyHook.cs
diff --git a/Ink Canvas/Helpers/PPTROTConnectionHelper.cs b/Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/PPTROTConnectionHelper.cs
rename to Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs
diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/Ppt/PPTUIManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/PPTUIManager.cs
rename to Ink Canvas/Helpers/Ppt/PPTUIManager.cs
diff --git a/Ink Canvas/Helpers/ROTPPTManager.cs b/Ink Canvas/Helpers/Ppt/ROTPPTManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/ROTPPTManager.cs
rename to Ink Canvas/Helpers/Ppt/ROTPPTManager.cs
diff --git a/Ink Canvas/Helpers/FeedbackSanitizer.cs b/Ink Canvas/Helpers/Security/FeedbackSanitizer.cs
similarity index 100%
rename from Ink Canvas/Helpers/FeedbackSanitizer.cs
rename to Ink Canvas/Helpers/Security/FeedbackSanitizer.cs
diff --git a/Ink Canvas/Helpers/SafeZipExtractor.cs b/Ink Canvas/Helpers/Security/SafeZipExtractor.cs
similarity index 100%
rename from Ink Canvas/Helpers/SafeZipExtractor.cs
rename to Ink Canvas/Helpers/Security/SafeZipExtractor.cs
diff --git a/Ink Canvas/Helpers/SecurityManager.cs b/Ink Canvas/Helpers/Security/SecurityManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/SecurityManager.cs
rename to Ink Canvas/Helpers/Security/SecurityManager.cs
diff --git a/Ink Canvas/Helpers/UsbSecurityManager.cs b/Ink Canvas/Helpers/Security/UsbSecurityManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/UsbSecurityManager.cs
rename to Ink Canvas/Helpers/Security/UsbSecurityManager.cs
diff --git a/Ink Canvas/Helpers/AnimationsHelper.cs b/Ink Canvas/Helpers/UiShell/AnimationsHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/AnimationsHelper.cs
rename to Ink Canvas/Helpers/UiShell/AnimationsHelper.cs
diff --git a/Ink Canvas/Helpers/AvoidFullScreenHelper.cs b/Ink Canvas/Helpers/UiShell/AvoidFullScreenHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/AvoidFullScreenHelper.cs
rename to Ink Canvas/Helpers/UiShell/AvoidFullScreenHelper.cs
diff --git a/Ink Canvas/Helpers/DwmCompositionHelper.cs b/Ink Canvas/Helpers/UiShell/DwmCompositionHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/DwmCompositionHelper.cs
rename to Ink Canvas/Helpers/UiShell/DwmCompositionHelper.cs
diff --git a/Ink Canvas/Helpers/ExceptionHandler.cs b/Ink Canvas/Helpers/UiShell/ExceptionHandler.cs
similarity index 100%
rename from Ink Canvas/Helpers/ExceptionHandler.cs
rename to Ink Canvas/Helpers/UiShell/ExceptionHandler.cs
diff --git a/Ink Canvas/Helpers/FullScreenHelper.Win32.cs b/Ink Canvas/Helpers/UiShell/FullScreenHelper.Win32.cs
similarity index 100%
rename from Ink Canvas/Helpers/FullScreenHelper.Win32.cs
rename to Ink Canvas/Helpers/UiShell/FullScreenHelper.Win32.cs
diff --git a/Ink Canvas/Helpers/FullScreenHelper.cs b/Ink Canvas/Helpers/UiShell/FullScreenHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/FullScreenHelper.cs
rename to Ink Canvas/Helpers/UiShell/FullScreenHelper.cs
diff --git a/Ink Canvas/Helpers/PopupManagerHelper.cs b/Ink Canvas/Helpers/UiShell/PopupManagerHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/PopupManagerHelper.cs
rename to Ink Canvas/Helpers/UiShell/PopupManagerHelper.cs
diff --git a/Ink Canvas/Helpers/ScreenDetectionHelper.cs b/Ink Canvas/Helpers/UiShell/ScreenDetectionHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/ScreenDetectionHelper.cs
rename to Ink Canvas/Helpers/UiShell/ScreenDetectionHelper.cs
diff --git a/Ink Canvas/Helpers/SliderTouchHelper.cs b/Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/SliderTouchHelper.cs
rename to Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs
diff --git a/Ink Canvas/Helpers/ThemeHelper.cs b/Ink Canvas/Helpers/UiShell/ThemeHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/ThemeHelper.cs
rename to Ink Canvas/Helpers/UiShell/ThemeHelper.cs
diff --git a/Ink Canvas/Helpers/TouchAwareDragDropHelper.cs b/Ink Canvas/Helpers/UiShell/TouchAwareDragDropHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/TouchAwareDragDropHelper.cs
rename to Ink Canvas/Helpers/UiShell/TouchAwareDragDropHelper.cs
diff --git a/Ink Canvas/Helpers/WindowBackdropHelper.cs b/Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/WindowBackdropHelper.cs
rename to Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs
diff --git a/Ink Canvas/Helpers/WindowOverviewModel.cs b/Ink Canvas/Helpers/UiShell/WindowOverviewModel.cs
similarity index 100%
rename from Ink Canvas/Helpers/WindowOverviewModel.cs
rename to Ink Canvas/Helpers/UiShell/WindowOverviewModel.cs
diff --git a/Ink Canvas/Helpers/WindowTopmostManager.cs b/Ink Canvas/Helpers/UiShell/WindowTopmostManager.cs
similarity index 100%
rename from Ink Canvas/Helpers/WindowTopmostManager.cs
rename to Ink Canvas/Helpers/UiShell/WindowTopmostManager.cs
diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/Update/AutoUpdateHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/AutoUpdateHelper.cs
rename to Ink Canvas/Helpers/Update/AutoUpdateHelper.cs
diff --git a/Ink Canvas/Helpers/BaseUploadQueue.cs b/Ink Canvas/Helpers/Update/BaseUploadQueue.cs
similarity index 100%
rename from Ink Canvas/Helpers/BaseUploadQueue.cs
rename to Ink Canvas/Helpers/Update/BaseUploadQueue.cs
diff --git a/Ink Canvas/Helpers/DlassApiClient.cs b/Ink Canvas/Helpers/Update/DlassApiClient.cs
similarity index 100%
rename from Ink Canvas/Helpers/DlassApiClient.cs
rename to Ink Canvas/Helpers/Update/DlassApiClient.cs
diff --git a/Ink Canvas/Helpers/DlassUploadQueue.cs b/Ink Canvas/Helpers/Update/DlassUploadQueue.cs
similarity index 100%
rename from Ink Canvas/Helpers/DlassUploadQueue.cs
rename to Ink Canvas/Helpers/Update/DlassUploadQueue.cs
diff --git a/Ink Canvas/Helpers/TelemetryUploader.cs b/Ink Canvas/Helpers/Update/TelemetryUploader.cs
similarity index 100%
rename from Ink Canvas/Helpers/TelemetryUploader.cs
rename to Ink Canvas/Helpers/Update/TelemetryUploader.cs
diff --git a/Ink Canvas/Helpers/UploadHelper.cs b/Ink Canvas/Helpers/Update/UploadHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/UploadHelper.cs
rename to Ink Canvas/Helpers/Update/UploadHelper.cs
diff --git a/Ink Canvas/Helpers/UploadQueueHelper.cs b/Ink Canvas/Helpers/Update/UploadQueueHelper.cs
similarity index 100%
rename from Ink Canvas/Helpers/UploadQueueHelper.cs
rename to Ink Canvas/Helpers/Update/UploadQueueHelper.cs
diff --git a/Ink Canvas/Helpers/WebDavUploadQueue.cs b/Ink Canvas/Helpers/Update/WebDavUploadQueue.cs
similarity index 100%
rename from Ink Canvas/Helpers/WebDavUploadQueue.cs
rename to Ink Canvas/Helpers/Update/WebDavUploadQueue.cs
diff --git a/Ink Canvas/Helpers/WebDavUploader.cs b/Ink Canvas/Helpers/Update/WebDavUploader.cs
similarity index 100%
rename from Ink Canvas/Helpers/WebDavUploader.cs
rename to Ink Canvas/Helpers/Update/WebDavUploader.cs
From d712d15ceaab0af0807d1330807c5e79613e4e98 Mon Sep 17 00:00:00 2001
From: HAILVa1kyrie <280684694+HAILVa1kyrie@users.noreply.github.com>
Date: Thu, 1 Oct 2026 07:52:27 +0800
Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=E4=BB=8E=20MainWindow=20?=
=?UTF-8?q?=E6=8F=90=E5=8F=96=E4=BD=8E=E8=80=A6=E5=90=88=E6=9C=8D=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
提取 SettingsStore、TrayIconService、NotificationService、TimerService、ScreenshotService、HotkeyService,原位保留转发壳。
---
Ink Canvas/App.xaml.cs | 66 ++
Ink Canvas/Helpers/GlobalHotkeyManager.cs | 485 +--------
Ink Canvas/MainWindow.xaml.cs | 62 +-
Ink Canvas/MainWindow_cs/MW_Hotkeys.cs | 33 +
Ink Canvas/MainWindow_cs/MW_ImageInsert.cs | 428 +-------
Ink Canvas/MainWindow_cs/MW_Notification.cs | 291 +-----
Ink Canvas/MainWindow_cs/MW_Screenshot.cs | 450 +-------
Ink Canvas/MainWindow_cs/MW_Settings.cs | 240 +----
Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 137 +--
Ink Canvas/MainWindow_cs/MW_TrayIcon.cs | 820 +--------------
Ink Canvas/Models/TimerState.cs | 85 ++
Ink Canvas/Services/Classroom/TimerService.cs | 415 ++++++++
Ink Canvas/Services/Settings/SettingsStore.cs | 423 ++++++++
Ink Canvas/Services/Shell/HotkeyService.cs | 474 +++++++++
.../Services/Shell/NotificationService.cs | 388 +++++++
.../Services/Shell/ScreenshotService.cs | 969 ++++++++++++++++++
Ink Canvas/Services/Shell/TrayIconService.cs | 840 +++++++++++++++
.../Windows/CountdownTimerWindow.xaml.cs | 295 +++---
.../Windows/NewStyleTimerWindow.xaml.cs | 459 +++------
19 files changed, 4168 insertions(+), 3192 deletions(-)
create mode 100644 Ink Canvas/Models/TimerState.cs
create mode 100644 Ink Canvas/Services/Classroom/TimerService.cs
create mode 100644 Ink Canvas/Services/Settings/SettingsStore.cs
create mode 100644 Ink Canvas/Services/Shell/HotkeyService.cs
create mode 100644 Ink Canvas/Services/Shell/NotificationService.cs
create mode 100644 Ink Canvas/Services/Shell/ScreenshotService.cs
create mode 100644 Ink Canvas/Services/Shell/TrayIconService.cs
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index ffacb8e9..01103cd5 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -1,6 +1,7 @@
using H.NotifyIcon;
using Ink_Canvas.Helpers;
using Ink_Canvas.Plugins;
+using Ink_Canvas.Services;
using Ink_Canvas.Properties;
using iNKORE.UI.WPF.Modern.Controls;
using Microsoft.Win32;
@@ -2260,9 +2261,74 @@ public static void RunWatchdogIfNeeded()
}
}
+ // M12:装配托盘服务的依赖委托。MainWindow/App 私有成员的访问全部经此处注入,
+ // TrayIconService 不引用 MainWindow 类型(事件出抛方向唯一:Service → App)。
+ private TrayIconService CreateTrayIconService()
+ {
+ var service = new TrayIconService(new TrayIconService.Hooks
+ {
+ GetMainWindow = () => Current.MainWindow,
+ GetTrayLeftClickAction = () => Ink_Canvas.MainWindow.Settings.Appearance.TrayLeftClickAction,
+ GetTrayRightClickAction = () => Ink_Canvas.MainWindow.Settings.Appearance.TrayRightClickAction,
+ IsAlwaysOnTop = () => Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop,
+ IsNoFocusMode = () => Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode,
+ IsFloatingBarFolded = () => ((MainWindow)Current.MainWindow).isFloatingBarFolded,
+ FoldFloatingBar = () => ((MainWindow)Current.MainWindow).FoldFloatingBar_MouseUp(new object(), null),
+ UnfoldFloatingBar = () => ((MainWindow)Current.MainWindow).UnFoldFloatingBar_MouseUp(new object(), null),
+ ResetFloatingBarPosition = () =>
+ {
+ var mw = (MainWindow)Current.MainWindow;
+ var isInPPTPresentationMode = mw.IsInPPTPresentationMode;
+ if (!mw.isFloatingBarFolded)
+ {
+ // 清空保存的状态,强制动画走默认位置分支
+ mw._userHasDraggedFloatingBar = false;
+ mw.pointDesktop = new Point(-1, -1);
+ mw.pointPPT = new Point(-1, -1);
+
+ if (!isInPPTPresentationMode) mw.PureViewboxFloatingBarMarginAnimationInDesktopMode();
+ else mw.PureViewboxFloatingBarMarginAnimationInPPTMode();
+ }
+ },
+ SetTrayTemporaryShowUntilUtc = value => Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = value,
+ UiInvoke = action => Dispatcher.BeginInvoke(action),
+ OpenSettings = () =>
+ {
+ try
+ {
+ var method = typeof(MainWindow).GetMethod("BtnSettings_Click", BindingFlags.NonPublic | BindingFlags.Instance);
+ method?.Invoke(Current.MainWindow, new object[] { null, null });
+ }
+ catch (Exception ex) { LogHelper.WriteLogToFile($"Open settings from tray failed: {ex.Message}", LogHelper.LogType.Error); }
+ },
+ MarkExitByUser = () => IsAppExitByUser = true,
+ ExitApplicationByUser = () =>
+ {
+ IsAppExitByUser = true;
+ ((MainWindow)Current.MainWindow).ExitApplication(null, null);
+ },
+ Shutdown = () => Current.Shutdown(),
+ ForceFullScreen = () =>
+ {
+ var mw = (MainWindow)Current.MainWindow;
+ Ink_Canvas.MainWindow.MoveWindow(new WindowInteropHelper(mw).Handle, 0, 0,
+ Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, true);
+ Ink_Canvas.MainWindow.ShowNewMessage($"已强制全屏化:{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}(缩放比例为{Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth}x{Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight})");
+ },
+ CheckMainWindowVisibility = () => ((MainWindow)Current.MainWindow).CheckMainWindowVisibility(),
+ GetGlobalHotkeyManager = () => typeof(MainWindow)
+ .GetField("_globalHotkeyManager", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?.GetValue(Current.MainWindow) as GlobalHotkeyManager,
+ });
+ service.TrayLeftClicked += () => PluginTrayLeftClicked?.Invoke();
+ service.TrayRightClicked += () => PluginTrayRightClicked?.Invoke();
+ return service;
+ }
+
private void App_Exit(object sender, ExitEventArgs e)
{
isAppExiting = true;
+ _trayIconService?.Dispose();
LogHelper.WriteLogToFile(
$"[Exit] 开始应用退出清理: user={IsAppExitByUser}, code={e.ApplicationExitCode}, crashAction={CrashAction}",
LogHelper.LogType.Info);
diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
index e4bc400e..c0b1be67 100644
--- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs
+++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
@@ -1,8 +1,6 @@
-using Newtonsoft.Json;
-using NHotkey.Wpf;
+using NHotkey.Wpf;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
@@ -34,8 +32,19 @@ public class GlobalHotkeyManager : IDisposable
private bool _isMouseOverWindow = false;
private System.Windows.Threading.DispatcherTimer _mousePositionTimer;
- // 配置文件路径
- private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json");
+ // M16:HotkeyConfig.json 读写与加载编排已提取至 Services.Shell.HotkeyService。
+ // 以下委托由 HotkeyService 构造时接线;原位 public 方法为转发壳,对外签名与语义不变。
+ internal Action LoadHotkeysHandler { get; set; }
+ internal Action SaveHotkeysHandler { get; set; }
+ internal Func> ConfigHotkeysProvider { get; set; }
+ internal Action RegisterDefaultsHandler { get; set; }
+
+ // M16:供 HotkeyService 读写(原私有字段,等价改写为 internal 访问器)
+ internal bool HotkeysShouldBeRegistered
+ {
+ get => _hotkeysShouldBeRegistered;
+ set => _hotkeysShouldBeRegistered = value;
+ }
#endregion
#region Constructor
@@ -48,8 +57,8 @@ public GlobalHotkeyManager(MainWindow mainWindow)
// 初始化多屏幕支持
InitializeMultiScreenSupport();
- // 启动时确保配置文件存在
- EnsureConfigFileExists();
+ // M16:启动时确保配置文件存在的职责随配置层搬入 HotkeyService 构造函数,
+ // 由主窗口在创建本管理器后立即创建服务(同一调用栈,时序不变)。
}
#endregion
@@ -300,167 +309,39 @@ public List GetRegisteredHotkeys()
///
/// 获取配置文件中的快捷键信息(不注册,仅用于显示)
+ /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
/// 配置文件中的快捷键列表
public List GetHotkeysFromConfigFile()
{
- try
- {
- if (!File.Exists(HotkeyConfigFile))
- {
- return new List();
- }
-
- // 读取配置文件内容
- string jsonContent = File.ReadAllText(HotkeyConfigFile, Encoding.UTF8);
- if (string.IsNullOrEmpty(jsonContent))
- {
- LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
- return new List();
- }
-
- // 反序列化配置
- var config = JsonConvert.DeserializeObject(jsonContent);
- if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
- {
- LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
- return new List();
- }
-
- // 转换为HotkeyInfo列表(不注册,仅用于显示)
- var hotkeyList = new List();
- foreach (var hotkeyConfig in config.Hotkeys)
- {
- hotkeyList.Add(new HotkeyInfo
- {
- Name = hotkeyConfig.Name,
- Key = hotkeyConfig.Key,
- Modifiers = hotkeyConfig.Modifiers,
- Action = null // 不设置动作,仅用于显示
- });
- }
-
- return hotkeyList;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从配置文件读取快捷键信息时出错: {ex.Message}", LogHelper.LogType.Error);
- return new List();
- }
+ return ConfigHotkeysProvider?.Invoke() ?? new List();
}
///
/// 注册默认快捷键集合
+ /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void RegisterDefaultHotkeys()
{
- try
- {
- // 开始注册默认快捷键集合
-
- // 基本操作快捷键
- RegisterHotkey("Undo", Key.Z, ModifierKeys.Control, () => _mainWindow.SymbolIconUndo_MouseUp(null, null));
- RegisterHotkey("Redo", Key.Y, ModifierKeys.Control, () => _mainWindow.SymbolIconRedo_MouseUp(null, null));
- RegisterHotkey("Clear", Key.E, ModifierKeys.Control, () => _mainWindow.SymbolIconDelete_MouseUp(null, null));
- RegisterHotkey("Paste", Key.V, ModifierKeys.Control, () => _mainWindow.HandleGlobalPaste(null, null));
-
- // 工具切换快捷键
- RegisterHotkey("SelectTool", Key.S, ModifierKeys.Alt, () => _mainWindow.SwitchToSelectFromHotkey());
- RegisterHotkey("DrawTool", Key.D, ModifierKeys.Alt, () => _mainWindow.PenIcon_Click(null, null));
- RegisterHotkey("EraserTool", Key.E, ModifierKeys.Alt, () => _mainWindow.SwitchToEraserFromHotkey());
- RegisterHotkey("BlackboardTool", Key.B, ModifierKeys.Alt, () => _mainWindow.ImageBlackboard_MouseUp(null, null));
- RegisterHotkey("QuitDrawTool", Key.Q, ModifierKeys.Alt, () => _mainWindow.KeyChangeToQuitDrawTool(null, null));
-
- // 画笔快捷键 - 使用反射访问penType字段
- RegisterHotkey("Pen1", Key.D1, ModifierKeys.Alt, () => SwitchToPenType(0));
- RegisterHotkey("Pen2", Key.D2, ModifierKeys.Alt, () => SwitchToPenType(1));
- RegisterHotkey("Pen3", Key.D3, ModifierKeys.Alt, () => SwitchToPenType(2));
- RegisterHotkey("Pen4", Key.D4, ModifierKeys.Alt, () => SwitchToPenType(3));
- RegisterHotkey("Pen5", Key.D5, ModifierKeys.Alt, () => SwitchToPenType(4));
-
- // 功能快捷键
- RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.DrawLineFromHotkey());
- RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
- RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, () => _mainWindow.OpenQuickDrawFromHotkey());
- RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
-
- // 退出快捷键
- RegisterHotkey("Exit", Key.Escape, ModifierKeys.None, () => _mainWindow.KeyExit(null, null));
-
- // 已注册默认全局快捷键集合
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"注册默认快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
- }
+ RegisterDefaultsHandler?.Invoke();
}
///
/// 从配置文件加载快捷键
+ /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void LoadHotkeysFromSettings()
{
- try
- {
- // 开始从配置文件加载快捷键设置
-
- // 检查是否应该注册快捷键
- if (!_hotkeysShouldBeRegistered)
- {
- // 当前状态不允许注册快捷键,跳过加载
- return;
- }
-
- // 如果配置文件不存在,先创建默认配置文件
- if (!File.Exists(HotkeyConfigFile))
- {
- LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
- CreateDefaultConfigFile();
- RegisterDefaultHotkeys();
- _hotkeysShouldBeRegistered = true;
- return;
- }
-
- // 尝试从配置文件加载
- if (LoadHotkeysFromConfigFile())
- {
- // 成功从配置文件加载快捷键设置
- _hotkeysShouldBeRegistered = true;
- }
- else
- {
- LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
- RegisterDefaultHotkeys();
- _hotkeysShouldBeRegistered = true;
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从设置加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
- // 出错时不自动使用默认快捷键,保持当前状态
- }
+ LoadHotkeysHandler?.Invoke();
}
///
/// 保存快捷键配置到设置
+ /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void SaveHotkeysToSettings()
{
- try
- {
-
- if (SaveHotkeysToConfigFile())
- {
- }
- else
- {
- LogHelper.WriteLogToFile("保存快捷键配置失败", LogHelper.LogType.Error);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存快捷键配置时出错: {ex.Message}", LogHelper.LogType.Error);
- }
+ SaveHotkeysHandler?.Invoke();
}
///
@@ -1114,304 +995,6 @@ private bool ShouldEnableHotkeysBasedOnContext()
}
}
- ///
- /// 切换到指定笔类型
- ///
- /// 笔类型索引
- private void SwitchToPenType(int penTypeIndex)
- {
- try
- {
- var switchMethod = _mainWindow.GetType().GetMethod(
- penTypeIndex == 2 ? "SwitchToLaserPen" : (penTypeIndex == 1 ? "SwitchToHighlighterPen" : "SwitchToDefaultPen"),
- BindingFlags.NonPublic | BindingFlags.Instance);
-
- if (switchMethod != null)
- {
- switchMethod.Invoke(_mainWindow, new object[] { null, null });
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"切换到笔类型{penTypeIndex}时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 确保配置文件存在,如果不存在则创建
- ///
- private void EnsureConfigFileExists()
- {
- try
- {
- // 如果配置文件不存在,创建默认配置文件
- if (!File.Exists(HotkeyConfigFile))
- {
- CreateDefaultConfigFile();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"确保快捷键配置文件存在时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 创建默认的快捷键配置文件
- ///
- private void CreateDefaultConfigFile()
- {
- try
- {
- // 确保配置目录存在
- string configDir = Path.GetDirectoryName(HotkeyConfigFile);
- if (!Directory.Exists(configDir))
- {
- Directory.CreateDirectory(configDir);
- }
-
- // 创建默认配置对象
- var config = new HotkeyConfig
- {
- Version = "1.0",
- LastModified = DateTime.Now,
- Hotkeys = new List()
- };
-
- // 添加默认快捷键配置
- config.Hotkeys.AddRange(new[]
- {
- new HotkeyConfigItem { Name = "Undo", Key = Key.Z, Modifiers = ModifierKeys.Control },
- new HotkeyConfigItem { Name = "Redo", Key = Key.Y, Modifiers = ModifierKeys.Control },
- new HotkeyConfigItem { Name = "Clear", Key = Key.E, Modifiers = ModifierKeys.Control },
- new HotkeyConfigItem { Name = "Paste", Key = Key.V, Modifiers = ModifierKeys.Control },
- new HotkeyConfigItem { Name = "SelectTool", Key = Key.S, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "DrawTool", Key = Key.D, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "EraserTool", Key = Key.E, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "BlackboardTool", Key = Key.B, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "QuitDrawTool", Key = Key.Q, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Pen1", Key = Key.D1, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Pen2", Key = Key.D2, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Pen3", Key = Key.D3, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Pen4", Key = Key.D4, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "QuickDraw", Key = Key.K, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
- new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
- });
-
- // 序列化为JSON
- var settings = new JsonSerializerSettings
- {
- Formatting = Formatting.Indented
- };
-
- string jsonContent = JsonConvert.SerializeObject(config, settings);
-
- // 写入配置文件
- File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
-
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"创建默认快捷键配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 从配置文件加载快捷键设置
- ///
- /// 是否加载成功
- private bool LoadHotkeysFromConfigFile()
- {
- try
- {
- if (!File.Exists(HotkeyConfigFile))
- {
- LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
- return false;
- }
-
- // 读取配置文件内容
- string jsonContent = File.ReadAllText(HotkeyConfigFile, Encoding.UTF8);
- if (string.IsNullOrEmpty(jsonContent))
- {
- LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
- return false;
- }
-
- // 反序列化配置
- var config = JsonConvert.DeserializeObject(jsonContent);
- if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
- {
- LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
- return false;
- }
-
- // 注册配置中的快捷键
- int successCount = 0;
- foreach (var hotkeyConfig in config.Hotkeys)
- {
- try
- {
- // 根据快捷键名称获取对应的动作
- var action = GetActionByName(hotkeyConfig.Name);
- if (action != null)
- {
- if (RegisterHotkey(hotkeyConfig.Name, hotkeyConfig.Key, hotkeyConfig.Modifiers, action))
- {
- successCount++;
- }
- }
- else
- {
- LogHelper.WriteLogToFile($"未找到快捷键 {hotkeyConfig.Name} 对应的动作", LogHelper.LogType.Warning);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"注册快捷键 {hotkeyConfig.Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- // 旧版 HotkeyConfig.json 无「快抽」项时补注册默认组合,避免升级后无快捷键
- if (successCount > 0 && !IsHotkeyRegistered("QuickDraw"))
- {
- var quickDrawAction = GetActionByName("QuickDraw");
- if (quickDrawAction != null && RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, quickDrawAction))
- successCount++;
- }
-
- if (successCount > 0)
- {
- _hotkeysShouldBeRegistered = true;
- }
- return successCount > 0;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从配置文件加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- ///
- /// 保存快捷键配置到配置文件
- ///
- /// 是否保存成功
- private bool SaveHotkeysToConfigFile()
- {
- try
- {
- // 确保配置目录存在
- string configDir = Path.GetDirectoryName(HotkeyConfigFile);
- if (!Directory.Exists(configDir))
- {
- Directory.CreateDirectory(configDir);
- }
-
- // 创建配置对象
- var config = new HotkeyConfig
- {
- Version = "1.0",
- LastModified = DateTime.Now,
- Hotkeys = new List()
- };
-
- // 添加所有已注册的快捷键
- foreach (var hotkey in _registeredHotkeys.Values)
- {
- config.Hotkeys.Add(new HotkeyConfigItem
- {
- Name = hotkey.Name,
- Key = hotkey.Key,
- Modifiers = hotkey.Modifiers
- });
- }
-
- // 序列化为JSON
- var settings = new JsonSerializerSettings
- {
- Formatting = Formatting.Indented
- };
-
- string jsonContent = JsonConvert.SerializeObject(config, settings);
-
- // 直接写入原文件,覆盖原有内容
- File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
-
- return true;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存快捷键配置到配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- ///
- /// 根据快捷键名称获取对应的动作
- ///
- /// 快捷键名称
- /// 对应的动作,如果不存在则返回null
- private Action GetActionByName(string hotkeyName)
- {
- try
- {
- switch (hotkeyName)
- {
- case "Undo":
- return () => _mainWindow.SymbolIconUndo_MouseUp(null, null);
- case "Redo":
- return () => _mainWindow.SymbolIconRedo_MouseUp(null, null);
- case "Clear":
- return () => _mainWindow.SymbolIconDelete_MouseUp(null, null);
- case "Paste":
- return () => _mainWindow.HandleGlobalPaste(null, null);
- case "SelectTool":
- return () => _mainWindow.SwitchToSelectFromHotkey();
- case "DrawTool":
- return () => _mainWindow.PenIcon_Click(null, null);
- case "EraserTool":
- return () => _mainWindow.SwitchToEraserFromHotkey();
- case "BlackboardTool":
- return () => _mainWindow.ImageBlackboard_MouseUp(null, null);
- case "QuitDrawTool":
- return () => _mainWindow.KeyChangeToQuitDrawTool(null, null);
- case "Pen1":
- return () => SwitchToPenType(0);
- case "Pen2":
- return () => SwitchToPenType(1);
- case "Pen3":
- return () => SwitchToPenType(2);
- case "Pen4":
- return () => SwitchToPenType(3);
- case "Pen5":
- return () => SwitchToPenType(4);
- case "DrawLine":
- return () => _mainWindow.DrawLineFromHotkey();
- case "Screenshot":
- return () => _mainWindow.SaveScreenShotToDesktop();
- case "QuickDraw":
- return () => _mainWindow.OpenQuickDrawFromHotkey();
- case "Hide":
- return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
- case "Exit":
- return () => _mainWindow.KeyExit(null, null);
- default:
- LogHelper.WriteLogToFile($"未知的快捷键名称: {hotkeyName}", LogHelper.LogType.Warning);
- return null;
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取快捷键 {hotkeyName} 对应动作时出错: {ex.Message}", LogHelper.LogType.Error);
- return null;
- }
- }
-
///
/// 检查当前是否处于鼠标模式(选择模式)
///
@@ -1553,26 +1136,6 @@ public override string ToString()
return $"{modifiersText}{Key}";
}
}
-
- ///
- /// 快捷键配置类
- ///
- private class HotkeyConfig
- {
- public string Version { get; set; }
- public DateTime LastModified { get; set; }
- public List Hotkeys { get; set; }
- }
-
- ///
- /// 快捷键配置项类
- ///
- private class HotkeyConfigItem
- {
- public string Name { get; set; }
- public Key Key { get; set; }
- public ModifierKeys Modifiers { get; set; }
- }
#endregion
}
}
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index 2f44a9c1..793ce2c9 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -3,6 +3,7 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Models;
using Ink_Canvas.Properties;
+using Ink_Canvas.Services;
using Ink_Canvas.Windows;
using Ink_Canvas.Windows.SettingsViews;
using Ink_Canvas.Windows.SettingsViews.Helpers;
@@ -53,6 +54,9 @@ public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
private GlobalHotkeyManager _globalHotkeyManager;
internal GlobalHotkeyManager GlobalHotkeyManagerInstance => _globalHotkeyManager;
+ // 全局热键配置服务(M16):HotkeyConfig.json 读写与加载编排,随管理器一并创建
+ private Services.Shell.HotkeyService _hotkeyService;
+
// PPT PageUp/PageDown 低级键盘钩子
private PPTPageKeyHook _pptPageKeyHook;
@@ -60,8 +64,8 @@ public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
private InkFadeManager _inkFadeManager;
// 暴露给插件墨迹特效服务的入口(未初始化时为 null)
internal InkFadeManager InkFadeManagerInstance => _inkFadeManager;
- private readonly CancellationTokenSource _notificationProviderCancellation = new CancellationTokenSource();
- private AnnouncementService _announcementService;
+ private readonly NotificationService _notificationService;
+ private readonly Services.Shell.ScreenshotService _screenshotService;
// 悬浮窗拦截管理器
public FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
@@ -535,6 +539,9 @@ 处于 PPT 放映内:BtnPPTSlideShowEnd.Visibility
*/
InitializeComponent();
+ _notificationService = CreateNotificationService();
+ _screenshotService = CreateScreenshotService();
+
if (BorderTools.Child is FrameworkElement btChild) btChild.Visibility = Visibility.Collapsed;
if (BorderDrawShape.Child is FrameworkElement bdsChild) bdsChild.Visibility = Visibility.Collapsed;
if (BoardBorderToolsPopup.Child is FrameworkElement bbtpChild) bbtpChild.Visibility = Visibility.Collapsed;
@@ -830,6 +837,9 @@ 处于 PPT 放映内:BtnPPTSlideShowEnd.Visibility
Deactivated += Window_Deactivated;
Dispatcher.BeginInvoke(new Action(CheckAndShowOobe), DispatcherPriority.Loaded);
+
+ // M11:设置存储服务(寄生提取),供各 partial 中的转发壳使用
+ _settingsStore = new Services.SettingsStore();
}
///
@@ -2756,6 +2766,52 @@ private void Window_Activated(object sender, EventArgs e)
_popupManager?.OnOwnerActivated();
}
+ ///
+ /// 装配通知服务(M13):注入 x:Name 控件强类型引用与访问主窗口私有状态的委托,
+ /// 服务本身不引用 MainWindow 类型、不直接引用 UI 线程调度器。
+ ///
+ private NotificationService CreateNotificationService()
+ {
+ return new NotificationService(
+ DynamicNotification,
+ TextBlockNotice,
+ GridNotifications,
+ ViewboxFloatingBar,
+ new NotificationService.Hooks
+ {
+ GetSettings = () => Settings,
+ IsCurrentThemeDark = IsCurrentThemeDark,
+ IsInPptPresentationMode = () => IsInPPTPresentationMode,
+ GetCurrentMode = () => currentMode,
+ IsFloatingBarFolded = () => isFloatingBarFolded,
+ IsFloatingBarChangingHideMode = () => isFloatingBarChangingHideMode,
+ GetHostWindow = () => this,
+ UiInvoke = a => Dispatcher.BeginInvoke(a),
+ UiInvokeContextIdle = a => Dispatcher.BeginInvoke(a, DispatcherPriority.ContextIdle)
+ });
+ }
+
+ ///
+ /// 装配截图服务(M15):注入设置读取、墨迹快照、白板页索引、DPI 缩放、通知与
+ /// UI 线程调度委托,服务本身不引用 MainWindow 类型、不直接引用 UI 线程调度器。
+ /// 通知文本(MainWindowStrings 资源)保留在主窗口侧格式化,服务只接收格式化入口。
+ ///
+ private Services.Shell.ScreenshotService CreateScreenshotService()
+ {
+ return new Services.Shell.ScreenshotService(
+ new Services.Shell.ScreenshotService.Hooks
+ {
+ GetSettings = () => Settings,
+ GetInkStrokeCount = () => inkCanvas?.Strokes?.Count,
+ CloneInkStrokes = () => inkCanvas.Strokes.Clone(),
+ GetCurrentWhiteboardIndex = () => CurrentWhiteboardIndex,
+ GetDpiScale = GetDpiScale,
+ ShowScreenshotSaveSuccess = path => ShowNotification(string.Format(Properties.MainWindowStrings.Main_Screenshot_SaveSuccess, path)),
+ UiInvoke = a => Dispatcher.Invoke(a),
+ SaveInkStrokesAtScreenshot = () => SaveInkCanvasStrokes(false)
+ });
+ }
+
private void InitializeNotificationAndAutomationForStartup()
{
try
@@ -3058,6 +3114,8 @@ internal GlobalHotkeyManager EnsureGlobalHotkeyManagerCreated()
if (_globalHotkeyManager == null)
{
_globalHotkeyManager = new GlobalHotkeyManager(this);
+ // M16:热键配置服务与管理器同栈创建,内置热键回调以字典注入(回调本体在 MW_Hotkeys.cs)
+ _hotkeyService = new Services.Shell.HotkeyService(_globalHotkeyManager, BuildBuiltinHotkeyActions());
// 启动时加载快捷键,但默认为鼠标模式,禁用快捷键以放行键盘操作
_globalHotkeyManager.EnableHotkeyRegistration();
// 启动时默认为鼠标模式,禁用快捷键
diff --git a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
index a444b795..bc198c17 100644
--- a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
@@ -1,5 +1,6 @@
using Ink_Canvas.Helpers;
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Interop;
@@ -472,5 +473,37 @@ private void KeyHide(object sender, ExecutedRoutedEventArgs e)
{
SymbolIconEmoji_MouseUp(null, null);
}
+
+ ///
+ /// 构建内置热键「名称 → 触发回调」字典(M16:HotkeyService 构造注入,
+ /// 替代原 GlobalHotkeyManager.GetActionByName 的 switch 硬编码)。
+ /// 键集合与原 switch 完全一致;Pen1-5 的笔型映射复刻原 SwitchToPenType:
+ /// 0/3/4→默认笔、1→荧光笔、2→激光笔(原为反射调用,此处直接调用,等价)。
+ ///
+ private Dictionary BuildBuiltinHotkeyActions()
+ {
+ return new Dictionary(StringComparer.Ordinal)
+ {
+ { "Undo", () => SymbolIconUndo_MouseUp(null, null) },
+ { "Redo", () => SymbolIconRedo_MouseUp(null, null) },
+ { "Clear", () => SymbolIconDelete_MouseUp(null, null) },
+ { "Paste", () => HandleGlobalPaste(null, null) },
+ { "SelectTool", () => SwitchToSelectFromHotkey() },
+ { "DrawTool", () => PenIcon_Click(null, null) },
+ { "EraserTool", () => SwitchToEraserFromHotkey() },
+ { "BlackboardTool", () => ImageBlackboard_MouseUp(null, null) },
+ { "QuitDrawTool", () => KeyChangeToQuitDrawTool(null, null) },
+ { "Pen1", () => SwitchToDefaultPen(null, null) },
+ { "Pen2", () => SwitchToHighlighterPen(null, null) },
+ { "Pen3", () => SwitchToLaserPen(null, null) },
+ { "Pen4", () => SwitchToDefaultPen(null, null) },
+ { "Pen5", () => SwitchToDefaultPen(null, null) },
+ { "DrawLine", () => DrawLineFromHotkey() },
+ { "Screenshot", () => SaveScreenShotToDesktop() },
+ { "QuickDraw", () => OpenQuickDrawFromHotkey() },
+ { "Hide", () => SymbolIconEmoji_MouseUp(null, null) },
+ { "Exit", () => KeyExit(null, null) },
+ };
+ }
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
index 33fc5026..ecdcad2f 100644
--- a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
@@ -1,4 +1,5 @@
using Ink_Canvas.Helpers;
+using Ink_Canvas.Services.Shell;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -292,56 +293,12 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
}
///
- /// 截取指定屏幕区域
+ /// 截取指定屏幕区域。
+ /// 壳:本体已搬入 (M15)。
///
/// 要截取的屏幕区域
/// 截取的位图
- ///
- /// 该方法会:
- /// 1. 确保区域在有效范围内
- /// 2. 调整区域边界,确保不超出屏幕范围
- /// 3. 创建支持透明度的位图
- /// 4. 设置高质量渲染
- /// 5. 截取屏幕区域
- ///
- private Bitmap CaptureScreenArea(Rectangle area)
- {
- try
- {
- // 确保区域在有效范围内
- var virtualScreen = SystemInformation.VirtualScreen;
-
- // 调整区域边界,确保不超出屏幕范围
- int x = Math.Max(area.X, virtualScreen.X);
- int y = Math.Max(area.Y, virtualScreen.Y);
- int right = Math.Min(area.Right, virtualScreen.Right);
- int bottom = Math.Min(area.Bottom, virtualScreen.Bottom);
-
- int width = Math.Max(1, right - x);
- int height = Math.Max(1, bottom - y);
-
- // 创建支持透明度的位图
- var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
- using (var graphics = Graphics.FromImage(bitmap))
- {
- // 设置高质量渲染
- graphics.CompositingQuality = CompositingQuality.HighQuality;
- graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphics.SmoothingMode = SmoothingMode.HighQuality;
- graphics.CompositingMode = CompositingMode.SourceOver;
-
- // 截取屏幕区域
- graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
- }
-
- return bitmap;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"截取屏幕区域失败: {ex.Message}", LogHelper.LogType.Error);
- return null;
- }
- }
+ private Bitmap CaptureScreenArea(Rectangle area) => ScreenshotService.CaptureScreenArea(area);
private BitmapSource CreateInkOverlayPreviewBitmapSource()
{
@@ -402,82 +359,19 @@ private BitmapSource CreateInkOverlayPreviewBitmapSource()
}
}
+ ///
+ /// 在捕获位图上叠加墨迹预览。
+ /// 壳:本体已搬入 (M15)。
+ ///
private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource)
- {
- if (capturedBitmap == null || inkOverlayBitmapSource == null)
- {
- return capturedBitmap;
- }
-
- try
- {
- var virtualScreen = SystemInformation.VirtualScreen;
- var sourceRect = new Rectangle(
- captureArea.X - virtualScreen.X,
- captureArea.Y - virtualScreen.Y,
- captureArea.Width,
- captureArea.Height);
-
- sourceRect.Intersect(new Rectangle(0, 0, inkOverlayBitmapSource.PixelWidth, inkOverlayBitmapSource.PixelHeight));
- if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
- {
- return capturedBitmap;
- }
-
- using (var inkOverlayBitmap = ConvertBitmapSourceToBitmap(inkOverlayBitmapSource))
- {
- if (inkOverlayBitmap == null)
- {
- return capturedBitmap;
- }
-
- Bitmap resultBitmap = null;
- try
- {
- resultBitmap = new Bitmap(capturedBitmap.Width, capturedBitmap.Height, PixelFormat.Format32bppArgb);
- using (var g = Graphics.FromImage(resultBitmap))
- {
- g.DrawImage(capturedBitmap, 0, 0, capturedBitmap.Width, capturedBitmap.Height);
-
- var targetRect = new Rectangle(0, 0, Math.Min(sourceRect.Width, capturedBitmap.Width), Math.Min(sourceRect.Height, capturedBitmap.Height));
- g.DrawImage(inkOverlayBitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
- }
-
- return resultBitmap;
- }
- catch
- {
- resultBitmap?.Dispose();
- throw;
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"叠加截图墨迹失败: {ex.Message}", LogHelper.LogType.Warning);
- return capturedBitmap;
- }
- }
+ => ScreenshotService.OverlayInkOnCapturedBitmap(capturedBitmap, captureArea, inkOverlayBitmapSource);
+ ///
+ /// 将 WPF BitmapSource 转换为 System.Drawing.Bitmap。
+ /// 壳:本体已搬入 (M15)。
+ ///
private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource)
- {
- if (bitmapSource == null)
- {
- return null;
- }
-
- using (var memoryStream = new MemoryStream())
- {
- var encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
- encoder.Save(memoryStream);
- memoryStream.Position = 0;
- using (var tempBitmap = new Bitmap(memoryStream))
- {
- return new Bitmap(tempBitmap);
- }
- }
- }
+ => ScreenshotService.ConvertBitmapSourceToBitmap(bitmapSource);
///
/// 将截图插入到画布
@@ -845,302 +739,16 @@ private void CenterAndScaleScreenshot(Image image)
/// 11. 重置裁剪区域,确保后续操作不受影响
///
private Bitmap ApplyShapeMask(Bitmap bitmap, List path, Rectangle area)
- {
- try
- {
- // 验证路径参数
- if (path == null || path.Count < 3)
- {
- LogHelper.WriteLogToFile("路径点数不足,无法应用形状遮罩", LogHelper.LogType.Warning);
- return bitmap;
- }
-
- // 获取DPI缩放比例
- var dpiScale = GetDpiScale();
- var virtualScreen = SystemInformation.VirtualScreen;
-
- // 创建结果位图,确保支持透明度
- var resultBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
-
- // 首先将整个位图设置为透明
- using (var resultGraphics = Graphics.FromImage(resultBitmap))
- {
- // 清除位图,设置为完全透明
- resultGraphics.Clear(Color.Transparent);
-
- // 设置高质量渲染
- resultGraphics.SmoothingMode = SmoothingMode.AntiAlias;
- resultGraphics.CompositingQuality = CompositingQuality.HighQuality;
- resultGraphics.CompositingMode = CompositingMode.SourceOver;
-
- // 创建路径
- using (var pathGraphics = new GraphicsPath())
- {
- // 转换WPF坐标到GDI+坐标,考虑DPI缩放和屏幕偏移
- var points = new PointF[path.Count];
- for (int i = 0; i < path.Count; i++)
- {
- // 将WPF坐标转换为实际屏幕坐标,然后相对于截图区域计算偏移
- double screenX = (path[i].X * dpiScale) + virtualScreen.Left;
- double screenY = (path[i].Y * dpiScale) + virtualScreen.Top;
-
- // 计算相对于截图区域的坐标
- float relativeX = (float)(screenX - area.X);
- float relativeY = (float)(screenY - area.Y);
-
- // 确保坐标在有效范围内
- relativeX = Math.Max(0, Math.Min(relativeX, bitmap.Width - 1));
- relativeY = Math.Max(0, Math.Min(relativeY, bitmap.Height - 1));
-
- points[i] = new PointF(relativeX, relativeY);
- }
-
- // 添加路径 - 使用FillMode.Winding确保路径正确填充
- pathGraphics.FillMode = FillMode.Winding;
- pathGraphics.AddPolygon(points);
-
- // 验证路径是否有效
- if (!pathGraphics.IsVisible(0, 0) && pathGraphics.GetBounds().Width > 0 && pathGraphics.GetBounds().Height > 0)
- {
- // 设置裁剪区域为路径内部
- resultGraphics.SetClip(pathGraphics);
-
- // 在裁剪区域内绘制原始图像
- resultGraphics.DrawImage(bitmap, 0, 0);
-
- // 重置裁剪区域,确保后续操作不受影响
- resultGraphics.ResetClip();
- }
- else
- {
- LogHelper.WriteLogToFile("生成的路径无效,返回透明图像", LogHelper.LogType.Warning);
- // 如果路径无效,返回透明图像
- return resultBitmap;
- }
- }
- }
-
- return resultBitmap;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"应用形状遮罩失败: {ex.Message}", LogHelper.LogType.Error);
- return bitmap;
- }
- }
+ => _screenshotService.ApplyShapeMask(bitmap, path, area);
///
- /// 将System.Drawing.Bitmap转换为WPF BitmapSource
+ /// 将System.Drawing.Bitmap转换为WPF BitmapSource。
+ /// 壳:本体(含备用/最简两条降级路径)已搬入 (M15)。
///
/// 要转换的位图
/// 转换后的BitmapSource
- ///
- /// 该方法会:
- /// 1. 验证位图有效性
- /// 2. 验证位图尺寸
- /// 3. 使用更安全的方法转换位图
- /// 4. 根据像素格式选择合适的WPF像素格式
- /// 5. 创建BitmapSource
- /// 6. 冻结BitmapSource以提高性能
- /// 7. 如果转换失败,尝试使用备用方法
- ///
private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
- {
- try
- {
- // 验证位图有效性
- if (bitmap == null)
- return null;
-
- // 验证位图尺寸
- if (bitmap.Width <= 0 || bitmap.Height <= 0)
- return null;
-
- // 使用更安全的方法转换位图
- var bitmapData = bitmap.LockBits(
- new Rectangle(0, 0, bitmap.Width, bitmap.Height),
- ImageLockMode.ReadOnly,
- bitmap.PixelFormat);
-
- try
- {
- // 根据像素格式选择合适的WPF像素格式
- System.Windows.Media.PixelFormat wpfPixelFormat;
- switch (bitmap.PixelFormat)
- {
- case PixelFormat.Format24bppRgb:
- wpfPixelFormat = PixelFormats.Bgr24;
- break;
- case PixelFormat.Format32bppArgb:
- wpfPixelFormat = PixelFormats.Bgra32;
- break;
- case PixelFormat.Format32bppRgb:
- wpfPixelFormat = PixelFormats.Bgr32;
- break;
- default:
- wpfPixelFormat = PixelFormats.Bgr24;
- break;
- }
-
- var bitmapSource = BitmapSource.Create(
- bitmapData.Width,
- bitmapData.Height,
- bitmap.HorizontalResolution,
- bitmap.VerticalResolution,
- wpfPixelFormat,
- null,
- bitmapData.Scan0,
- bitmapData.Stride * bitmapData.Height,
- bitmapData.Stride);
-
- bitmapSource.Freeze();
- return bitmapSource;
- }
- finally
- {
- bitmap.UnlockBits(bitmapData);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
-
- // 尝试使用备用方法:内存流转换
- try
- {
- return ConvertBitmapToBitmapSourceFallback(bitmap);
- }
- catch (Exception fallbackEx)
- {
- LogHelper.WriteLogToFile($"备用转换方法也失败: {fallbackEx.Message}", LogHelper.LogType.Error);
-
- // 最后尝试:使用最简单的转换方法
- try
- {
- return ConvertBitmapToBitmapSourceSimple(bitmap);
- }
- catch (Exception simpleEx)
- {
- LogHelper.WriteLogToFile($"简单转换方法也失败: {simpleEx.Message}", LogHelper.LogType.Error);
- throw;
- }
- }
- }
- }
-
- ///
- /// 备用的位图转换方法(使用内存流)
- ///
- /// 要转换的位图
- /// 转换后的BitmapSource
- ///
- /// 该方法会:
- /// 1. 验证位图有效性
- /// 2. 创建一个新的位图,确保格式正确
- /// 3. 在内存流中保存为PNG格式
- /// 4. 创建BitmapImage并加载内存流中的数据
- /// 5. 冻结BitmapImage以提高性能
- ///
- private BitmapSource ConvertBitmapToBitmapSourceFallback(Bitmap bitmap)
- {
- try
- {
- // 验证位图有效性
- if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
- return null;
-
- // 创建一个新的位图,确保保留Alpha通道
- using (var convertedBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb))
- {
- using (var graphics = Graphics.FromImage(convertedBitmap))
- {
- graphics.CompositingMode = CompositingMode.SourceCopy;
- graphics.DrawImage(bitmap, 0, 0);
- }
-
- using (var memory = new MemoryStream())
- {
- convertedBitmap.Save(memory, ImageFormat.Png);
- memory.Position = 0;
-
- var bitmapImage = new BitmapImage();
- bitmapImage.BeginInit();
- bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
- bitmapImage.StreamSource = memory;
- bitmapImage.EndInit();
- bitmapImage.Freeze();
-
- return bitmapImage;
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"备用转换方法失败: {ex.Message}", LogHelper.LogType.Error);
- throw;
- }
- }
-
- ///
- /// 最简单的位图转换方法
- ///
- /// 要转换的位图
- /// 转换后的BitmapSource
- ///
- /// 该方法会:
- /// 1. 验证位图有效性
- /// 2. 使用最基础的方法:直接保存为PNG然后加载
- /// 3. 创建临时文件
- /// 4. 将位图保存为PNG格式到临时文件
- /// 5. 创建BitmapImage并加载临时文件
- /// 6. 冻结BitmapImage以提高性能
- /// 7. 清理临时文件
- ///
- private BitmapSource ConvertBitmapToBitmapSourceSimple(Bitmap bitmap)
- {
- try
- {
- if (bitmap == null)
- return null;
-
- // 使用最基础的方法:直接保存为PNG然后加载
- var tempFile = Path.GetTempFileName() + ".png";
-
- try
- {
- bitmap.Save(tempFile, ImageFormat.Png);
-
- var bitmapImage = new BitmapImage();
- bitmapImage.BeginInit();
- bitmapImage.UriSource = new Uri(tempFile);
- bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
- bitmapImage.EndInit();
- bitmapImage.Freeze();
-
- return bitmapImage;
- }
- finally
- {
- // 清理临时文件
- try
- {
- if (File.Exists(tempFile))
- {
- File.Delete(tempFile);
- }
- }
- catch (Exception deleteEx)
- {
- LogHelper.WriteLogToFile($"删除临时文件失败: {deleteEx.Message}", LogHelper.LogType.Warning);
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"简单转换方法失败: {ex.Message}", LogHelper.LogType.Error);
- throw;
- }
- }
+ => ScreenshotService.ConvertBitmapToBitmapSource(bitmap);
///
/// 获取DPI缩放比例
diff --git a/Ink Canvas/MainWindow_cs/MW_Notification.cs b/Ink Canvas/MainWindow_cs/MW_Notification.cs
index 334008f1..d99a3da2 100644
--- a/Ink Canvas/MainWindow_cs/MW_Notification.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Notification.cs
@@ -1,21 +1,15 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Models;
-using Ink_Canvas.Properties;
-using Ink_Canvas.Windows;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Threading;
namespace Ink_Canvas
{
+ ///
+ /// 通知转发壳(M13):实现已整体搬至 ,
+ /// 本文件仅保留对外入口签名,方法体为一行转发。
+ /// 通知队列与去重逻辑由 (纯逻辑静态类)承载。
+ ///
public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
{
- private int lastNotificationShowTime;
- private int notificationShowTime = 2500;
- private bool _startupUnreadNotificationShown;
-
public static void ShowNewMessage(string notice, bool isShowImmediately = true)
{
NotificationCenterService.EnqueueText(notice, NotificationMessageLevel.Normal, 3);
@@ -23,289 +17,22 @@ public static void ShowNewMessage(string notice, bool isShowImmediately = true)
public void ShowNotification(string notice, bool isShowImmediately = true)
{
- NotificationCenterService.EnqueueText(notice, NotificationMessageLevel.Normal, Math.Max(1, notificationShowTime / 1000));
+ _notificationService.ShowText(notice);
}
public void ShowPPTModePromptNotification()
{
- if (Settings?.PowerPointSettings?.ShowPPTModePrompt != true) return;
-
- NotificationCenterService.Enqueue(new NotificationMessage
- {
- Id = "ppt-mode-prompt-" + Guid.NewGuid().ToString("N"),
- Type = NotificationMessageType.Reminder,
- Level = NotificationMessageLevel.Normal,
- Title = PPTStrings.PPT_ModePrompt_Title,
- Summary = PPTStrings.PPT_ModePrompt_Message,
- Icon = "Info",
- DisplaySeconds = 4,
- Priority = 20,
- Source = "ppt-mode-prompt",
- ProviderId = "local"
- });
+ _notificationService.ShowPptModePromptNotification();
}
private void InitializeNotificationProviders()
{
- if (DynamicNotification != null)
- {
- DynamicNotification.Closed -= DynamicNotification_Closed;
- DynamicNotification.Closed += DynamicNotification_Closed;
- }
-
- NotificationCenterService.NotificationRequested -= NotificationCenterService_NotificationRequested;
- NotificationCenterService.NotificationRequested += NotificationCenterService_NotificationRequested;
-
- if (_announcementService == null && Settings?.Notification?.IsAnnouncementEnabled == true)
- {
- _announcementService = new AnnouncementService(Settings);
-
- AnnouncementService.UnreadCountChanged -= OnAnnouncementUnreadCountChanged;
- AnnouncementService.UnreadCountChanged += OnAnnouncementUnreadCountChanged;
-
- Dispatcher.BeginInvoke(new Action(async () =>
- {
- try
- {
- await Task.Delay(TimeSpan.FromSeconds(3), _notificationProviderCancellation.Token);
- await _announcementService.StartAsync(_notificationProviderCancellation.Token);
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"公告通知提供商启动失败: {ex.Message}", LogHelper.LogType.Warning);
- }
- }), DispatcherPriority.ContextIdle);
- }
-
- LogHelper.WriteLogToFile(
- $"[Notification] 通知提供商已初始化: 公告已启用={Settings?.Notification?.IsAnnouncementEnabled == true}, 公告服务实例={_announcementService != null}",
- LogHelper.LogType.Info);
+ _notificationService.InitializeProviders();
}
- ///
- /// 拆除通知控件上挂的插件 Action 回调。插件热重载时由
- /// 调用:当前显示的通知若来自该插件,currentMessage.Action 直接指向插件 ALC,
- /// 留着会阻止热重载时被回收。
- ///
internal void DetachPluginNotificationAction(string pluginId)
{
- if (string.IsNullOrEmpty(pluginId)) return;
- try
- {
- DynamicNotification?.DetachPluginActionIfMatches(pluginId);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"摘除插件通知 Action 失败: {ex.Message}", LogHelper.LogType.Warning);
- }
- }
-
- private void NotificationCenterService_NotificationRequested(NotificationMessage message)
- {
- Dispatcher.BeginInvoke(new Action(() =>
- {
- try
- {
- if (IsNotificationSuppressedByDictationDoNotDisturb())
- {
- NotificationCenterService.NotifyCurrentClosed();
- return;
- }
-
- if (Settings?.Notification?.IsWindowsToastEnabled == true)
- {
- WindowsNotificationHelper.ShowToast(message);
- }
-
- if (Settings?.Notification?.IsDynamicNotificationEnabled == true && DynamicNotification != null)
- {
- DynamicNotification.RefreshTheme(IsCurrentThemeDark());
- ApplyDynamicNotificationPlacement(message);
- DynamicNotification.Show(message);
- }
- else
- {
- ShowLegacyNotification(message.Title, message.DisplaySeconds);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"灵动通知显示失败: {ex.Message}", LogHelper.LogType.Error);
- NotificationCenterService.NotifyCurrentClosed();
- }
- }));
- }
-
- private bool IsNotificationSuppressedByDictationDoNotDisturb()
- {
- var notification = Settings?.Notification;
- if (notification?.IsDictationDoNotDisturbEnabled != true) return false;
-
- if (notification.IsDictationDoNotDisturbInPPTEnabled && IsInPPTPresentationMode)
- {
- return true;
- }
-
- return notification.IsDictationDoNotDisturbInWhiteboardEnabled && currentMode == 1;
- }
-
- private void DynamicNotification_Closed(object sender, EventArgs e)
- {
- NotificationCenterService.NotifyCurrentClosed();
- }
-
- private void ApplyDynamicNotificationPlacement(NotificationMessage message = null)
- {
- if (DynamicNotification == null) return;
-
- DynamicNotification.HorizontalAlignment = HorizontalAlignment.Center;
- DynamicNotification.VerticalAlignment = VerticalAlignment.Top;
- DynamicNotification.Margin = new Thickness(0);
-
- if (message?.Source == "ppt-mode-prompt")
- {
- ApplyDynamicNotificationFloatingBarPlacement();
- return;
- }
-
- switch (Settings?.Notification?.Placement)
- {
- case "TopLeft":
- DynamicNotification.HorizontalAlignment = HorizontalAlignment.Left;
- DynamicNotification.Margin = new Thickness(16, 0, 0, 0);
- break;
- case "TopRight":
- DynamicNotification.HorizontalAlignment = HorizontalAlignment.Right;
- DynamicNotification.Margin = new Thickness(0, 0, 16, 0);
- break;
- case "FloatingBarAbove":
- ApplyDynamicNotificationFloatingBarPlacement();
- break;
- }
- }
-
- private void ApplyDynamicNotificationFloatingBarPlacement()
- {
- // 收纳或收纳动画期间浮动栏已移出可用区域,保留默认的顶部居中位置,避免通知跟随到屏幕外。
- if (DynamicNotification == null || ViewboxFloatingBar == null ||
- ViewboxFloatingBar.Visibility != Visibility.Visible ||
- isFloatingBarFolded || isFloatingBarChangingHideMode)
- {
- return;
- }
-
- try
- {
- var position = ViewboxFloatingBar.TransformToAncestor(this).Transform(new Point(0, 0));
- double notificationWidth = DynamicNotification.ActualWidth > 0 ? DynamicNotification.ActualWidth : DynamicNotification.Width;
- double notificationHeight = DynamicNotification.ActualHeight > 0 ? DynamicNotification.ActualHeight : 72;
- double floatingBarWidth = ViewboxFloatingBar.ActualWidth;
- double left = position.X + floatingBarWidth / 2 - notificationWidth / 2;
- double top = position.Y - notificationHeight - 12;
-
- left = Math.Max(12, Math.Min(ActualWidth - notificationWidth - 12, left));
- top = Math.Max(12, top);
-
- DynamicNotification.HorizontalAlignment = HorizontalAlignment.Left;
- DynamicNotification.VerticalAlignment = VerticalAlignment.Top;
- DynamicNotification.Margin = new Thickness(left, top, 0, 0);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Notification] 计算通知悬浮栏位置失败: {ex.Message}", LogHelper.LogType.Info);
- }
- }
-
- private void OnAnnouncementUnreadCountChanged()
- {
- if (_startupUnreadNotificationShown) return;
-
- Dispatcher.BeginInvoke(new Action(() =>
- {
- if (_startupUnreadNotificationShown) return;
- ShowStartupUnreadNotification();
- }));
- }
-
- private void ShowStartupUnreadNotification()
- {
- try
- {
- if (_startupUnreadNotificationShown) return;
- _startupUnreadNotificationShown = true;
-
- var count = AnnouncementService.GetUnreadCount(Settings);
- if (count <= 0) return;
-
- NotificationCenterService.Enqueue(new NotificationMessage
- {
- Id = "startup-unread-announcements",
- Type = NotificationMessageType.Reminder,
- Level = NotificationMessageLevel.Normal,
- Title = AnnouncementStrings.StartupUnreadTitle,
- Summary = string.Format(AnnouncementStrings.StartupUnreadSummary, count),
- ActionText = AnnouncementStrings.StartupUnreadAction,
- Icon = "Info",
- DisplaySeconds = 8,
- Priority = 50,
- Source = "startup-unread",
- ProviderId = "announcement",
- Action = () =>
- {
- try
- {
- var window = new AnnouncementCenterWindow { Owner = this };
- window.Show();
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"打开公告中心失败: {ex.Message}", LogHelper.LogType.Warning);
- }
- }
- });
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"启动未读公告通知失败: {ex.Message}", LogHelper.LogType.Warning);
- }
- }
-
- private void ShowLegacyNotification(string notice, int displaySeconds)
- {
- try
- {
- if (TextBlockNotice == null || GridNotifications == null)
- {
- NotificationCenterService.NotifyCurrentClosed();
- return;
- }
-
- lastNotificationShowTime = Environment.TickCount;
- notificationShowTime = Math.Max(1, displaySeconds) * 1000;
- TextBlockNotice.Text = notice;
- AnimationsHelper.ShowWithSlideFromBottomAndFade(GridNotifications);
-
- new Thread(() =>
- {
- Thread.Sleep(notificationShowTime + 300);
- if (Environment.TickCount - lastNotificationShowTime >= notificationShowTime)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- AnimationsHelper.HideWithSlideAndFade(GridNotifications);
- NotificationCenterService.NotifyCurrentClosed();
- });
- }
- }).Start();
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"ShowNotification 异常: {ex.Message}", LogHelper.LogType.Error);
- NotificationCenterService.NotifyCurrentClosed();
- }
+ _notificationService.DetachPluginNotificationAction(pluginId);
}
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
index 794a5d15..90884248 100644
--- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
@@ -1,14 +1,9 @@
-using Ink_Canvas.Helpers;
+using Ink_Canvas.Services.Shell;
using System;
using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
using System.IO;
-using System.Linq;
using System.Threading.Tasks;
using System.Windows;
-using System.Windows.Forms;
-using System.Windows.Ink;
using System.Windows.Media.Imaging;
namespace Ink_Canvas
@@ -18,147 +13,28 @@ public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
///
/// 在切页/加页场景下使用:先捕获当前画面到内存并克隆墨迹,然后立即返回;截图与墨迹保存在后台异步执行,不阻塞切页。
/// 调用方应在调用本方法后立即执行 SaveStrokes、ClearStrokes、切页、RestoreStrokes 等逻辑。
+ /// 壳:本体已搬入 (M15)。
///
/// 是否隐藏保存成功通知
/// 截图文件名(可选)
private void CaptureAndEnqueueScreenshotSave(bool isHideNotification, string fileName = null)
- {
- var savePath = Settings.Automation.IsSaveScreenshotsInDateFolders
- ? GetDateFolderPath(fileName)
- : GetDefaultFolderPath();
-
- System.Drawing.Bitmap bitmap = null;
- StrokeCollection strokesToSave = null;
- int pageIndexForStrokes = 0;
- string strokeSavePath = null;
-
- try
- {
- bitmap = CaptureScreenshotToBitmap();
- if (bitmap == null) return;
-
- if (Settings.Automation.IsAutoSaveStrokesAtScreenshot && inkCanvas.Strokes.Count > 0)
- {
- strokesToSave = inkCanvas.Strokes.Clone();
- pageIndexForStrokes = CurrentWhiteboardIndex;
- var basePath = Settings.Automation.AutoSavedStrokesLocation
- + @"\Auto Saved - BlackBoard Strokes";
- if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
- string stem;
- if (Settings.Automation.IsUseCustomSaveFileName)
- {
- stem = SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
- new SaveFileNameContext
- {
- Mode = "BlackBoard",
- Type = "Auto",
- Page = pageIndexForStrokes,
- Count = strokesToSave.Count
- });
- }
- else
- {
- stem = $"{DateTime.Now:yyyy-MM-dd HH-mm-ss-fff} Page-{pageIndexForStrokes} StrokesCount-{strokesToSave.Count}";
- }
- strokeSavePath = Path.Combine(basePath, stem + ".icstk");
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"CaptureAndEnqueueScreenshotSave 捕获失败: {ex}", LogHelper.LogType.Error);
- bitmap?.Dispose();
- return;
- }
-
- var bitmapToSave = bitmap;
- var path = savePath;
- var hideNotification = isHideNotification;
-
- _ = Task.Run(async () =>
- {
- try
- {
- if (bitmapToSave != null)
- {
- var directory = Path.GetDirectoryName(path);
- if (!Directory.Exists(directory))
- Directory.CreateDirectory(directory);
- Helpers.ScreenshotImageSaveHelper.Save(bitmapToSave, path);
- bitmapToSave.Dispose();
- }
-
- if (strokesToSave != null && !string.IsNullOrEmpty(strokeSavePath))
- {
- // 原子写:tmp + File.Replace/Move,避免 FileMode.Create 直接截断后
- // 写入中途失败(磁盘满/进程被杀)让 .icstk 停在 0 字节。
- var tmpStrokePath = strokeSavePath + ".tmp";
- try
- {
- using (var fs = new FileStream(tmpStrokePath, FileMode.Create))
- {
- strokesToSave.Save(fs);
- }
- if (File.Exists(strokeSavePath))
- File.Replace(tmpStrokePath, strokeSavePath, null);
- else
- File.Move(tmpStrokePath, strokeSavePath);
- }
- catch
- {
- try { if (File.Exists(tmpStrokePath)) File.Delete(tmpStrokePath); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Screenshot] 清理临时笔画文件失败: {ex.Message}", LogHelper.LogType.Info);
- }
- throw;
- }
- }
-
- if (!hideNotification && !string.IsNullOrEmpty(path))
- {
- Dispatcher.Invoke(() => ShowNotification(string.Format(Properties.MainWindowStrings.Main_Screenshot_SaveSuccess, path)));
- }
-
- // 使用上传帮助类上传到所有启用的服务
- await Helpers.UploadHelper.UploadFileAsync(path);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"后台保存截图/墨迹失败: {ex}", LogHelper.LogType.Error);
- bitmapToSave?.Dispose();
- }
- });
- }
-
- ///
- /// 将当前屏幕内容捕获为位图(仅内存,不写文件)。调用方或后台任务负责 Dispose。
- ///
- private System.Drawing.Bitmap CaptureScreenshotToBitmap()
- {
- var rc = SystemInformation.VirtualScreen;
- var bitmap = new System.Drawing.Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
- using (var memoryGraphics = Graphics.FromImage(bitmap))
- {
- memoryGraphics.CompositingQuality = CompositingQuality.HighQuality;
- memoryGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- memoryGraphics.SmoothingMode = SmoothingMode.HighQuality;
- memoryGraphics.CompositingMode = CompositingMode.SourceOver;
- memoryGraphics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy);
- }
- return bitmap;
- }
+ => _screenshotService.CaptureAndEnqueueScreenshotSave(isHideNotification, fileName);
///
/// 供插件截图服务调用的全屏捕获入口(调用方负责 Dispose 返回值)。
+ /// 壳:本体已搬入 (M15)。
///
- internal System.Drawing.Bitmap CapturePluginFullScreen() => CaptureScreenshotToBitmap();
+ internal System.Drawing.Bitmap CapturePluginFullScreen() => ScreenshotService.CaptureScreenshotToBitmap();
///
/// 供插件截图服务调用的区域捕获入口(调用方负责 Dispose 返回值)。
+ /// 壳:本体已搬入 (M15)。
///
- internal System.Drawing.Bitmap CapturePluginScreenArea(Rectangle area) => CaptureScreenArea(area);
+ internal System.Drawing.Bitmap CapturePluginScreenArea(Rectangle area) => ScreenshotService.CaptureScreenArea(area);
///
/// 保存截图
+ /// 壳:本体已搬入 (M15)。
///
/// 是否隐藏通知
/// 文件名
@@ -169,63 +45,11 @@ private System.Drawing.Bitmap CaptureScreenshotToBitmap()
/// 3. 如果设置了自动保存墨迹,调用SaveInkCanvasStrokes方法保存墨迹
///
private void SaveScreenShot(bool isHideNotification, string fileName = null)
- {
- var savePath = Settings.Automation.IsSaveScreenshotsInDateFolders
- ? GetDateFolderPath(fileName)
- : GetDefaultFolderPath();
-
- CaptureAndSaveScreenshot(savePath, isHideNotification);
-
- if (Settings.Automation.IsAutoSaveStrokesAtScreenshot)
- SaveInkCanvasStrokes(false);
- }
-
- ///
- /// 获取截图保存目录。
- /// 仅当"截图保存到指定位置"开关开启时使用自定义位置,否则回退到桌面。
- ///
- private string GetScreenshotSaveDirectory()
- {
- if (Settings.Automation.IsSaveScreenshotToCustomLocation)
- {
- var location = Settings.Automation.ScreenshotSaveLocation;
- if (!string.IsNullOrWhiteSpace(location))
- return location;
- }
- return Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
- }
-
- ///
- /// 将截图写入剪贴板,同时提供 PNG 流、Bitmap、FileDrop 三种格式。
- /// 仅写入 Bitmap 格式时 Win+V 剪贴板历史不收录、资源管理器右键也不能粘贴为文件,
- /// 故用 DataObject 多格式写入:
- /// - PNG 流:Win+V 历史 / 现代应用可识别
- /// - Bitmap:老式应用兼容
- /// - FileDrop:资源管理器右键可粘贴为 .png 文件(需传入已保存的文件路径)
- /// 注意:带上 FileDrop 后,部分聊天软件可能优先把贴图当作发送文件处理。
- ///
- private static void CopyImageToClipboard(BitmapSource image, string filePath = null)
- {
- if (image == null) return;
-
- var pngStream = new MemoryStream();
- var encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(image));
- encoder.Save(pngStream);
- pngStream.Position = 0;
-
- var data = new System.Windows.DataObject();
- data.SetData("PNG", pngStream); // Win+V 历史 / 现代应用
- data.SetData(System.Windows.DataFormats.Bitmap, image); // 老式 Bitmap 兼容
- if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
- {
- data.SetData(System.Windows.DataFormats.FileDrop, new[] { filePath }); // 资源管理器右键粘贴为文件
- }
- System.Windows.Clipboard.SetDataObject(data, true);
- }
+ => _screenshotService.SaveScreenShot(isHideNotification, fileName);
///
/// 保存截图到配置的保存目录
+ /// 壳:本体已搬入 (M15)。
///
///
/// 该方法会:
@@ -234,17 +58,7 @@ private static void CopyImageToClipboard(BitmapSource image, string filePath = n
/// 3. 如果设置了截图后复制到剪贴板,将截图复制到剪贴板
/// 4. 如果设置了自动保存墨迹,调用SaveInkCanvasStrokes方法保存墨迹
///
- internal void SaveScreenShotToDesktop()
- {
- var savePath = Path.Combine(
- GetScreenshotSaveDirectory(),
- $"{GetScreenshotFileNameStem()}.png");
-
- CaptureAndSaveScreenshot(savePath, false, Settings.Automation.IsCopyScreenshotToClipboard);
-
- if (Settings.Automation.IsAutoSaveStrokesAtScreenshot)
- SaveInkCanvasStrokes(false);
- }
+ internal void SaveScreenShotToDesktop() => _screenshotService.SaveScreenShotToDesktop();
internal async Task SaveAreaScreenShotToDesktop()
{
@@ -276,76 +90,19 @@ internal async Task SaveAreaScreenShotToDesktop()
}
var savePath = Path.Combine(
- GetScreenshotSaveDirectory(),
- $"{GetScreenshotFileNameStem()}.png");
+ _screenshotService.GetScreenshotSaveDirectory(),
+ $"{_screenshotService.GetScreenshotFileNameStem()}.png");
- using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
+ // 执行段(捕获→叠墨→遮罩→落盘→剪贴板)已搬入 ScreenshotService(M15);
+ // 成功/失败通知保持原有相对顺序:落盘(+剪贴板)之后、自动保存墨迹之前。
+ if (!_screenshotService.SaveAreaScreenshot(screenshotResult.Value, savePath, Settings.Automation.IsCopyScreenshotToClipboard))
{
- if (originalBitmap == null)
- {
- ShowNotification(Properties.MainWindowStrings.Main_Screenshot_Failed);
- return;
- }
-
- Bitmap finalBitmap = originalBitmap;
- bool needDisposeFinalBitmap = false;
-
- try
- {
- if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
- {
- var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
- if (withInkBitmap != null && withInkBitmap != finalBitmap)
- {
- if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
- {
- finalBitmap.Dispose();
- }
- finalBitmap = withInkBitmap;
- needDisposeFinalBitmap = true;
- }
- }
-
- if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
- {
- var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
- if (maskedBitmap != null && maskedBitmap != finalBitmap)
- {
- if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
- {
- finalBitmap.Dispose();
- }
- finalBitmap = maskedBitmap;
- needDisposeFinalBitmap = true;
- }
- }
-
- var directory = Path.GetDirectoryName(savePath);
- if (!Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
-
- finalBitmap.Save(savePath, ImageFormat.Png);
-
- // 截图后复制到剪贴板
- if (Settings.Automation.IsCopyScreenshotToClipboard)
- {
- try { CopyImageToClipboard(ConvertBitmapToBitmapSource(finalBitmap), savePath); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"截图复制到剪贴板失败: {ex}", LogHelper.LogType.Warning); }
- }
-
- ShowNotification(string.Format(Properties.MainWindowStrings.Main_Screenshot_SaveSuccess, savePath));
- }
- finally
- {
- if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
- {
- finalBitmap.Dispose();
- }
- }
+ ShowNotification(Properties.MainWindowStrings.Main_Screenshot_Failed);
+ return;
}
+ ShowNotification(string.Format(Properties.MainWindowStrings.Main_Screenshot_SaveSuccess, savePath));
+
if (Settings.Automation.IsAutoSaveStrokesAtScreenshot)
SaveInkCanvasStrokes(false);
}
@@ -454,172 +211,5 @@ private async Task AddScreenshotToNewWhiteboardPage(ScreenshotResult screenshotR
await InsertBitmapSourceToCanvas(bitmapSourceForClipboard);
}
-
- ///
- /// 提取公共的截图和保存逻辑
- ///
- /// 保存路径
- /// 是否隐藏通知
- /// 是否在保存后复制到剪贴板
- ///
- /// 该方法会:
- /// 1. 获取虚拟屏幕边界
- /// 2. 创建位图并设置高质量渲染
- /// 3. 从屏幕复制内容到位图
- /// 4. 确保保存目录存在
- /// 5. 保存为PNG格式
- /// 6. 若 copyToClipboard 为真,将截图复制到剪贴板
- /// 7. 如果不隐藏通知,显示保存成功通知
- /// 8. 异步上传截图到Dlass
- ///
- private void CaptureAndSaveScreenshot(string savePath, bool isHideNotification, bool copyToClipboard = false)
- {
- var rc = SystemInformation.VirtualScreen;
-
- using (var bitmap = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb))
- using (var memoryGraphics = Graphics.FromImage(bitmap))
- {
- // 设置高质量渲染
- memoryGraphics.CompositingQuality = CompositingQuality.HighQuality;
- memoryGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- memoryGraphics.SmoothingMode = SmoothingMode.HighQuality;
- memoryGraphics.CompositingMode = CompositingMode.SourceOver;
-
- memoryGraphics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy);
-
- // 确保目录存在
- var directory = Path.GetDirectoryName(savePath);
- if (!Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
-
- // 使用PNG格式保存,确保透明度信息不丢失
- bitmap.Save(savePath, ImageFormat.Png);
- LogHelper.WriteLogToFile(
- $"[Screenshot] 截图已保存: {savePath} ({rc.Width}x{rc.Height}), 复制到剪贴板={copyToClipboard}",
- LogHelper.LogType.Info);
-
- // 截图后复制到剪贴板
- if (copyToClipboard)
- {
- try { CopyImageToClipboard(ConvertBitmapToBitmapSource(bitmap), savePath); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"截图复制到剪贴板失败: {ex}", LogHelper.LogType.Warning); }
- }
- }
-
- if (!isHideNotification)
- {
- Task.Delay(100).ContinueWith(t =>
- {
- Dispatcher.Invoke(() =>
- {
- ShowNotification(string.Format(Properties.MainWindowStrings.Main_Screenshot_SaveSuccess, savePath));
- });
- });
- }
- _ = Task.Run(async () =>
- {
- try
- {
- // 使用上传帮助类上传到所有启用的服务
- await Helpers.UploadHelper.UploadFileAsync(savePath);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Screenshot] 截图上传失败(本地文件已保存): {ex.Message}", LogHelper.LogType.Info);
- }
- });
- }
-
- ///
- /// 获取日期文件夹路径
- ///
- /// 文件名
- /// 日期文件夹路径
- ///
- /// 该方法会:
- /// 1. 如果文件名为空,使用当前时间作为文件名
- /// 2. 获取基础路径和日期文件夹名
- /// 3. 组合路径并返回
- ///
- private string GetDateFolderPath(string fileName)
- {
- if (string.IsNullOrWhiteSpace(fileName))
- {
- fileName = Settings.Automation.IsUseCustomSaveFileName
- ? SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
- new SaveFileNameContext { Mode = "Screenshot", Type = "Auto", Count = inkCanvas?.Strokes?.Count })
- : DateTime.Now.ToString("HH-mm-ss");
- }
-
- var basePath = Settings.Automation.AutoSavedStrokesLocation;
- var dateFolder = DateTime.Now.ToString("yyyyMMdd");
- var safeRelativePath = SanitizeScreenshotRelativePath(fileName);
-
- return Path.Combine(
- basePath,
- "Auto Saved - Screenshots",
- dateFolder,
- safeRelativePath + Helpers.ScreenshotImageSaveHelper.GetExtension());
- }
-
- private static string SanitizeScreenshotRelativePath(string relativePath)
- {
- if (string.IsNullOrWhiteSpace(relativePath)) return DateTime.Now.ToString("HH-mm-ss");
-
- var separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
- var parts = relativePath
- .Split(separators, StringSplitOptions.RemoveEmptyEntries)
- .Select(SanitizePathPart)
- .Where(part => !string.IsNullOrWhiteSpace(part));
-
- var sanitized = Path.Combine(parts.ToArray());
- return string.IsNullOrWhiteSpace(sanitized) ? DateTime.Now.ToString("HH-mm-ss") : sanitized;
- }
-
- private static string SanitizePathPart(string pathPart)
- {
- var invalidChars = Path.GetInvalidFileNameChars();
- var chars = pathPart.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray();
- var sanitized = new string(chars).Trim().TrimEnd('.', ' ');
- return sanitized == "." || sanitized == ".." ? "_" : sanitized;
- }
-
- ///
- /// 获取默认文件夹路径
- ///
- /// 默认文件夹路径
- ///
- /// 该方法会:
- /// 1. 获取基础路径
- /// 2. 组合截图文件夹路径
- /// 3. 确保截图文件夹存在
- /// 4. 生成文件名并组合完整路径返回
- ///
- private string GetDefaultFolderPath()
- {
- var basePath = Settings.Automation.AutoSavedStrokesLocation;
- var screenshotsFolder = Path.Combine(basePath, "Auto Saved - Screenshots");
-
- if (!Directory.Exists(screenshotsFolder))
- {
- Directory.CreateDirectory(screenshotsFolder);
- }
-
- return Path.Combine(
- screenshotsFolder,
- GetScreenshotFileNameStem() + Helpers.ScreenshotImageSaveHelper.GetExtension());
- }
-
- private string GetScreenshotFileNameStem()
- {
- if (Settings.Automation.IsUseCustomSaveFileName)
- {
- return SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
- new SaveFileNameContext { Mode = "Screenshot", Type = "Auto", Count = inkCanvas?.Strokes?.Count });
- }
- return DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
- }
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index 9220f999..beeb0c81 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -22,6 +22,9 @@ namespace Ink_Canvas
{
public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
{
+ // M11 寄生提取:设置存储服务,构造函数末尾初始化;壳在全部提取模块完成后删除。
+ private readonly Services.SettingsStore _settingsStore;
+
#region Behavior
@@ -154,20 +157,8 @@ private HttpClient GetHitokotoHttpClientOrNull(bool writeLog = true)
}
}
- private string BuildHitokotoRequestUrl()
- {
- var cats = Settings.Appearance.HitokotoCategories;
- if (cats == null || cats.Count == 0)
- cats = new List { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" };
-
- var urlBuilder = new StringBuilder("https://v1.hitokoto.cn/?encode=text");
- foreach (var category in cats)
- {
- urlBuilder.Append($"&c={category}");
- }
-
- return urlBuilder.ToString();
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private string BuildHitokotoRequestUrl() => _settingsStore.BuildHitokotoRequestUrl();
private async Task FetchHitokotoTextCoreAsync(HttpClient client, string requestUrl)
{
@@ -663,29 +654,11 @@ public void UpdatePPTUIManagerSettings()
#region Canvas
/// 笔锋下拉 UI 顺序:0 实时笔锋,1 基于点集,2 基于速率,3 关闭。与存储值 InkStyle:3,0,1,2 对应。
- private static int PenStyleUiIndexFromInkStyle(int inkStyle)
- {
- switch (inkStyle)
- {
- case 3: return 0;
- case 0: return 1;
- case 1: return 2;
- case 2: return 3;
- default: return 1;
- }
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private static int PenStyleUiIndexFromInkStyle(int inkStyle) => Services.SettingsStore.PenStyleUiIndexFromInkStyle(inkStyle);
- private static int InkStyleFromPenStyleUiIndex(int uiIndex)
- {
- switch (uiIndex)
- {
- case 0: return 3;
- case 1: return 0;
- case 2: return 1;
- case 3: return 2;
- default: return 0;
- }
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private static int InkStyleFromPenStyleUiIndex(int uiIndex) => Services.SettingsStore.InkStyleFromPenStyleUiIndex(uiIndex);
private void ComboBoxPenStyle_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
@@ -1060,156 +1033,8 @@ private void ToggleSwitchEnableTwoFingerRotation_Toggled(object sender, RoutedEv
/// 该方法会重新创建全局 Settings 实例并应用推荐值,覆盖大部分子模块配置(如外观、画布、自动化、PPT、手势、高级选项等)。
/// 在重置过程中会保留并恢复当前 Settings.Automation 中的 AutoDelSavedFiles 与 AutoDelSavedFilesDaysThreshold 两项值以避免意外删除策略变化。
///
- public static void SetSettingsToRecommendation()
- {
- var AutoDelSavedFilesDays = Settings.Automation.AutoDelSavedFiles;
- var AutoDelSavedFilesDaysThreshold = Settings.Automation.AutoDelSavedFilesDaysThreshold;
- Settings = new Settings();
- Settings.Advanced.IsSpecialScreen = true;
- Settings.Advanced.IsQuadIR = false;
- Settings.Advanced.TouchMultiplier = 0.3;
- Settings.Advanced.NibModeBoundsWidth = 5;
- Settings.Advanced.FingerModeBoundsWidth = 20;
- Settings.Advanced.NibModeBoundsWidthThresholdValue = 2.5;
- Settings.Advanced.FingerModeBoundsWidthThresholdValue = 2.5;
- Settings.Advanced.NibModeBoundsWidthEraserSize = 0.8;
- Settings.Advanced.FingerModeBoundsWidthEraserSize = 0.8;
- Settings.Advanced.EraserBindTouchMultiplier = true;
- Settings.Advanced.IsLogEnabled = true;
- Settings.Advanced.IsSecondConfirmWhenShutdownApp = false;
- Settings.Advanced.IsEnableEdgeGestureUtil = false;
- Settings.Advanced.EdgeGestureUtilOnlyAffectBlackboardMode = false;
- Settings.Advanced.IsEnableFullScreenHelper = false;
- Settings.Advanced.IsEnableAvoidFullScreenHelper = OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows11;
- Settings.Advanced.IsEnableForceFullScreen = false;
- Settings.Advanced.IsEnableDPIChangeDetection = false;
- Settings.Advanced.IsEnableResolutionChangeDetection = false;
- Settings.Advanced.EnableMultiScreenSupport = true;
- Settings.Advanced.FollowMouseForScreenSelection = true;
-
- Settings.Appearance.IsColorfulViewboxFloatingBar = false;
- Settings.Appearance.ViewboxFloatingBarScaleTransformValue = 1;
- Settings.Appearance.ViewboxBlackBoardScaleTransformValue = 0.8;
- Settings.Appearance.IsTransparentButtonBackground = true;
- Settings.Appearance.IsShowExitButton = true;
- Settings.Appearance.IsShowEraserButton = true;
- Settings.Appearance.IsShowHideControlButton = false;
- Settings.Appearance.IsShowLRSwitchButton = false;
- Settings.Appearance.IsShowModeFingerToggleSwitch = true;
- Settings.Appearance.IsShowQuickPanel = true;
- Settings.Appearance.Theme = 0;
- Settings.Appearance.EnableChickenSoupInWhiteboardMode = true;
- Settings.Appearance.EnableTimeDisplayInWhiteboardMode = true;
- Settings.Appearance.ChickenSoupSource = 1;
- Settings.Appearance.ViewboxFloatingBarOpacityValue = 1.0;
- Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue = 1.0;
- Settings.Appearance.EnableTrayIcon = true;
-
- // 浮动栏按钮显示控制默认值
- Settings.Appearance.IsShowQuickColorPalette = false;
- Settings.Appearance.QuickColorPaletteDisplayMode = 1;
- Settings.Appearance.EraserDisplayOption = 0;
-
- Settings.Automation.IsAutoFoldInEasiNote = true;
- Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno = true;
- Settings.Automation.IsAutoFoldInEasiCamera = true;
- Settings.Automation.IsAutoFoldInEasiNote3C = false;
- Settings.Automation.IsAutoFoldInEasiNote3 = false;
- Settings.Automation.IsAutoFoldInEasiNote5C = true;
- Settings.Automation.IsAutoFoldInSeewoPincoTeacher = false;
- Settings.Automation.IsAutoFoldInHiteTouchPro = false;
- Settings.Automation.IsAutoFoldInHiteCamera = false;
- Settings.Automation.IsAutoFoldInWxBoardMain = false;
- Settings.Automation.IsAutoFoldInOldZyBoard = false;
- Settings.Automation.IsAutoFoldInMSWhiteboard = false;
- Settings.Automation.IsAutoFoldInAdmoxWhiteboard = false;
- Settings.Automation.IsAutoFoldInAdmoxBooth = false;
- Settings.Automation.IsAutoFoldInQPoint = false;
- Settings.Automation.IsAutoFoldInYiYunVisualPresenter = false;
- Settings.Automation.IsAutoFoldInMaxHubWhiteboard = false;
- Settings.Automation.IsAutoFoldInPPTSlideShow = false;
- Settings.Automation.IsAutoKillPPTService = false;
- Settings.Automation.IsAutoKillEasiNote = false;
- Settings.Automation.IsAutoKillVComYouJiao = false;
- Settings.Automation.IsAutoKillInkCanvas = false;
- Settings.Automation.IsAutoKillICA = false;
- Settings.Automation.IsAutoKillIDT = false;
- Settings.Automation.IsAutoKillSeewoLauncher2DesktopAnnotation = false;
- Settings.Automation.IsSaveScreenshotsInDateFolders = false;
- Settings.Automation.ScreenshotSaveFormat = 0;
- Settings.Automation.ScreenshotJpegQuality = 90;
- Settings.Automation.ScreenshotScaleMode = 0;
- Settings.Automation.IsAutoSaveStrokesAtScreenshot = true;
- Settings.Automation.IsAutoSaveScreenshotAtClear = true;
- Settings.Automation.IsAutoClearWhenExitingWritingMode = false;
- Settings.Automation.MinimumAutomationStrokeNumber = 0;
- Settings.Automation.AutoDelSavedFiles = AutoDelSavedFilesDays;
- Settings.Automation.AutoDelSavedFilesDaysThreshold = AutoDelSavedFilesDaysThreshold;
-
- //Settings.PowerPointSettings.IsShowPPTNavigation = true;
- //Settings.PowerPointSettings.IsShowBottomPPTNavigationPanel = false;
- //Settings.PowerPointSettings.IsShowSidePPTNavigationPanel = true;
- Settings.PowerPointSettings.PowerPointSupport = true;
- Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow = false;
- Settings.PowerPointSettings.IsNoClearStrokeOnSelectWhenInPowerPoint = true;
- Settings.PowerPointSettings.IsShowStrokeOnSelectInPowerPoint = false;
- Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint = true;
- Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint = true;
- Settings.PowerPointSettings.IsNotifyPreviousPage = false;
- Settings.PowerPointSettings.IsNotifyHiddenPage = false;
- Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode = false;
- Settings.PowerPointSettings.IsEnableFingerGestureSlideShowControl = false;
- Settings.PowerPointSettings.IsSupportWPS = false;
- Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview = false;
- Settings.PowerPointSettings.ShowPPTEnhancedPreviewLoadingAnimation = true;
-
- Settings.Canvas.InkWidth = 2.5;
- Settings.Canvas.IsShowCursor = false;
- Settings.Canvas.InkStyle = 0;
- Settings.Canvas.HighlighterWidth = 20;
- Settings.Canvas.EraserSize = 1;
- Settings.Canvas.EraserType = 0;
- Settings.Canvas.EraserShapeType = 1;
- Settings.Canvas.HideStrokeWhenSelecting = false;
- Settings.Canvas.ClearCanvasAndClearTimeMachine = false;
- Settings.Canvas.FitToCurve = false;
- Settings.Canvas.UseAdvancedBezierSmoothing = true;
- Settings.Canvas.MergeInkSmoothingWithUndo = false;
- Settings.Canvas.EnablePressureTouchMode = false;
- Settings.Canvas.DisablePressure = false;
- Settings.Canvas.AutoStraightenLine = true;
- Settings.Canvas.AutoStraightenLineThreshold = 80;
- Settings.Canvas.PauseStraightenLine = false;
- Settings.Canvas.PauseStraightenDelay = 300;
- Settings.Canvas.LineEndpointSnapping = true;
- Settings.Canvas.LineEndpointSnappingThreshold = 15;
- Settings.Canvas.UsingWhiteboard = false;
- Settings.Canvas.HyperbolaAsymptoteOption = 0;
-
- Settings.Gesture.IsEnableTwoFingerTranslate = true;
- Settings.Gesture.IsEnableTwoFingerZoom = false;
- Settings.Gesture.IsEnableTwoFingerRotation = false;
- Settings.Gesture.IsEnableTwoFingerRotationOnSelection = false;
-
- Settings.InkToShape.IsInkToShapeEnabled = true;
- Settings.InkToShape.IsInkToShapeNoFakePressureRectangle = false;
- Settings.InkToShape.IsInkToShapeNoFakePressureTriangle = false;
- Settings.InkToShape.IsInkToShapeTriangle = true;
- Settings.InkToShape.IsInkToShapeRectangle = true;
- Settings.InkToShape.IsInkToShapeRounded = true;
- Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify = false;
- Settings.InkToShape.HandwritingCorrectionFontFamily = "Ink Free,KaiTi,Segoe Script";
- Settings.InkToShape.HandwritingLanguageOverrideLcid = 0;
- Settings.InkToShape.HandwritingBeautifyDebounceMs = 2000;
-
- Settings.Startup.IsEnableNibMode = false;
- Settings.Startup.IsAutoUpdate = true;
- Settings.Startup.IsAutoUpdateWithSilence = true;
- Settings.Startup.AutoUpdateWithSilenceStartTime = "06:00";
- Settings.Startup.AutoUpdateWithSilenceEndTime = "22:00";
- Settings.Startup.IsFoldAtStartup = false;
- Settings.Startup.StartupMode = StartupMode.Default;
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ public static void SetSettingsToRecommendation() => Services.SettingsStore.SetSettingsToRecommendation();
///
/// 将应用设置重置为推荐的默认值,并保存与重新加载配置以应用更改。
@@ -1328,44 +1153,8 @@ void SetIcon(ToolbarImageButton btn, bool isSolid, string solidGeom, string line
}
}
- public string GetCorrectIcon(string iconType, bool isSolid = false)
- {
- if (Settings.Appearance.UseLegacyFloatingBarUI)
- {
- // 使用老版图标
- switch (iconType)
- {
- case "cursor":
- return isSolid ? XamlGraphicsIconGeometries.LegacySolidCursorIcon : XamlGraphicsIconGeometries.LegacyLinedCursorIcon;
- case "pen":
- return isSolid ? XamlGraphicsIconGeometries.LegacySolidPenIcon : XamlGraphicsIconGeometries.LegacyLinedPenIcon;
- case "eraserStroke":
- return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserStrokeIcon : XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon;
- case "eraserCircle":
- return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserCircleIcon : XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon;
- case "lassoSelect":
- return isSolid ? XamlGraphicsIconGeometries.LegacySolidLassoSelectIcon : XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon;
- }
- }
- else
- {
- // 使用新版图标
- switch (iconType)
- {
- case "cursor":
- return isSolid ? XamlGraphicsIconGeometries.SolidCursorIcon : XamlGraphicsIconGeometries.LinedCursorIcon;
- case "pen":
- return isSolid ? XamlGraphicsIconGeometries.SolidPenIcon : XamlGraphicsIconGeometries.LinedPenIcon;
- case "eraserStroke":
- return isSolid ? XamlGraphicsIconGeometries.SolidEraserStrokeIcon : XamlGraphicsIconGeometries.LinedEraserStrokeIcon;
- case "eraserCircle":
- return isSolid ? XamlGraphicsIconGeometries.SolidEraserCircleIcon : XamlGraphicsIconGeometries.LinedEraserCircleIcon;
- case "lassoSelect":
- return isSolid ? XamlGraphicsIconGeometries.SolidLassoSelectIcon : XamlGraphicsIconGeometries.LinedLassoSelectIcon;
- }
- }
- return "";
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ public string GetCorrectIcon(string iconType, bool isSolid = false) => _settingsStore.GetCorrectIcon(iconType, isSolid);
#region 浮动栏按钮显示控制
@@ -1405,7 +1194,8 @@ internal void UpdateFloatingBarButtonsVisibility()
///
/// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。
///
- public static void SaveSettingsToFile() => SettingsManager.SaveSettingsToFile();
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ public static void SaveSettingsToFile() => Services.SettingsStore.SaveSettingsToFile();
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index 763e66e7..5f46ea9e 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -734,39 +734,8 @@ private void LoadInkFadeSettings()
/// 4. 如果有清理操作,重新反序列化并保存
/// 5. 记录清理结果到日志
///
- private void CleanupObsoleteSettings(string userConfigJson)
- {
- try
- {
- // 创建默认配置对象
- Settings defaultSettings = new Settings();
-
- // 将默认配置和用户配置都序列化为JObject
- JObject defaultConfigObj = JObject.FromObject(defaultSettings); EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
- JObject userConfigObj = JObject.Parse(userConfigJson);
-
- // 记录是否有清理或迁移操作
- bool hasChanges = false;
- MigrateLegacyStartupMode(userConfigObj, ref hasChanges);
-
- // 递归比较并删除用户配置中多余的键
- RemoveObsoleteProperties(userConfigObj, defaultConfigObj, ref hasChanges);
-
- // 如果有清理操作,重新反序列化并保存
- if (hasChanges)
- {
- string cleanedJson = userConfigObj.ToString(Formatting.Indented);
- Settings = JsonConvert.DeserializeObject(cleanedJson);
- SaveSettingsToFile();
- App.UpdateCachedSettingsJson(cleanedJson);
- LogHelper.WriteLogToFile("已清理过期配置项", LogHelper.LogType.Event);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清理过期配置时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private void CleanupObsoleteSettings(string userConfigJson) => _settingsStore.CleanupObsoleteSettings(userConfigJson);
///
/// 递归删除用户配置中多余的属性
@@ -785,101 +754,17 @@ private void CleanupObsoleteSettings(string userConfigJson)
/// 7. 删除标记的键
/// 8. 设置变更标志
///
- private static void MigrateLegacyStartupMode(JObject userConfigObj, ref bool hasChanges)
- {
- if (!(userConfigObj?["startup"] is JObject startup)) return;
-
- if (startup["startupMode"] == null && startup["enableFastStartup"]?.Type == JTokenType.Boolean)
- {
- startup["startupMode"] = startup["enableFastStartup"].Value()
- ? (int)StartupMode.Fastest
- : (int)StartupMode.Faster;
- hasChanges = true;
- }
-
- if (startup.Remove("enableFastStartup"))
- {
- hasChanges = true;
- }
- }
-
- private static void EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(JObject defaultConfigObj)
- {
- if (defaultConfigObj == null) return;
- if (defaultConfigObj["appearance"] is JObject appearance)
- {
- // 这些属性同时具备 NullValueHandling.Ignore 且默认值为 null,
- // 不会出现在默认 JObject 中。CleanupObsoleteSettings 会把它们
- // 误判为"过期"并立即 SaveSettingsToFile 删除,于是用户自建语录
- // 被静默清空。补成 null 占位让 RemoveObsoleteProperties 放行。
- foreach (var ignoredKey in new[] { "hitokotoCategories", "customTipsSchemes", "enabledPresetTipsSources" })
- {
- if (!appearance.ContainsKey(ignoredKey))
- appearance[ignoredKey] = JValue.CreateNull();
- }
- }
- }
-
- private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges)
- {
- if (userObj == null || defaultObj == null)
- return;
-
- // 获取需要删除的键列表(避免在遍历时修改集合)
- List keysToRemove = new List();
-
- foreach (var property in userObj.Properties())
- {
- string propertyName = property.Name;
-
- // 如果默认配置中不存在该属性,标记为删除
- if (!defaultObj.ContainsKey(propertyName))
- {
- keysToRemove.Add(propertyName);
- continue;
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private static void MigrateLegacyStartupMode(JObject userConfigObj, ref bool hasChanges) =>
+ Services.SettingsStore.MigrateLegacyStartupMode(userConfigObj, ref hasChanges);
- // 如果两个属性都是对象类型,递归比较
- JToken userValue = property.Value;
- JToken defaultValue = defaultObj[propertyName];
-
- if (userValue != null && defaultValue != null)
- {
- if (userValue.Type == JTokenType.Object && defaultValue.Type == JTokenType.Object)
- {
- RemoveObsoleteProperties(userValue as JObject, defaultValue as JObject, ref hasChanges);
- }
- // 处理数组中的对象(如自定义图标列表等)
- else if (userValue.Type == JTokenType.Array && defaultValue.Type == JTokenType.Array)
- {
- JArray userArray = userValue as JArray;
- JArray defaultArray = defaultValue as JArray;
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private static void EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(JObject defaultConfigObj) =>
+ Services.SettingsStore.EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
- if (userArray != null && defaultArray != null && userArray.Count > 0 && defaultArray.Count > 0)
- {
- // 如果数组元素是对象,比较第一个元素的属性结构
- if (userArray[0].Type == JTokenType.Object && defaultArray[0].Type == JTokenType.Object)
- {
- for (int i = 0; i < userArray.Count; i++)
- {
- if (userArray[i] is JObject userItemObj && defaultArray[0] is JObject defaultItemObj)
- {
- RemoveObsoleteProperties(userItemObj, defaultItemObj, ref hasChanges);
- }
- }
- }
- }
- }
- }
- }
-
- // 删除标记的键
- foreach (string key in keysToRemove)
- {
- userObj.Remove(key);
- hasChanges = true;
- }
- }
+ // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges) =>
+ _settingsStore.RemoveObsoleteProperties(userObj, defaultObj, ref hasChanges);
internal void ApplyQuickPanelBottomOffset(double offset)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
index b409a728..bd815543 100644
--- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
@@ -1,806 +1,46 @@
using H.NotifyIcon;
-using Ink_Canvas.Helpers;
-using Ink_Canvas.Properties;
-using iNKORE.UI.WPF.Controls;
+using Ink_Canvas.Services;
using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Forms;
-using System.Windows.Interop;
-using System.Windows.Media;
-using System.Windows.Threading;
using Application = System.Windows.Application;
-using ContextMenu = System.Windows.Controls.ContextMenu;
-using MenuItem = System.Windows.Controls.MenuItem;
namespace Ink_Canvas
{
public partial class App : Application
{
- private const int TrayTemporaryShowMinutes = 2;
+ // M12:托盘逻辑已提取至 Services/Shell/TrayIconService.cs,本文件仅保留
+ // XAML wired 事件处理器与插件托盘 API 的转发壳(壳删除试点)。
+ // 服务的依赖装配在 App.xaml.cs 的 CreateTrayIconService,释放在 App_Exit。
+ private TrayIconService _trayIconService;
- private DispatcherTimer _trayTemporaryShowTimer;
-
- private bool _trayTemporaryShowRestoreHideChecked;
-
- private void TaskbarTrayIcon_TrayLeftMouseDown(object sender, RoutedEventArgs e)
- {
- var action = Ink_Canvas.MainWindow.Settings.Appearance.TrayLeftClickAction;
- ExecuteTrayClickAction(action);
-
- try { PluginTrayLeftClicked?.Invoke(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"转发插件托盘左键事件失败: {ex.Message}", LogHelper.LogType.Warning); }
- }
-
- private void TaskbarTrayIcon_TrayRightMouseDown(object sender, RoutedEventArgs e)
- {
- var action = Ink_Canvas.MainWindow.Settings.Appearance.TrayRightClickAction;
- ExecuteTrayClickAction(action);
-
- try { PluginTrayRightClicked?.Invoke(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"转发插件托盘右键事件失败: {ex.Message}", LogHelper.LogType.Warning); }
- }
-
- private void ExecuteTrayClickAction(TrayClickAction action)
- {
- switch (action)
- {
- case TrayClickAction.ShowMenu:
- ShowTrayContextMenu();
- break;
- case TrayClickAction.HideShowMainWindow:
- ToggleMainWindowVisibility();
- break;
- case TrayClickAction.TempShowMainWindow:
- TempShowMainWindowTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.OpenSettings:
- OpenSettingsTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.DisableAllHotkeys:
- DisableAllHotkeysMenuItem_Clicked(FindTrayMenuItem("DisableAllHotkeysMenuItem"), null);
- break;
- case TrayClickAction.ForceFullScreen:
- ForceFullScreenTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.ToggleFoldFloatingBar:
- FoldFloatingBarTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.ResetFloatingBarPosition:
- ResetFloatingBarPositionTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.RestartApp:
- RestartAppTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.CloseApp:
- CloseAppTrayIconMenuItem_Clicked(null, null);
- break;
- case TrayClickAction.NoAction:
- break;
- }
- }
-
- private MenuItem FindTrayMenuItem(string name)
- {
- try
- {
- var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
- return trayMenu?.Items.OfType
- /// 主窗的 _currentToolMode。
+ /// 主窗当前工具模式的内部字符串(ToolModeMapping.ToInternalString 输出)。
/// 0 = 普通笔,1 = 荧光笔。
/// 当前画笔颜色,用于点亮颜色圆点。
internal void SyncActiveState(string toolMode, int penType, Color? color)
From 8319263a140076b277a310e2f608f9a94568b6d0 Mon Sep 17 00:00:00 2001
From: HAILVa1kyrie <280684694+HAILVa1kyrie@users.noreply.github.com>
Date: Sat, 3 Oct 2026 10:41:56 +0800
Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=E7=A9=BA=20catch=20=E6=B2=BB?=
=?UTF-8?q?=E7=90=86=E4=B8=8E=E6=94=B6=E5=B0=BE=E4=BF=AE=E5=A4=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
引入 LogService,空 catch 改为记日志、重抛或注释说明。MessageBox 弹窗主题锁定为 Owner 窗口实际主题。清理重构期遗留的模块编号措辞。
---
Ink Canvas/App.xaml.cs | 27 ++---
.../KillProcessActionHandler.cs | 9 +-
.../Automation/Actions/KillProcessAction.cs | 8 +-
Ink Canvas/Automation/AutomationBootstrap.cs | 5 +-
.../Automation/Services/AutomationService.cs | 2 +-
.../Automation/Services/SystemEventMonitor.cs | 7 +-
.../Triggers/AnnotationModeEnterTrigger.cs | 8 +-
.../Triggers/AnnotationModeExitTrigger.cs | 8 +-
.../Triggers/PptSlideShowEnterTrigger.cs | 4 +-
.../Triggers/PptSlideShowExitTrigger.cs | 4 +-
.../Triggers/PptSlideShowTrigger.cs | 4 +-
.../Controls/CanvasMediaControl.xaml.cs | 28 ++---
.../DynamicNotificationControl.xaml.cs | 4 +-
.../BoardToolbar/BoardToolbarRegistry.cs | 5 +-
Ink Canvas/Helpers/AutoFontSizeHelper.cs | 4 +-
.../Helpers/CapturableVideoCaptureElement.cs | 5 +-
Ink Canvas/Helpers/Converters.cs | 2 +-
Ink Canvas/Helpers/DebugConsoleManager.cs | 4 +-
Ink Canvas/Helpers/DeviceIdentifier.cs | 8 +-
Ink Canvas/Helpers/DirectShowCameraService.cs | 51 +++------
.../Helpers/FloatingBarThemeMarketService.cs | 9 +-
Ink Canvas/Helpers/GlobalHotkeyManager.cs | 18 ++--
.../HardwareAcceleratedInkProcessor.cs | 2 +-
Ink Canvas/Helpers/LiquidGlassMagnifier.cs | 14 +--
Ink Canvas/Helpers/LocalizationHelper.cs | 22 ++--
Ink Canvas/Helpers/LogService.cs | 54 ++++++++++
Ink Canvas/Helpers/MemoryBreakdownHelper.cs | 24 ++---
Ink Canvas/Helpers/MessageBoxHelper.cs | 80 +++++++++++---
Ink Canvas/Helpers/MicroBinClient.cs | 3 +-
Ink Canvas/Helpers/NameRosterManager.cs | 18 +---
.../Helpers/Ppt/PPTROTConnectionHelper.cs | 14 +--
Ink Canvas/Helpers/Ppt/ROTPPTManager.cs | 37 +++----
.../Helpers/ProcessProtectionManager.cs | 35 +++---
Ink Canvas/Helpers/UIAccessHelper.cs | 2 +-
.../Helpers/UiShell/SliderTouchHelper.cs | 2 +-
Ink Canvas/Helpers/UiShell/ThemeHelper.cs | 8 +-
.../Helpers/UiShell/WindowBackdropHelper.cs | 20 +++-
Ink Canvas/Helpers/Update/AutoUpdateHelper.cs | 43 ++------
.../Helpers/Update/TelemetryUploader.cs | 2 +-
Ink Canvas/Helpers/VstoRegistrationHelper.cs | 5 +-
.../Helpers/WindowsNotificationHelper.cs | 4 +-
Ink Canvas/IPC/PPTAgentPipeClient.cs | 3 +-
Ink Canvas/Ink/WinRT/WetInkOverlayWindow.cs | 20 +---
Ink Canvas/Ink/WinRT/WinRTInkHost.cs | 15 +--
Ink Canvas/MainWindow.xaml.cs | 30 +++---
Ink Canvas/MainWindow_cs/MW_AutoTheme.cs | 18 ++--
Ink Canvas/MainWindow_cs/MW_BoardRoaming.cs | 2 +-
.../MainWindow_cs/MW_FloatingBarIcons.cs | 23 ++--
Ink Canvas/MainWindow_cs/MW_Hotkeys.cs | 12 +--
Ink Canvas/MainWindow_cs/MW_ImageInsert.cs | 8 +-
Ink Canvas/MainWindow_cs/MW_Notification.cs | 2 +-
Ink Canvas/MainWindow_cs/MW_PPT.cs | 41 +++----
.../MainWindow_cs/MW_SaveAndOpenStrokes.cs | 20 ++--
Ink Canvas/MainWindow_cs/MW_Screenshot.cs | 12 +--
Ink Canvas/MainWindow_cs/MW_Settings.cs | 27 +++--
Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 10 +-
Ink Canvas/MainWindow_cs/MW_Timer.cs | 17 +--
Ink Canvas/MainWindow_cs/MW_TrayIcon.cs | 2 +-
Ink Canvas/MainWindow_cs/MW_UInk.cs | 10 +-
Ink Canvas/MainWindow_cs/MW_UriHandler.cs | 5 +-
Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs | 99 ++++-------------
.../MainWindow_cs/MW_WindowChromeHitTest.cs | 33 ++----
Ink Canvas/Models/TimerState.cs | 4 +-
Ink Canvas/Models/ToolMode.cs | 6 +-
.../Plugins/PluginAuthorizationService.cs | 15 +--
Ink Canvas/Plugins/PluginIpcService.cs | 18 +---
Ink Canvas/Plugins/PluginManager.cs | 36 ++-----
Ink Canvas/Plugins/PluginMarketService.cs | 17 +--
Ink Canvas/Services/Classroom/TimerService.cs | 4 +-
Ink Canvas/Services/Settings/SettingsStore.cs | 2 +-
Ink Canvas/Services/Shell/HotkeyService.cs | 6 +-
.../Services/Shell/NotificationService.cs | 15 +--
.../Services/Shell/ScreenshotService.cs | 13 +--
Ink Canvas/Services/Shell/TrayIconService.cs | 8 +-
Ink Canvas/UInk/UInkSaveService.cs | 17 +--
Ink Canvas/UInk/UInkWriter.cs | 9 +-
.../Windows/AnnouncementCenterWindow.xaml.cs | 6 +-
.../Windows/CountdownTimerWindow.xaml.cs | 2 +-
Ink Canvas/Windows/CrashWindow.xaml.cs | 6 +-
.../Windows/MiniWhiteboardWindow.xaml.cs | 6 +-
.../NewStyleMinimizedTimerWindow.xaml.cs | 3 +-
.../Windows/NewStyleTimerWindow.xaml.cs | 19 ++--
.../Windows/OperatingGuideWindow.xaml.cs | 3 +-
Ink Canvas/Windows/PPTQuickPanel.xaml.cs | 2 +-
Ink Canvas/Windows/PPTTimeCapsule.xaml.cs | 35 ++----
.../Windows/PrivacyAgreementWindow.xaml.cs | 6 +-
.../SettingsViews/Helpers/AutoStartHelper.cs | 12 +--
.../SettingsViews/Helpers/SettingsManager.cs | 8 +-
.../Helpers/WindowSettingsHelper.cs | 5 +-
.../SettingsViews/Pages/AboutPage.xaml.cs | 4 +-
.../Pages/CloudStoragePage.xaml.cs | 6 +-
.../Pages/FloatingBarThemeMarketPage.xaml.cs | 24 +----
.../Pages/PPTPageFlipPreviewPage.xaml.cs | 12 +--
.../SettingsViews/Pages/PluginPage.xaml.cs | 7 +-
.../SettingsViews/Pages/StoragePage.xaml.cs | 13 +--
.../SettingsViews/SettingsWindow.xaml.cs | 100 ++++--------------
.../InkCanvas.IACoreHelper.csproj | 1 +
InkCanvas.IACoreHelper/LogService.cs | 55 ++++++++++
InkCanvas.IACoreHelper/Program.cs | 8 +-
InkCanvas.LiquidGlassMagHost/LogService.cs | 55 ++++++++++
InkCanvas.LiquidGlassMagHost/MagnifierHost.cs | 2 +-
.../Core/PPTController.cs | 16 +--
InkCanvas.PowerPointAddIn/IPC/PipeHost.cs | 4 +-
.../InkCanvas.PowerPointAddIn.csproj | 3 +
InkCanvas.PowerPointAddIn/LogService.cs | 55 ++++++++++
package.json | 4 +
106 files changed, 749 insertions(+), 925 deletions(-)
create mode 100644 Ink Canvas/Helpers/LogService.cs
create mode 100644 InkCanvas.IACoreHelper/LogService.cs
create mode 100644 InkCanvas.LiquidGlassMagHost/LogService.cs
create mode 100644 InkCanvas.PowerPointAddIn/LogService.cs
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 01103cd5..e401b832 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -210,6 +210,8 @@ public App()
}
catch
{
+ // 此处守卫的正是日志写入本身:日志系统尚未就绪或写盘失败时无法再记录,
+ // 只能静默吞掉以保证启动流程不被日志故障中断(这是全仓唯一允许无日志的空 catch)。
}
System.Windows.Forms.Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
@@ -238,7 +240,7 @@ public App()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[App] 设置 AppUserModelID 失败(任务栏分组可能异常): {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
// 配置TLS协议以支持Windows 7
@@ -435,7 +437,7 @@ private void ConfigureTlsForWindows7()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[App] TLS 兼容配置失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -525,7 +527,7 @@ private void BindMainWindowLifecycle()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Crash] 绑定主窗口 SourceInitialized 事件失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -548,7 +550,7 @@ private void MainWindow_SourceInitialized(object sender, EventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Crash] 注册主窗口销毁监听失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -606,7 +608,7 @@ private void CleanupTerminationMonitoring()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Crash] 卸载窗口销毁 WinEvent 钩子失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -1040,16 +1042,15 @@ private static void SyncCrashActionFromParsed(dynamic parsedSettings)
try
{
int crashAction = 2;
- try { crashAction = (int)(parsedSettings?["startup"]?["crashAction"] ?? 2); }
- catch (Exception ex)
+ try { crashAction = (int)(parsedSettings?["startup"]?["crashAction"] ?? 2); } catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[App] 解析崩溃后动作设置失败,改用默认值 2: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
CrashAction = (CrashActionType)crashAction;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[App] 同步崩溃后动作失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -2261,7 +2262,7 @@ public static void RunWatchdogIfNeeded()
}
}
- // M12:装配托盘服务的依赖委托。MainWindow/App 私有成员的访问全部经此处注入,
+ // 装配托盘服务的依赖委托。MainWindow/App 私有成员的访问全部经此处注入,
// TrayIconService 不引用 MainWindow 类型(事件出抛方向唯一:Service → App)。
private TrayIconService CreateTrayIconService()
{
@@ -2345,11 +2346,11 @@ private void App_Exit(object sender, ExitEventArgs e)
try { heartbeatTimer?.Stop(); } catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Exit] 停止心跳计时器失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
try { watchdogTimer?.Change(Timeout.Infinite, Timeout.Infinite); watchdogTimer?.Dispose(); } catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Exit] 释放看门狗计时器失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
MemoryBreakdownHelper.StopAutomaticDumpMonitor();
@@ -2377,7 +2378,7 @@ private void App_Exit(object sender, ExitEventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Exit] 释放单实例互斥体失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
// 卸载所有插件
diff --git a/Ink Canvas/Automation/ActionHandlers/KillProcessActionHandler.cs b/Ink Canvas/Automation/ActionHandlers/KillProcessActionHandler.cs
index e21af0f9..534b97b5 100644
--- a/Ink Canvas/Automation/ActionHandlers/KillProcessActionHandler.cs
+++ b/Ink Canvas/Automation/ActionHandlers/KillProcessActionHandler.cs
@@ -19,16 +19,15 @@ public KillProcessActionHandler(IActionService actionService)
{
foreach (var process in Process.GetProcessesByName(s.ProcessName))
{
- try { process.Kill(); }
- catch (Exception ex)
+ try { process.Kill(); } catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 结束进程 处理器: 结束进程 \"{s.ProcessName}\" 中的某个进程失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 结束进程 处理器: 枚举进程 \"{s.ProcessName}\" 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
});
}
diff --git a/Ink Canvas/Automation/Actions/KillProcessAction.cs b/Ink Canvas/Automation/Actions/KillProcessAction.cs
index 77a2f0cd..0612d337 100644
--- a/Ink Canvas/Automation/Actions/KillProcessAction.cs
+++ b/Ink Canvas/Automation/Actions/KillProcessAction.cs
@@ -43,15 +43,15 @@ public static ActionRegistryInfo Register()
{
process.Kill();
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 结束进程 行动: 结束进程 \"{s.ProcessName}\" 中的某个进程失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 结束进程 行动: 枚举进程 \"{s.ProcessName}\" 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
};
diff --git a/Ink Canvas/Automation/AutomationBootstrap.cs b/Ink Canvas/Automation/AutomationBootstrap.cs
index 847a0bd5..53c3961b 100644
--- a/Ink Canvas/Automation/AutomationBootstrap.cs
+++ b/Ink Canvas/Automation/AutomationBootstrap.cs
@@ -158,10 +158,9 @@ public static void Initialize()
{
// 任意步骤失败时整体回滚到未初始化状态,避免后续 AutomationBootstrap 调用走错误路径
LogHelper.WriteLogToFile($"[Automation] 初始化失败,已回滚: {ex.Message}", LogHelper.LogType.Error);
- try { Shutdown(); }
- catch (Exception shutdownEx)
+ try { Shutdown(); } catch (Exception shutdownEx)
{
- LogHelper.WriteLogToFile($"[Automation] 初始化失败后回滚 Shutdown 也失败: {shutdownEx.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(shutdownEx);
}
throw;
}
diff --git a/Ink Canvas/Automation/Services/AutomationService.cs b/Ink Canvas/Automation/Services/AutomationService.cs
index e5c8fc4e..5831f01a 100644
--- a/Ink Canvas/Automation/Services/AutomationService.cs
+++ b/Ink Canvas/Automation/Services/AutomationService.cs
@@ -329,7 +329,7 @@ private void UnloadTrigger(Workflow workflow, TriggerSettings trigger)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 卸载触发器 \"{trigger.Id}\" 的 UnLoaded() 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
trigger.TriggerInstance.Triggered -= TriggerTriggered;
diff --git a/Ink Canvas/Automation/Services/SystemEventMonitor.cs b/Ink Canvas/Automation/Services/SystemEventMonitor.cs
index 11a911c9..7faec40d 100644
--- a/Ink Canvas/Automation/Services/SystemEventMonitor.cs
+++ b/Ink Canvas/Automation/Services/SystemEventMonitor.cs
@@ -258,7 +258,7 @@ private void OnForegroundFallbackElapsed(object sender, ElapsedEventArgs e)
}
catch (Exception ex)
{
- LogCallbackException("前台窗口兜底轮询获取当前前台窗口", ex);
+ Helpers.LogService.LogException(ex);
}
}
@@ -284,10 +284,9 @@ public void Dispose()
if (_foregroundHook != HWINEVENTHOOK.Null)
{
- try { PInvoke.UnhookWinEvent(_foregroundHook); }
- catch (Exception ex)
+ try { PInvoke.UnhookWinEvent(_foregroundHook); } catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] SystemEventMonitor 释放时卸载前台窗口 WinEvent 钩子失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
_foregroundHook = HWINEVENTHOOK.Null;
}
diff --git a/Ink Canvas/Automation/Triggers/AnnotationModeEnterTrigger.cs b/Ink Canvas/Automation/Triggers/AnnotationModeEnterTrigger.cs
index a80307d1..37d2e271 100644
--- a/Ink Canvas/Automation/Triggers/AnnotationModeEnterTrigger.cs
+++ b/Ink Canvas/Automation/Triggers/AnnotationModeEnterTrigger.cs
@@ -65,9 +65,9 @@ private void TrySubscribeInkCanvas()
mw.inkCanvas.EditingModeChanged += OnEditingModeChanged;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 批注模式进入触发器订阅 inkCanvas.EditingModeChanged 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -81,9 +81,9 @@ private void TryUnsubscribeInkCanvas()
mw.inkCanvas.EditingModeChanged -= OnEditingModeChanged;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 批注模式进入触发器退订 inkCanvas.EditingModeChanged 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Automation/Triggers/AnnotationModeExitTrigger.cs b/Ink Canvas/Automation/Triggers/AnnotationModeExitTrigger.cs
index e487f3c3..9f3bb821 100644
--- a/Ink Canvas/Automation/Triggers/AnnotationModeExitTrigger.cs
+++ b/Ink Canvas/Automation/Triggers/AnnotationModeExitTrigger.cs
@@ -65,9 +65,9 @@ private void TrySubscribeInkCanvas()
mw.inkCanvas.EditingModeChanged += OnEditingModeChanged;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 批注模式退出触发器订阅 inkCanvas.EditingModeChanged 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -81,9 +81,9 @@ private void TryUnsubscribeInkCanvas()
mw.inkCanvas.EditingModeChanged -= OnEditingModeChanged;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Automation] 批注模式退出触发器退订 inkCanvas.EditingModeChanged 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Automation/Triggers/PptSlideShowEnterTrigger.cs b/Ink Canvas/Automation/Triggers/PptSlideShowEnterTrigger.cs
index 3d09ebd9..717983c4 100644
--- a/Ink Canvas/Automation/Triggers/PptSlideShowEnterTrigger.cs
+++ b/Ink Canvas/Automation/Triggers/PptSlideShowEnterTrigger.cs
@@ -83,9 +83,9 @@ private static bool IsPowerPointInSlideShow()
return true;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogCallbackException("读取 POWERPNT 进程主窗口标题判断是否处于放映", ex);
+ Helpers.LogService.LogException(ex);
}
}
return false;
diff --git a/Ink Canvas/Automation/Triggers/PptSlideShowExitTrigger.cs b/Ink Canvas/Automation/Triggers/PptSlideShowExitTrigger.cs
index 1dc6d7eb..dfd97a22 100644
--- a/Ink Canvas/Automation/Triggers/PptSlideShowExitTrigger.cs
+++ b/Ink Canvas/Automation/Triggers/PptSlideShowExitTrigger.cs
@@ -83,9 +83,9 @@ private static bool IsPowerPointInSlideShow()
return true;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogCallbackException("读取 POWERPNT 进程主窗口标题判断是否处于放映", ex);
+ Helpers.LogService.LogException(ex);
}
}
return false;
diff --git a/Ink Canvas/Automation/Triggers/PptSlideShowTrigger.cs b/Ink Canvas/Automation/Triggers/PptSlideShowTrigger.cs
index 5279cf34..1dc9d5f8 100644
--- a/Ink Canvas/Automation/Triggers/PptSlideShowTrigger.cs
+++ b/Ink Canvas/Automation/Triggers/PptSlideShowTrigger.cs
@@ -84,9 +84,9 @@ private static bool IsPowerPointInSlideShow()
return true;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogCallbackException("读取 POWERPNT 进程主窗口标题判断是否处于放映", ex);
+ Helpers.LogService.LogException(ex);
}
}
return false;
diff --git a/Ink Canvas/Controls/CanvasMediaControl.xaml.cs b/Ink Canvas/Controls/CanvasMediaControl.xaml.cs
index 0f9f241d..315a4250 100644
--- a/Ink Canvas/Controls/CanvasMediaControl.xaml.cs
+++ b/Ink Canvas/Controls/CanvasMediaControl.xaml.cs
@@ -160,9 +160,9 @@ public void PausePlayback()
_isPlaying = false;
UpdatePlayPauseGlyph();
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 暂停播放失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -179,9 +179,9 @@ public void StopPlayback()
SeekTo(TimeSpan.Zero);
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 停止播放失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -193,9 +193,9 @@ public void Shutdown()
Player.Stop();
Player.Source = null;
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 关闭媒体控件(卸载渲染钩子/停止播放/释放源)失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -299,9 +299,9 @@ private void Player_MediaEnded(object sender, RoutedEventArgs e)
{
Player.Stop();
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 播放结束时停止播放器失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
SeekTo(TimeSpan.Zero);
UpdatePlayPauseGlyph();
@@ -330,9 +330,9 @@ private void TogglePlayback()
}
UpdatePlayPauseGlyph();
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 切换播放/暂停状态失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
@@ -401,9 +401,9 @@ private void SeekTo(TimeSpan position)
{
Player.Position = position;
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 定位播放位置到 {position} 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
UpdateTimeText(position);
@@ -465,9 +465,9 @@ private void ApplySelectedSpeed()
{
Player.SpeedRatio = ratio;
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Media] 应用播放倍速 {ratio} 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Controls/DynamicNotificationControl.xaml.cs b/Ink Canvas/Controls/DynamicNotificationControl.xaml.cs
index 731dcaf1..ad3e64d2 100644
--- a/Ink Canvas/Controls/DynamicNotificationControl.xaml.cs
+++ b/Ink Canvas/Controls/DynamicNotificationControl.xaml.cs
@@ -179,9 +179,9 @@ private void ActionButton_Click(object sender, RoutedEventArgs e)
Process.Start(new ProcessStartInfo(currentMessage.ActionUrl) { UseShellExecute = true });
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Notification] 执行动态通知的动作回调/打开动作链接失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
Close();
diff --git a/Ink Canvas/Controls/Toolbar/BoardToolbar/BoardToolbarRegistry.cs b/Ink Canvas/Controls/Toolbar/BoardToolbar/BoardToolbarRegistry.cs
index 970112fb..43e1fbc6 100644
--- a/Ink Canvas/Controls/Toolbar/BoardToolbar/BoardToolbarRegistry.cs
+++ b/Ink Canvas/Controls/Toolbar/BoardToolbar/BoardToolbarRegistry.cs
@@ -447,10 +447,9 @@ public static void SaveConfigFile(string name, BoardToolbarLayoutSettings layout
}
catch (Exception innerEx)
{
- try { if (File.Exists(tmpPath)) File.Delete(tmpPath); }
- catch (Exception ex)
+ try { if (File.Exists(tmpPath)) File.Delete(tmpPath); } catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[Toolbar] BoardToolbarRegistry 原子写入失败后删除临时文件 {Path.GetFileName(tmpPath)} 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
throw new Exception($"原子写入失败: {innerEx.Message}", innerEx);
}
diff --git a/Ink Canvas/Helpers/AutoFontSizeHelper.cs b/Ink Canvas/Helpers/AutoFontSizeHelper.cs
index 2e523c21..a03d821f 100644
--- a/Ink Canvas/Helpers/AutoFontSizeHelper.cs
+++ b/Ink Canvas/Helpers/AutoFontSizeHelper.cs
@@ -418,9 +418,9 @@ private static double MeasureTextWidth(FrameworkElement fe, string text, double
{
culture = fe.Language.GetEquivalentCulture();
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[UI] 取控件语言对应的 Culture 失败,回退到当前 UI Culture: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Helpers/CapturableVideoCaptureElement.cs b/Ink Canvas/Helpers/CapturableVideoCaptureElement.cs
index 8fbe1050..4ee864e4 100644
--- a/Ink Canvas/Helpers/CapturableVideoCaptureElement.cs
+++ b/Ink Canvas/Helpers/CapturableVideoCaptureElement.cs
@@ -137,7 +137,10 @@ private static void LogCaptureDiag(string message)
LogHelper.WriteLogToFile($"[CapturableVideoCaptureElement] {message}", LogHelper.LogType.Info);
Debug.WriteLine($"[CapturableVideoCaptureElement] {message}");
}
- catch { }
+ catch (System.Exception ex)
+ {
+ Helpers.LogService.LogException(ex);
+ }
}
}
}
diff --git a/Ink Canvas/Helpers/Converters.cs b/Ink Canvas/Helpers/Converters.cs
index 860bce55..d7f83337 100644
--- a/Ink Canvas/Helpers/Converters.cs
+++ b/Ink Canvas/Helpers/Converters.cs
@@ -209,7 +209,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[UI] 几何字符串解析失败,图标将不显示: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
return null;
}
diff --git a/Ink Canvas/Helpers/DebugConsoleManager.cs b/Ink Canvas/Helpers/DebugConsoleManager.cs
index a6f5328c..3ca2cced 100644
--- a/Ink Canvas/Helpers/DebugConsoleManager.cs
+++ b/Ink Canvas/Helpers/DebugConsoleManager.cs
@@ -103,9 +103,9 @@ public static void WriteLine(string line, ConsoleColor? color = null)
Console.WriteLine(line);
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[UI] 写入调试控制台失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs
index 99dd3827..4c2d44b0 100644
--- a/Ink Canvas/Helpers/DeviceIdentifier.cs
+++ b/Ink Canvas/Helpers/DeviceIdentifier.cs
@@ -260,9 +260,9 @@ private static string GetSystemDriveVolumeSerial()
return serialNumber.ToString("X8");
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[DeviceId] 读取系统盘卷序列号失败(硬件指纹的 DISK 段将退化为其它来源): {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
return null;
@@ -380,9 +380,9 @@ private static DeviceIdInfo LoadDeviceIdFromFile(string filePath)
return info;
}
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile($"[DeviceId] 解析设备ID文件({filePath})中的 JSON 内容失败,将回退按纯文本读取: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
if (IsValidDeviceId(content))
diff --git a/Ink Canvas/Helpers/DirectShowCameraService.cs b/Ink Canvas/Helpers/DirectShowCameraService.cs
index 93b5b916..ed55ca41 100644
--- a/Ink Canvas/Helpers/DirectShowCameraService.cs
+++ b/Ink Canvas/Helpers/DirectShowCameraService.cs
@@ -208,7 +208,7 @@ public Task ProbeCameraPropertiesAsync()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Booth] 探测摄像头属性时从采集 pin 获取 IAMVideoProcAmp/IAMCameraControl 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -389,38 +389,22 @@ public void ReleaseCameraPropertyControl()
{
if (_propVideoProcAmp != null)
{
- try { Marshal.ReleaseComObject(_propVideoProcAmp); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 释放 IAMVideoProcAmp COM 引用失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Marshal.ReleaseComObject(_propVideoProcAmp); } catch (Exception ex) { LogService.LogException(ex); }
_propVideoProcAmp = null;
}
if (_propCameraControl != null)
{
- try { Marshal.ReleaseComObject(_propCameraControl); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 释放 IAMCameraControl COM 引用失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Marshal.ReleaseComObject(_propCameraControl); } catch (Exception ex) { LogService.LogException(ex); }
_propCameraControl = null;
}
if (_propSourceFilter != null)
{
- try { Marshal.ReleaseComObject(_propSourceFilter); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 释放属性探测用 source filter COM 引用失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Marshal.ReleaseComObject(_propSourceFilter); } catch (Exception ex) { LogService.LogException(ex); }
_propSourceFilter = null;
}
if (_propGraph != null)
{
- try { Marshal.ReleaseComObject(_propGraph); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 释放属性探测用 FilterGraphNoThread COM 引用失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Marshal.ReleaseComObject(_propGraph); } catch (Exception ex) { LogService.LogException(ex); }
_propGraph = null;
}
// 不清 _cameraPropStates 的 Supported 状态:调用方切换摄像头后会重新 Probe 覆盖;
@@ -493,7 +477,7 @@ public IReadOnlyList GetFrameratesFor(int width, int height)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Booth] 汇总指定分辨率({width}x{height})支持的帧率列表失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return result;
}
@@ -864,7 +848,10 @@ private void EnumerateCapabilitiesFromConfig(IAMStreamConfig config)
$"formatType={mt.formatType}",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex)
+ {
+ LogService.LogException(ex);
+ }
// === 帧率枚举策略(修复 50fps 误判) ===
// 驱动通过两种方式暴露帧率:
@@ -1294,11 +1281,7 @@ private Task StopPreviewAsyncInternal()
{
if (_mediaControl != null)
{
- try { _mediaControl.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 内部停止预览时停止 IMediaControl 图失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _mediaControl.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
}
CleanupGraph();
_isCapturing = false;
@@ -1320,11 +1303,7 @@ public void StopPreview()
if (_mediaControl != null)
{
- try { _mediaControl.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 停止摄像头预览时停止 IMediaControl 图失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _mediaControl.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
}
CleanupGraph();
@@ -1356,11 +1335,7 @@ private void CleanupGraph()
// 先断开 sample grabber 回调,避免释放过程中触发
if (_sampleGrabber != null)
{
- try { _sampleGrabber.SetCallback(null, 0); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 清理 DirectShow 图前断开 SampleGrabber 回调失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _sampleGrabber.SetCallback(null, 0); } catch (Exception ex) { LogService.LogException(ex); }
}
// 释放子 filter RCW(每个是独立的 RCW)
diff --git a/Ink Canvas/Helpers/FloatingBarThemeMarketService.cs b/Ink Canvas/Helpers/FloatingBarThemeMarketService.cs
index c18793da..c74b297f 100644
--- a/Ink Canvas/Helpers/FloatingBarThemeMarketService.cs
+++ b/Ink Canvas/Helpers/FloatingBarThemeMarketService.cs
@@ -100,14 +100,7 @@ public async Task InstallAsync(ThemeMarketEntry entry)
}
finally
{
- try
- {
- if (File.Exists(tempPath)) File.Delete(tempPath);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Theme] 清理主题安装临时包 {tempPath} 失败(将残留于临时目录): {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(tempPath)) File.Delete(tempPath); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
index c0b1be67..b9f3eff4 100644
--- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs
+++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
@@ -32,14 +32,14 @@ public class GlobalHotkeyManager : IDisposable
private bool _isMouseOverWindow = false;
private System.Windows.Threading.DispatcherTimer _mousePositionTimer;
- // M16:HotkeyConfig.json 读写与加载编排已提取至 Services.Shell.HotkeyService。
+ // HotkeyConfig.json 读写与加载编排已提取至 Services.Shell.HotkeyService。
// 以下委托由 HotkeyService 构造时接线;原位 public 方法为转发壳,对外签名与语义不变。
internal Action LoadHotkeysHandler { get; set; }
internal Action SaveHotkeysHandler { get; set; }
internal Func> ConfigHotkeysProvider { get; set; }
internal Action RegisterDefaultsHandler { get; set; }
- // M16:供 HotkeyService 读写(原私有字段,等价改写为 internal 访问器)
+ // 供 HotkeyService 读写(原私有字段,等价改写为 internal 访问器)
internal bool HotkeysShouldBeRegistered
{
get => _hotkeysShouldBeRegistered;
@@ -57,7 +57,7 @@ public GlobalHotkeyManager(MainWindow mainWindow)
// 初始化多屏幕支持
InitializeMultiScreenSupport();
- // M16:启动时确保配置文件存在的职责随配置层搬入 HotkeyService 构造函数,
+ // 启动时确保配置文件存在的职责随配置层搬入 HotkeyService 构造函数,
// 由主窗口在创建本管理器后立即创建服务(同一调用栈,时序不变)。
}
#endregion
@@ -97,7 +97,7 @@ public bool RegisterHotkey(string hotkeyName, Key key, ModifierKeys modifiers, A
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Hotkey] 注册前清理全局热键 {hotkeyName} 的旧注册失败,将直接覆盖: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -163,7 +163,7 @@ public bool RegisterPluginHotkey(string hotkeyName, Key key, ModifierKeys modifi
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Hotkey] 注册插件热键前清理同名全局热键 {hotkeyName} 失败,将直接覆盖: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_registeredHotkeys.Remove(hotkeyName);
@@ -309,7 +309,7 @@ public List GetRegisteredHotkeys()
///
/// 获取配置文件中的快捷键信息(不注册,仅用于显示)
- /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
+ /// 实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
/// 配置文件中的快捷键列表
public List GetHotkeysFromConfigFile()
@@ -319,7 +319,7 @@ public List GetHotkeysFromConfigFile()
///
/// 注册默认快捷键集合
- /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
+ /// 实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void RegisterDefaultHotkeys()
{
@@ -328,7 +328,7 @@ public void RegisterDefaultHotkeys()
///
/// 从配置文件加载快捷键
- /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
+ /// 实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void LoadHotkeysFromSettings()
{
@@ -337,7 +337,7 @@ public void LoadHotkeysFromSettings()
///
/// 保存快捷键配置到设置
- /// M16:实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
+ /// 实现已提取至 Services.Shell.HotkeyService,本方法为转发壳。
///
public void SaveHotkeysToSettings()
{
diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
index 8b22005c..ae8a4a14 100644
--- a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
+++ b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
@@ -208,7 +208,7 @@ public void Dispose()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Ink] 释放 GPU 墨迹渲染资源失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Helpers/LiquidGlassMagnifier.cs b/Ink Canvas/Helpers/LiquidGlassMagnifier.cs
index c015fb29..75458367 100644
--- a/Ink Canvas/Helpers/LiquidGlassMagnifier.cs
+++ b/Ink Canvas/Helpers/LiquidGlassMagnifier.cs
@@ -177,17 +177,13 @@ internal static void Shutdown()
{
if (!host.HasExited)
{
- try { host.StandardInput.Close(); } catch (Exception ex) { LogHelper.WriteLogToFile($"[LiquidGlass] 关闭放大镜子进程标准输入失败: {ex.Message}", LogHelper.LogType.Info); }
- try { host.Kill(); } catch (Exception ex) { LogHelper.WriteLogToFile($"[LiquidGlass] 强制结束放大镜子进程失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { host.StandardInput.Close(); } catch (Exception ex) { LogService.LogException(ex); }
+ try { host.Kill(); } catch (Exception ex) { LogService.LogException(ex); }
host.WaitForExit(1000);
}
}
- catch (Exception ex)
- {
- /* 退出期 */
- LogHelper.WriteLogToFile($"[LiquidGlass] 关闭放大镜子进程(标准输入/结束/等待退出)失败: {ex.Message}", LogHelper.LogType.Info);
- }
- try { host.Dispose(); } catch (Exception ex) { LogHelper.WriteLogToFile($"[LiquidGlass] 释放放大镜子进程对象失败: {ex.Message}", LogHelper.LogType.Info); }
+ catch (Exception ex) { /* 退出期 */ LogService.LogException(ex); }
+ try { host.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
LogHelper.WriteLogToFile("[LiquidGlass] 放大镜辅助进程已关闭", LogHelper.LogType.Info);
}
@@ -247,7 +243,7 @@ private static bool EnsureHost()
{
if (!_available)
{
- try { proc.Kill(); } catch (Exception ex) { LogHelper.WriteLogToFile($"[LiquidGlass] 启动后判定放大镜已不可用,结束刚拉起的子进程失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { proc.Kill(); } catch (Exception ex) { LogService.LogException(ex); }
return false;
}
_host = proc;
diff --git a/Ink Canvas/Helpers/LocalizationHelper.cs b/Ink Canvas/Helpers/LocalizationHelper.cs
index 2b3fbf55..559f1c44 100644
--- a/Ink Canvas/Helpers/LocalizationHelper.cs
+++ b/Ink Canvas/Helpers/LocalizationHelper.cs
@@ -61,7 +61,7 @@ internal static void SyncCommonResources()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 应用 ToggleSwitch 本地化失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -106,7 +106,7 @@ private static void PatchModernStrings(string onText, string offText)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 反射替换 iNKORE Strings 的 ResourceManager 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -161,7 +161,7 @@ private static void UpdateAllToggleSwitches(string onText, string offText)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 遍历窗口更新 ToggleSwitch 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -185,7 +185,7 @@ private static void UpdateToggleSwitchesInLogicalTree(DependencyObject parent, s
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 遍历逻辑树更新 ToggleSwitch 失败 (node={parent?.GetType().Name}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -211,7 +211,7 @@ private static void UpdateSingleToggleSwitch(ToggleSwitch ts, string onText, str
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 更新单个 ToggleSwitch 文本失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -228,7 +228,7 @@ internal static void BindToggleSwitchesInWindow(System.Windows.Window window)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 为设置窗口绑定 ToggleSwitch 文本失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -278,7 +278,7 @@ private static CultureInfo CreateCustomCulture(string name)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 按名称创建自定义 Culture 失败 (name={name}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -301,7 +301,7 @@ private static CultureInfo CreateCustomCulture(string name)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 反射克隆父 Culture 失败,将回退 zh-CN (name={name}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return CultureInfo.GetCultureInfo("zh-CN");
@@ -409,7 +409,7 @@ private static Dictionary LoadEmbeddedResource(Assembly asm, str
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 加载内嵌 .resources 失败 ({resName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
if (result.Count > 0)
@@ -429,7 +429,7 @@ private static Dictionary LoadEmbeddedResource(Assembly asm, str
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 加载程序集内嵌 .resx 失败 ({resxName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
if (result.Count > 0)
@@ -452,7 +452,7 @@ private static Dictionary LoadEmbeddedResource(Assembly asm, str
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[I18n] 从程序目录加载 .resx 失败 ({className}.{cultureName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_embeddedCache[cacheKey] = result;
diff --git a/Ink Canvas/Helpers/LogService.cs b/Ink Canvas/Helpers/LogService.cs
new file mode 100644
index 00000000..84e316cb
--- /dev/null
+++ b/Ink Canvas/Helpers/LogService.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 重构期吞异常治理用最小日志设施。
+ /// 写入 %AppData%/InkCanvasForClass/logs/refactor.log,单文件上限 1MB,滚动保留 3 个。
+ ///
+ internal static class LogService
+ {
+ private const long MaxFileSizeBytes = 1024 * 1024;
+ private const int RetainedFiles = 3;
+ private static readonly object WriteLock = new object();
+
+ private static string LogDirectory =>
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "InkCanvasForClass", "logs");
+
+ private static string LogFile => Path.Combine(LogDirectory, "refactor.log");
+
+ public static void LogException(Exception ex, [CallerMemberName] string member = "")
+ {
+ try
+ {
+ lock (WriteLock)
+ {
+ Directory.CreateDirectory(LogDirectory);
+ RollIfNeeded();
+ File.AppendAllText(LogFile,
+ $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{member}] {ex}{Environment.NewLine}");
+ }
+ }
+ catch
+ {
+ // 日志设施自身失败时静默放弃,绝不影响主流程。
+ }
+ }
+
+ private static void RollIfNeeded()
+ {
+ if (!File.Exists(LogFile) || new FileInfo(LogFile).Length < MaxFileSizeBytes) return;
+ for (var i = RetainedFiles - 1; i >= 1; i--)
+ {
+ var older = $"{LogFile}.{i}";
+ var newer = $"{LogFile}.{i + 1}";
+ if (File.Exists(newer)) File.Delete(newer);
+ if (File.Exists(older)) File.Move(older, newer);
+ }
+ File.Move(LogFile, $"{LogFile}.1");
+ }
+ }
+}
diff --git a/Ink Canvas/Helpers/MemoryBreakdownHelper.cs b/Ink Canvas/Helpers/MemoryBreakdownHelper.cs
index eed574d8..060dd87e 100644
--- a/Ink Canvas/Helpers/MemoryBreakdownHelper.cs
+++ b/Ink Canvas/Helpers/MemoryBreakdownHelper.cs
@@ -441,7 +441,7 @@ private static int ReadInstanceCollectionCount(object instance, string fieldName
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 反射读取实例集合计数失败 (field={fieldName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return 0;
}
@@ -461,7 +461,7 @@ private static T ReadInstanceField(object instance, string fieldName)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 反射读取实例字段失败 (field={fieldName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return default;
}
@@ -514,7 +514,7 @@ private static long ReadTimeMachineCount(Ink_Canvas.MainWindow mw, out string be
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 读取 TimeMachine 字段失败 (field={f.Name}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
return best;
@@ -557,7 +557,7 @@ private static long ReadInkSmoothingSampleCount(Ink_Canvas.MainWindow mw)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 读取墨迹平滑采样数失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return 0;
}
@@ -585,7 +585,7 @@ private static void AppendAssembliesSection(StringBuilder sb)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 读取程序集大小失败 (asm={asm.GetName().Name}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
sb2.AppendLine($" {asm.GetName().Name,-50} v{asm.GetName().Version?.ToString() ?? "?"} ({bytes / 1024} KB)");
@@ -655,10 +655,7 @@ private static void CountStrokeValue(object value, ref long strokeCount, ref lon
foreach (System.Windows.Ink.Stroke stroke in collection)
{
strokeCount++;
- try { pointCount += stroke.StylusPoints.Count; } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Memory] 读取 Stroke 采样点数失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { pointCount += stroke.StylusPoints.Count; } catch (Exception ex) { LogService.LogException(ex); }
}
return;
}
@@ -666,10 +663,7 @@ private static void CountStrokeValue(object value, ref long strokeCount, ref lon
if (value is System.Windows.Ink.Stroke strokeValue)
{
strokeCount++;
- try { pointCount += strokeValue.StylusPoints.Count; } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Memory] 读取 Stroke 采样点数失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { pointCount += strokeValue.StylusPoints.Count; } catch (Exception ex) { LogService.LogException(ex); }
return;
}
@@ -766,7 +760,7 @@ private static int ReadSettingsPageCacheCount(Ink_Canvas.Windows.SettingsViews.S
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 读取设置窗口页面缓存数量失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return 0;
}
@@ -781,7 +775,7 @@ private static long ReadStaticField(Type type, string fieldName)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Memory] 读取静态集合字段失败 (type={type?.Name}, field={fieldName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return 0;
}
diff --git a/Ink Canvas/Helpers/MessageBoxHelper.cs b/Ink Canvas/Helpers/MessageBoxHelper.cs
index e6d48aa3..e89c9347 100644
--- a/Ink Canvas/Helpers/MessageBoxHelper.cs
+++ b/Ink Canvas/Helpers/MessageBoxHelper.cs
@@ -1,3 +1,4 @@
+using iNKORE.UI.WPF.Modern;
using iNKORE.UI.WPF.Modern.Common;
using iNKORE.UI.WPF.Modern.Common.IconKeys;
using System;
@@ -94,12 +95,13 @@ public static MessageBoxResult Show(
}
var owner = GetDefaultOwner(context);
- if (owner != null && owner.IsLoaded && owner.IsVisible)
- {
- return MessageBox.Show(owner, messageBoxText, caption, button, icon, defaultResult);
- }
+ if (owner != null && (!owner.IsLoaded || !owner.IsVisible)) owner = null;
- return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult);
+ // 自行实例化(镜像库内静态 Show 的初始化逻辑),以便锁定弹窗主题:
+ // 库的静态 Show 拿不到实例,新窗口只能按应用级 ApplicationTheme 渲染,
+ // 而白板逻辑会将其强制设为 Dark 且不恢复,导致浅色窗口中弹出深色对话框。
+ var box = CreateThemedMessageBox(owner, messageBoxText, caption, button, icon);
+ return box.ShowDialog();
}
public static MessageBoxResult Show(
@@ -145,12 +147,12 @@ public static async Task ShowAsync(
}
var owner = GetDefaultOwner(context);
- if (owner != null && owner.IsLoaded && owner.IsVisible)
- {
- return await MessageBox.ShowAsync(owner, messageBoxText, caption, button, icon, defaultResult);
- }
+ if (owner != null && (!owner.IsLoaded || !owner.IsVisible)) owner = null;
- return await MessageBox.ShowAsync(messageBoxText, caption, button, icon, defaultResult);
+ // 与同步 Show 一致:库的 ShowAsync 内部也是 Dispatcher.Invoke(同步 Show),
+ // 模态语义不变,只是改为自行实例化以锁定主题。
+ var box = CreateThemedMessageBox(owner, messageBoxText, caption, button, icon);
+ return box.ShowDialog();
}
public static Task ShowAsync(
@@ -280,10 +282,7 @@ public static MessageBox ShowAtNonBlocking(
};
autoCloseTimer.Tick += (s, e) =>
{
- try { box.Close(MessageBoxResult.None); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UI] 自动关闭消息框失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { box.Close(MessageBoxResult.None); } catch (Exception ex) { LogService.LogException(ex); }
};
autoCloseTimer.Start();
}
@@ -315,6 +314,58 @@ void Complete()
return box;
}
+ ///
+ /// 构建 MessageBox 实例(镜像库内静态 Show 的初始化逻辑),并将弹窗主题锁定为
+ /// Owner 窗口的实际主题;Owner 缺失或其主题为 Default 时回落到用户设置的有效主题。
+ ///
+ private static MessageBox CreateThemedMessageBox(
+ Window owner,
+ string messageBoxText, string caption,
+ MessageBoxButton button, MessageBoxImage icon)
+ {
+ var box = new MessageBox
+ {
+ Owner = owner,
+ Content = messageBoxText,
+ Caption = caption ?? string.Empty,
+ MessageBoxButtons = button,
+ IconSource = CreateIconSource(icon),
+ WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen,
+ };
+
+ if (MessageBox.MakeSound)
+ {
+ box.SystemSoundOnLoaded = CreateSystemSound(icon);
+ }
+
+ ApplyEffectiveTheme(box, owner);
+ return box;
+ }
+
+ ///
+ /// 将弹窗主题锁定为 Owner 窗口的实际主题(窗口级 RequestedTheme 解析结果),
+ /// 使其不随应用级 ApplicationTheme 漂移(白板进出会将其强制设为 Dark 且不恢复)。
+ ///
+ private static void ApplyEffectiveTheme(MessageBox box, Window owner)
+ {
+ try
+ {
+ var theme = owner != null ? ThemeManager.GetActualTheme(owner) : ElementTheme.Default;
+ if (theme == ElementTheme.Default)
+ {
+ theme = ThemeHelper.GetEffectiveTheme(Windows.SettingsViews.Helpers.SettingsManager.Settings);
+ }
+ if (theme != ElementTheme.Default)
+ {
+ ThemeManager.SetRequestedTheme(box, theme);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogService.LogException(ex);
+ }
+ }
+
///
/// 构建以 Manual 方式定位到屏幕指定位置(DIP)的 MessageBox 实例。
/// 镜像 Owner 的置顶状态(置顶窗口的弹窗需同置顶才能保持可见),
@@ -351,6 +402,7 @@ private static MessageBox CreatePositionedMessageBox(
box.SystemSoundOnLoaded = CreateSystemSound(icon);
}
+ ApplyEffectiveTheme(box, owner);
box.ContentRendered += (s, e) => ClampToWorkArea(box);
return box;
}
diff --git a/Ink Canvas/Helpers/MicroBinClient.cs b/Ink Canvas/Helpers/MicroBinClient.cs
index a0ee83f8..3ea5c7ef 100644
--- a/Ink Canvas/Helpers/MicroBinClient.cs
+++ b/Ink Canvas/Helpers/MicroBinClient.cs
@@ -168,8 +168,7 @@ private static string ParsePasteUrl(string responseBody, string serverUrl)
}
catch (Exception ex)
{
- // 非 JSON 响应属预期分支(纯 URL / HTML 页面),仅留痕便于排查上传异常
- LogHelper.WriteLogToFile($"[Upload] 上传响应不是 JSON,按纯文本/HTML 继续解析: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
// 纯 URL
diff --git a/Ink Canvas/Helpers/NameRosterManager.cs b/Ink Canvas/Helpers/NameRosterManager.cs
index 59e455a7..1530d9c5 100644
--- a/Ink Canvas/Helpers/NameRosterManager.cs
+++ b/Ink Canvas/Helpers/NameRosterManager.cs
@@ -38,22 +38,8 @@ public static (string namesContent, string replaceContent) ReadCurrentFiles()
{
string names = "";
string replace = "";
- try
- {
- if (File.Exists(NamesFilePath)) names = File.ReadAllText(NamesFilePath);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Roster] 读取当前名单文件 Names.txt 失败,保存为方案时该部分将为空: {ex.Message}", LogHelper.LogType.Info);
- }
- try
- {
- if (File.Exists(ReplaceFilePath)) replace = File.ReadAllText(ReplaceFilePath);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Roster] 读取当前替换表文件 Replace.txt 失败,保存为方案时该部分将为空: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(NamesFilePath)) names = File.ReadAllText(NamesFilePath); } catch (Exception ex) { LogService.LogException(ex); }
+ try { if (File.Exists(ReplaceFilePath)) replace = File.ReadAllText(ReplaceFilePath); } catch (Exception ex) { LogService.LogException(ex); }
return (names, replace);
}
diff --git a/Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs b/Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs
index ba4eba37..17264bdc 100644
--- a/Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs
+++ b/Ink Canvas/Helpers/Ppt/PPTROTConnectionHelper.cs
@@ -116,14 +116,15 @@ public static Microsoft.Office.Interop.PowerPoint.Application TryConnectViaROT(b
{
var _ = pptApp.Name;
}
- catch (COMException)
+ catch (COMException ex)
{
+ LogService.LogException(ex);
}
return pptApp;
}
}
- catch (COMException) { }
- catch (InvalidCastException) { }
+ catch (COMException ex) { LogService.LogException(ex); }
+ catch (InvalidCastException ex) { LogService.LogException(ex); }
if (isSupportWPS)
{
@@ -136,14 +137,15 @@ public static Microsoft.Office.Interop.PowerPoint.Application TryConnectViaROT(b
{
var _ = wpsApp.Name;
}
- catch (COMException)
+ catch (COMException ex)
{
+ LogService.LogException(ex);
}
return wpsApp;
}
}
- catch (COMException) { }
- catch (InvalidCastException) { }
+ catch (COMException ex) { LogService.LogException(ex); }
+ catch (InvalidCastException ex) { LogService.LogException(ex); }
}
return null;
diff --git a/Ink Canvas/Helpers/Ppt/ROTPPTManager.cs b/Ink Canvas/Helpers/Ppt/ROTPPTManager.cs
index 17e3c0ed..0a4a2a45 100644
--- a/Ink Canvas/Helpers/Ppt/ROTPPTManager.cs
+++ b/Ink Canvas/Helpers/Ppt/ROTPPTManager.cs
@@ -764,7 +764,7 @@ private void TryRaiseSlideShowBeginOnConnect()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 连接后补发 SlideShowBegin 时访问 SlideShowWindows 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
finally
{
@@ -808,7 +808,7 @@ private void UnbindEvents()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 解绑 COM 事件(SlideShowNextSlide/Begin/End/PresentationBeforeClose)失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_bindingEvents = false;
@@ -816,7 +816,7 @@ private void UnbindEvents()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] UnbindEvents 重置事件绑定标记失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -998,12 +998,13 @@ private void SafeFinalReleaseComObject(object comObject, string objectName)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 终态释放回退为循环 ReleaseComObject 失败({objectName}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
- catch (InvalidComObjectException)
+ catch (InvalidComObjectException ex)
{
+ LogService.LogException(ex);
}
catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx))
{
@@ -1235,7 +1236,7 @@ private void OnPresentationOpen(Presentation pres)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] PresentationOpen 时读取 ActivePresentation 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1278,7 +1279,7 @@ private void OnPresentationBeforeClose(Presentation pres, ref bool cancel)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] PresentationBeforeClose 时解绑 COM 事件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_bindingEvents = false;
@@ -2436,7 +2437,7 @@ private bool HasWpsWindowInTaskbar()
}
catch (Exception ex)
{
- LogCallbackException("EnumWindows 回调中读取单个窗口信息失败", ex);
+ LogService.LogException(ex);
}
return true;
}, IntPtr.Zero);
@@ -2575,23 +2576,19 @@ private void SafeTerminateWpsProcess()
// 确保清理状态
if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
{
- try { Marshal.ReleaseComObject(CurrentSlide); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 结束WPS进程后释放 CurrentSlide 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { Marshal.ReleaseComObject(CurrentSlide); } catch (Exception ex) { LogService.LogException(ex); }
}
if (CurrentSlides != null && Marshal.IsComObject(CurrentSlides))
{
- try { Marshal.ReleaseComObject(CurrentSlides); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 结束WPS进程后释放 CurrentSlides 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { Marshal.ReleaseComObject(CurrentSlides); } catch (Exception ex) { LogService.LogException(ex); }
}
if (CurrentPresentation != null && Marshal.IsComObject(CurrentPresentation))
{
- try { Marshal.ReleaseComObject(CurrentPresentation); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 结束WPS进程后释放 CurrentPresentation 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { Marshal.ReleaseComObject(CurrentPresentation); } catch (Exception ex) { LogService.LogException(ex); }
}
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
{
- try { Marshal.ReleaseComObject(PPTApplication); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 结束WPS进程后释放 PPTApplication 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { Marshal.ReleaseComObject(PPTApplication); } catch (Exception ex) { LogService.LogException(ex); }
}
CurrentSlide = null;
@@ -2754,7 +2751,7 @@ private WpsWindowInfo GetWindowInfo(IntPtr hWnd)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] GetWindowInfo 取进程名失败(pid={processId},进程可能已退出): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return windowInfo;
@@ -2972,7 +2969,7 @@ public IntPtr GetPPTHwnd()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] GetPPTHwnd 备用路径读取 pres.FullName/PPTApplication.Name 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -3009,13 +3006,13 @@ private IntPtr GetPPTHwndFromSlideShowWindow(object pptSlideShowWindowObj)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] GetPPTHwndFromSlideShowWindow 走 dynamic 读 HWND 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] GetPPTHwndFromSlideShowWindow 强类型转换或读取 HWND 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return hwnd;
diff --git a/Ink Canvas/Helpers/ProcessProtectionManager.cs b/Ink Canvas/Helpers/ProcessProtectionManager.cs
index bdfaf799..ceb6a3ad 100644
--- a/Ink Canvas/Helpers/ProcessProtectionManager.cs
+++ b/Ink Canvas/Helpers/ProcessProtectionManager.cs
@@ -72,7 +72,10 @@ public static void SetEnabled(bool enabled)
{
LogHelper.WriteLogToFile($"ProcessProtectionManager.SetEnabled 后台执行失败: {ex.Message}", LogHelper.LogType.Warning);
}
- catch { }
+ catch (Exception ex2)
+ {
+ LogService.LogException(ex2);
+ }
}
});
}
@@ -100,10 +103,7 @@ public static void ReleaseLocksForPath(string targetPath)
if (_lockedDirs.TryGetValue(d, out var handle))
{
_lockedDirs.Remove(d);
- try { handle.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ProcGuard] 释放目录锁句柄失败 (path={d}): {ex.Message}", LogHelper.LogType.Info);
- }
+ try { handle.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
}
}
@@ -117,10 +117,7 @@ public static void ReleaseLocksForPath(string targetPath)
if (_lockedFiles.TryGetValue(fp, out var fs))
{
_lockedFiles.Remove(fp);
- try { fs.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ProcGuard] 释放文件锁句柄失败 (path={fp}): {ex.Message}", LogHelper.LogType.Info);
- }
+ try { fs.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -228,7 +225,7 @@ public static void WithWriteAccess(string targetPath, Action action)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 写入门控结束后按路径链恢复文件锁失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
return;
@@ -295,7 +292,7 @@ public static void WithWriteAccess(string targetPath, Action action)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 写入门控结束后恢复文件锁失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
Interlocked.Exchange(ref _writeGate, 0);
@@ -380,9 +377,7 @@ private static void Enable(bool rescanRoot, IEnumerable rescanDirs)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[ProcGuard] 建立文件/目录锁失败 (rescanRoot={rescanRoot}): {ex.Message}",
- LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -446,7 +441,7 @@ private static void LockDirectoryRecursive(string root)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 递归锁定目录失败 (root={root}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -483,7 +478,7 @@ private static void LockFilesRecursive(string root)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 递归锁定文件失败 (root={root}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -504,7 +499,7 @@ private static void LockFile(string filePath)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 锁定文件失败(可能被占用): {filePath} - {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
@@ -529,7 +524,7 @@ private static void LockDirectory(string dirPath)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 锁定目录失败: {dirPath} - {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
@@ -576,7 +571,7 @@ private static List GetDirChainToRoot(string path)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 计算到应用根目录的路径链失败 (path={path}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return list;
}
@@ -604,7 +599,7 @@ private static bool IsExcludedPath(string path)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 判断路径是否属于排除目录失败 (path={path}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return false;
}
diff --git a/Ink Canvas/Helpers/UIAccessHelper.cs b/Ink Canvas/Helpers/UIAccessHelper.cs
index f7bbad8b..ca20b3ba 100644
--- a/Ink Canvas/Helpers/UIAccessHelper.cs
+++ b/Ink Canvas/Helpers/UIAccessHelper.cs
@@ -1197,7 +1197,7 @@ private static string GetExecutablePathForRelaunch()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 以 UIAccess/普通用户身份重启前读取当前主模块路径失败,将回退到 Environment.ProcessPath: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
if (!string.IsNullOrEmpty(mainModulePath) && System.IO.File.Exists(mainModulePath))
diff --git a/Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs b/Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs
index 9ba01aee..16af78c9 100644
--- a/Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs
+++ b/Ink Canvas/Helpers/UiShell/SliderTouchHelper.cs
@@ -247,7 +247,7 @@ private static void UpdateSliderValueFromPositionFallback(Slider slider, Point p
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[UI] 按触摸位置更新滑块值失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Helpers/UiShell/ThemeHelper.cs b/Ink Canvas/Helpers/UiShell/ThemeHelper.cs
index 431f18bf..e4d4bac9 100644
--- a/Ink Canvas/Helpers/UiShell/ThemeHelper.cs
+++ b/Ink Canvas/Helpers/UiShell/ThemeHelper.cs
@@ -25,7 +25,7 @@ static ThemeHelper()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 订阅系统外观偏好变化事件失败,系统深浅色/强调色将不再自动跟随: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -67,7 +67,7 @@ public static Color GetSystemAccentColor()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 读取 DWM 强调色注册表值失败,改用下一级回退来源: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -92,7 +92,7 @@ public static Color GetSystemAccentColor()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 读取资源管理器 Accent 注册表值失败,改用窗口玻璃色作为最终来源: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -142,7 +142,7 @@ public static bool IsSystemThemeLight()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 读取系统深浅色注册表值(AppsUseLightTheme)失败,按浅色处理: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return true;
}
diff --git a/Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs b/Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs
index 8aaff8d1..0dd7b9b7 100644
--- a/Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs
+++ b/Ink Canvas/Helpers/UiShell/WindowBackdropHelper.cs
@@ -105,8 +105,10 @@ private static void ApplyNativeBackdrop(Window window, BackdropType backdropType
SyncWindowDarkMode(window);
}
- catch
+ catch (Exception ex)
{
+ // DWM 背景应用为尽力而为的视觉增强,失败不影响窗口功能,但需留可诊断记录。
+ LogService.LogException(ex);
}
}
@@ -130,8 +132,10 @@ private static void SyncWindowDarkMode(Window window)
BackdropHelper.RemoveDarkMode(window);
}
}
- catch
+ catch (Exception ex)
{
+ // DWM 深色模式同步为尽力而为的视觉增强,失败不影响窗口功能,但需留可诊断记录。
+ LogService.LogException(ex);
}
}
@@ -161,8 +165,10 @@ private static void AttachReapplyAfterShow(Window window, BackdropType backdropT
};
window.Loaded += onLoaded;
}
- catch
+ catch (Exception ex)
{
+ // 重应用挂载失败仅意味着系统背景不在首帧后刷新,不影响既有背景,但需留可诊断记录。
+ LogService.LogException(ex);
}
}
@@ -182,8 +188,10 @@ private static bool TrySetWindowHelperBackdrop(Window window, BackdropType backd
method.Invoke(null, new object[] { window, backdropType });
return true;
}
- catch
+ catch (Exception ex)
{
+ // 反射探测失败按"路径不可用"回落(返回 false),属预期分支,但仍记录以便排查库版本漂移。
+ LogService.LogException(ex);
return false;
}
}
@@ -206,8 +214,10 @@ private static bool TryGetWindowHelperBackdropType(Window window, out BackdropTy
return true;
}
}
- catch
+ catch (Exception ex)
{
+ // 反射探测失败按"路径不可用"回落(返回 false + None),属预期分支,但仍记录以便排查库版本漂移。
+ LogService.LogException(ex);
}
return false;
diff --git a/Ink Canvas/Helpers/Update/AutoUpdateHelper.cs b/Ink Canvas/Helpers/Update/AutoUpdateHelper.cs
index b1d55534..4e4df612 100644
--- a/Ink Canvas/Helpers/Update/AutoUpdateHelper.cs
+++ b/Ink Canvas/Helpers/Update/AutoUpdateHelper.cs
@@ -74,11 +74,7 @@ public static void RequestCancelDownload()
{
foreach (var cts in _activeDownloadSessions)
{
- try { cts?.Cancel(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Update] 请求取消下载时取消 CancellationTokenSource 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { cts?.Cancel(); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -99,11 +95,7 @@ private static void EndDownloadSession(CancellationTokenSource cts)
{
_activeDownloadSessions.Remove(cts);
}
- try { cts?.Dispose(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Update] 释放下载会话 CancellationTokenSource 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { cts?.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
}
public static bool IsX64UpdatePackageSelected()
@@ -1310,11 +1302,7 @@ public static async Task DownloadSetupFileWithFallback(string version, Lis
return true;
}
// zip 不存在则回退下载,并清理状态文件以避免下次再误判。
- try { File.Delete(statusFilePath); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Update] 删除失效的下载状态文件 {statusFilePath} 失败,下次检查可能仍误判为已下载: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { File.Delete(statusFilePath); } catch (Exception ex) { LogService.LogException(ex); }
LogHelper.WriteLogToFile("AutoUpdate | 状态文件存在但 zip 缺失,重新下载", LogHelper.LogType.Warning);
}
@@ -1624,11 +1612,7 @@ public static async Task DownloadFile(string fileUrl, string destinationPa
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载已被用户取消", LogHelper.LogType.Warning);
if (File.Exists(tempPath))
{
- try { File.Delete(tempPath); }
- catch (Exception deleteEx)
- {
- LogHelper.WriteLogToFile($"[Update] 取消分块{block.Index}下载后删除临时分块文件 {tempPath} 失败: {deleteEx.Message}", LogHelper.LogType.Info);
- }
+ try { File.Delete(tempPath); } catch (Exception ex2) { LogService.LogException(ex2); }
}
cts.Cancel();
return;
@@ -1800,14 +1784,7 @@ private static void TryDeleteFileWithRetry(string path, int maxRetry = 3)
for (int i = 0; i < maxRetry; i++)
{
try { File.Delete(path); return; }
- catch (IOException)
- {
- try { Thread.Sleep(500); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Update] 删除文件 {path} 占用重试时的等待被打断,放弃继续重试: {ex.Message}", LogHelper.LogType.Info);
- }
- }
+ catch (IOException) { try { Thread.Sleep(500); } catch (Exception ex) { LogService.LogException(ex); } }
catch { return; }
}
}
@@ -1873,11 +1850,7 @@ private static async Task DownloadSingleThread(string fileUrl, string dest
{
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载已被取消", LogHelper.LogType.Warning);
progressCallback?.Invoke(0, "下载已取消");
- try { if (File.Exists(destinationPath)) File.Delete(destinationPath); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Update] 取消单线程下载后删除半成品文件 {destinationPath} 失败(下次更新会重新下载): {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(destinationPath)) File.Delete(destinationPath); } catch (Exception ex) { LogService.LogException(ex); }
SetDownloadFailure(DownloadFailureReason.Cancelled);
return false;
}
@@ -1955,7 +1928,7 @@ public static void InstallNewVersionApp(string version, bool isInSilence)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Update] 读取进程保护当前开关状态失败,将按“未开启”处理并继续安装: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -2140,7 +2113,7 @@ public static void InstallNewVersionApp(string version, bool isInSilence)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Update] 安装结束后恢复进程保护开关状态失败(可能残留禁用状态): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Helpers/Update/TelemetryUploader.cs b/Ink Canvas/Helpers/Update/TelemetryUploader.cs
index 6210e517..696589ac 100644
--- a/Ink Canvas/Helpers/Update/TelemetryUploader.cs
+++ b/Ink Canvas/Helpers/Update/TelemetryUploader.cs
@@ -236,7 +236,7 @@ private static string GetTelemetryToken()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Upload] 向上查找遥测令牌文件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return string.Empty;
diff --git a/Ink Canvas/Helpers/VstoRegistrationHelper.cs b/Ink Canvas/Helpers/VstoRegistrationHelper.cs
index 1f59587d..825c95a8 100644
--- a/Ink Canvas/Helpers/VstoRegistrationHelper.cs
+++ b/Ink Canvas/Helpers/VstoRegistrationHelper.cs
@@ -107,10 +107,7 @@ public static void CleanupRegistry()
LogHelper.WriteLogToFile("VSTO 旧注册表项已清理", LogHelper.LogType.Trace);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[PPT] 清理 VSTO 旧注册表项 {AddInKeyName} 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
public static bool Unregister()
diff --git a/Ink Canvas/Helpers/WindowsNotificationHelper.cs b/Ink Canvas/Helpers/WindowsNotificationHelper.cs
index c309205a..54357fb6 100644
--- a/Ink Canvas/Helpers/WindowsNotificationHelper.cs
+++ b/Ink Canvas/Helpers/WindowsNotificationHelper.cs
@@ -41,7 +41,7 @@ public static void ShowToast(NotificationMessage message)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Notify] 弹出系统通知({message?.Type})失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -61,7 +61,7 @@ private static void ShowBalloonForWin7(NotificationMessage message)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Notify] 通过任务栏气泡显示通知({message?.Type})失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
}
diff --git a/Ink Canvas/IPC/PPTAgentPipeClient.cs b/Ink Canvas/IPC/PPTAgentPipeClient.cs
index 7a637818..53f13855 100644
--- a/Ink Canvas/IPC/PPTAgentPipeClient.cs
+++ b/Ink Canvas/IPC/PPTAgentPipeClient.cs
@@ -280,8 +280,7 @@ private void SetConnected(bool connected)
private void ClosePipe()
{
- try { _pipe?.Dispose(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT-Agent] 关闭并释放命名管道失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { _pipe?.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
_pipe = null;
SetConnected(false);
}
diff --git a/Ink Canvas/Ink/WinRT/WetInkOverlayWindow.cs b/Ink Canvas/Ink/WinRT/WetInkOverlayWindow.cs
index 0e92c938..fb546e71 100644
--- a/Ink Canvas/Ink/WinRT/WetInkOverlayWindow.cs
+++ b/Ink Canvas/Ink/WinRT/WetInkOverlayWindow.cs
@@ -255,34 +255,22 @@ public void Dispose()
if (_compositionVisual != null)
{
- try { _compositionVisual.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 释放合成视觉对象失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _compositionVisual.Dispose(); } catch (Exception ex) { Helpers.LogService.LogException(ex); }
_compositionVisual = null;
}
if (_compositionTarget != null)
{
- try { _compositionTarget.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 释放合成目标失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _compositionTarget.Dispose(); } catch (Exception ex) { Helpers.LogService.LogException(ex); }
_compositionTarget = null;
}
if (_compositionDevice3 != null)
{
- try { _compositionDevice3.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 释放合成设备(D3D11)失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _compositionDevice3.Dispose(); } catch (Exception ex) { Helpers.LogService.LogException(ex); }
_compositionDevice3 = null;
}
if (_compositionDevice != null)
{
- try { _compositionDevice.Dispose(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 释放合成设备失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _compositionDevice.Dispose(); } catch (Exception ex) { Helpers.LogService.LogException(ex); }
_compositionDevice = null;
}
diff --git a/Ink Canvas/Ink/WinRT/WinRTInkHost.cs b/Ink Canvas/Ink/WinRT/WinRTInkHost.cs
index 511a4000..4321b328 100644
--- a/Ink Canvas/Ink/WinRT/WinRTInkHost.cs
+++ b/Ink Canvas/Ink/WinRT/WinRTInkHost.cs
@@ -403,10 +403,7 @@ public void Dispose()
_independentInput.PointerLost -= _onPointerLost;
_independentInput.PointerExiting -= _onPointerExiting;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 取消输入源事件订阅失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
_independentInput = null;
}
@@ -418,10 +415,7 @@ public void Dispose()
_presenter.StrokeInput.StrokeCanceled -= _onStrokeCanceled;
_presenter.StrokesCollected -= _onStrokesCollected;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 取消 Presenter 事件订阅失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
_presenter = null;
}
@@ -440,10 +434,7 @@ public void Dispose()
if (_host != null)
{
try { Marshal.FinalReleaseComObject(_host); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[WinRTInk] 释放 InkDesktopHost COM 对象失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
_host = null;
}
}
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index 793ce2c9..955b9d2b 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -54,7 +54,7 @@ public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
private GlobalHotkeyManager _globalHotkeyManager;
internal GlobalHotkeyManager GlobalHotkeyManagerInstance => _globalHotkeyManager;
- // 全局热键配置服务(M16):HotkeyConfig.json 读写与加载编排,随管理器一并创建
+ // 全局热键配置服务:HotkeyConfig.json 读写与加载编排,随管理器一并创建
private Services.Shell.HotkeyService _hotkeyService;
// PPT PageUp/PageDown 低级键盘钩子
@@ -838,7 +838,7 @@ 处于 PPT 放映内:BtnPPTSlideShowEnd.Visibility
Dispatcher.BeginInvoke(new Action(CheckAndShowOobe), DispatcherPriority.Loaded);
- // M11:设置存储服务(寄生提取),供各 partial 中的转发壳使用
+ // 设置存储服务(寄生提取),供各 partial 中的转发壳使用
_settingsStore = new Services.SettingsStore();
}
@@ -1914,7 +1914,7 @@ await Dispatcher.BeginInvoke(new Action(async () =>
catch (Exception ex)
{
// 走到这里说明验证流程中途失败且未置位放行标志,窗口会表现为"点关闭没反应"
- LogHelper.WriteLogToFile($"[Exit] 退出密码验证流程异常,本次关闭未放行: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
finally
{
@@ -1926,7 +1926,7 @@ await Dispatcher.BeginInvoke(new Action(async () =>
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Exit] 检查是否需要退出密码验证失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp)
@@ -2170,7 +2170,7 @@ public async void AutoUpdate()
{
LogHelper.WriteLogToFile($"AutoUpdate | 暂停期已过,恢复自动更新检查");
Settings.Startup.AutoUpdatePauseUntilDate = "";
- try { await Dispatcher.InvokeAsync(() => SaveSettingsToFile()); } catch (TaskCanceledException) { } catch (ObjectDisposedException) { }
+ try { await Dispatcher.InvokeAsync(() => SaveSettingsToFile()); } catch (TaskCanceledException ex) { LogService.LogException(ex); } catch (ObjectDisposedException ex) { LogService.LogException(ex); }
}
}
}
@@ -2201,8 +2201,8 @@ await Dispatcher.InvokeAsync(() =>
updateCheckRetryCount = 0;
});
}
- catch (TaskCanceledException) { }
- catch (ObjectDisposedException) { }
+ catch (TaskCanceledException ex) { LogService.LogException(ex); }
+ catch (ObjectDisposedException ex) { LogService.LogException(ex); }
// 检测到新版本
LogHelper.WriteLogToFile($"AutoUpdate | New version available: {AvailableLatestVersion}");
@@ -2251,7 +2251,7 @@ await Dispatcher.InvokeAsync(() =>
{
LogHelper.WriteLogToFile($"AutoUpdate | Detected new version {AvailableLatestVersion} different from skipped version {Settings.Startup.SkippedVersion}, clearing skip record");
Settings.Startup.SkippedVersion = "";
- try { await Dispatcher.InvokeAsync(() => SaveSettingsToFile()); } catch (TaskCanceledException) { } catch (ObjectDisposedException) { }
+ try { await Dispatcher.InvokeAsync(() => SaveSettingsToFile()); } catch (TaskCanceledException ex) { LogService.LogException(ex); } catch (ObjectDisposedException ex) { LogService.LogException(ex); }
}
// 如果启用了静默更新,则自动下载更新而不显示提示
@@ -2267,7 +2267,7 @@ await Dispatcher.InvokeAsync(() =>
LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will install when conditions are met");
// 启动检查定时器,定期检查是否可以安装
- try { await Dispatcher.InvokeAsync(() => timerCheckAutoUpdateWithSilence.Start()); } catch (TaskCanceledException) { } catch (ObjectDisposedException) { }
+ try { await Dispatcher.InvokeAsync(() => timerCheckAutoUpdateWithSilence.Start()); } catch (TaskCanceledException ex) { LogService.LogException(ex); } catch (ObjectDisposedException ex) { LogService.LogException(ex); }
}
else
{
@@ -2294,8 +2294,8 @@ await Dispatcher.InvokeAsync(() =>
updateCheckRetryCount = 0;
});
}
- catch (TaskCanceledException) { }
- catch (ObjectDisposedException) { }
+ catch (TaskCanceledException ex) { LogService.LogException(ex); }
+ catch (ObjectDisposedException ex) { LogService.LogException(ex); }
}
else
{
@@ -2306,7 +2306,7 @@ await Dispatcher.InvokeAsync(() =>
updateCheckRetryCount = 0;
// 启动重试定时器,10分钟后重新检查
- try { await Dispatcher.InvokeAsync(() => timerCheckAutoUpdateRetry.Start()); } catch (TaskCanceledException) { } catch (ObjectDisposedException) { }
+ try { await Dispatcher.InvokeAsync(() => timerCheckAutoUpdateRetry.Start()); } catch (TaskCanceledException ex) { LogService.LogException(ex); } catch (ObjectDisposedException ex) { LogService.LogException(ex); }
// 清理更新文件夹
AutoUpdateHelper.DeleteUpdatesFolder();
@@ -2767,7 +2767,7 @@ private void Window_Activated(object sender, EventArgs e)
}
///
- /// 装配通知服务(M13):注入 x:Name 控件强类型引用与访问主窗口私有状态的委托,
+ /// 装配通知服务:注入 x:Name 控件强类型引用与访问主窗口私有状态的委托,
/// 服务本身不引用 MainWindow 类型、不直接引用 UI 线程调度器。
///
private NotificationService CreateNotificationService()
@@ -2792,7 +2792,7 @@ private NotificationService CreateNotificationService()
}
///
- /// 装配截图服务(M15):注入设置读取、墨迹快照、白板页索引、DPI 缩放、通知与
+ /// 装配截图服务:注入设置读取、墨迹快照、白板页索引、DPI 缩放、通知与
/// UI 线程调度委托,服务本身不引用 MainWindow 类型、不直接引用 UI 线程调度器。
/// 通知文本(MainWindowStrings 资源)保留在主窗口侧格式化,服务只接收格式化入口。
///
@@ -3114,7 +3114,7 @@ internal GlobalHotkeyManager EnsureGlobalHotkeyManagerCreated()
if (_globalHotkeyManager == null)
{
_globalHotkeyManager = new GlobalHotkeyManager(this);
- // M16:热键配置服务与管理器同栈创建,内置热键回调以字典注入(回调本体在 MW_Hotkeys.cs)
+ // 热键配置服务与管理器同栈创建,内置热键回调以字典注入(回调本体在 MW_Hotkeys.cs)
_hotkeyService = new Services.Shell.HotkeyService(_globalHotkeyManager, BuildBuiltinHotkeyActions());
// 启动时加载快捷键,但默认为鼠标模式,禁用快捷键以放行键盘操作
_globalHotkeyManager.EnableHotkeyRegistration();
diff --git a/Ink Canvas/MainWindow_cs/MW_AutoTheme.cs b/Ink Canvas/MainWindow_cs/MW_AutoTheme.cs
index 45fb95ea..33e1f59d 100644
--- a/Ink Canvas/MainWindow_cs/MW_AutoTheme.cs
+++ b/Ink Canvas/MainWindow_cs/MW_AutoTheme.cs
@@ -115,7 +115,7 @@ private void RefreshQuickPanelIcons()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新快捷面板图标失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -166,7 +166,7 @@ private void RefreshFloatingBarHighlightColors()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新浮动栏高亮配色失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -257,7 +257,7 @@ void SetSelectedFloatingBarButtonBrush(ToolbarImageButton btn)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新浮动栏按钮配色失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -376,7 +376,7 @@ private void AutoSwitchFloatingBarIconForTheme(string theme)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 按主题自动切换浮动栏图标失败 (theme={theme}): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -398,7 +398,7 @@ private void UpdateFloatingBarIconComboBox()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 同步浮动栏图标下拉框失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -427,7 +427,7 @@ private void RefreshStrokeSelectionIcons()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新墨迹选择栏图标失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -442,7 +442,7 @@ private void RefreshImageSelectionIcons()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新图片选择栏图标失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -457,7 +457,7 @@ private void RefreshGestureButtonIcon()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新手势按钮图标失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -478,7 +478,7 @@ private void RefreshOtherWindowsTheme()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Theme] 刷新其它窗口主题失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_BoardRoaming.cs b/Ink Canvas/MainWindow_cs/MW_BoardRoaming.cs
index a79b48a9..c6988e7f 100644
--- a/Ink Canvas/MainWindow_cs/MW_BoardRoaming.cs
+++ b/Ink Canvas/MainWindow_cs/MW_BoardRoaming.cs
@@ -619,7 +619,7 @@ private Rect GetBoardRoamingContentBounds()
catch (InvalidOperationException ex)
{
// TransformToAncestor 失败通常意味着该元素不在 inkCanvas 可视树内(状态不同步)
- LogHelper.WriteLogToFile($"[Board] 计算子元素边界失败,元素可能不在画布可视树内: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 46e0cedc..63cf6187 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1015,8 +1015,9 @@ internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
{
LogHelper.WriteLogToFile($"进入白板模式时更新名言失败: {t.Exception?.GetBaseException().Message}", LogHelper.LogType.Warning);
}
- catch
+ catch (Exception ex)
{
+ LogService.LogException(ex);
}
if (Settings.Appearance.EnableChickenSoupInWhiteboardMode && Settings.Appearance.ChickenSoupSource != 3)
{
@@ -5433,10 +5434,7 @@ internal void UpdatePenIconColor()
Pen_Icon.Icon.Brush = new SolidColorBrush(inkColor);
ApplyPenIconOutlineForContrast();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Toolbar] 更新浮动栏批注图标颜色为当前画笔颜色失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -5470,10 +5468,7 @@ private Color GetFloatingBarBackgroundColor()
if (Application.Current.TryFindResource("FloatBarBackground") is SolidColorBrush backgroundBrush)
return backgroundBrush.Color;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Toolbar] 读取浮动栏背景色资源 FloatBarBackground 失败,将按主题推断: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
(Settings.Appearance.Theme == 2 && !ThemeHelper.IsSystemThemeLight());
@@ -5503,10 +5498,7 @@ private void ApplyPenIconOutlineForContrast()
Pen_Icon.ClearIconInnerOutline();
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Toolbar] 按对比度为浮动栏批注图标添加/移除内描边失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -5831,10 +5823,7 @@ private void AnimateFloatingBarHighlightTo(string mode)
_activeIndicatorCompletedHandler = null;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Toolbar] 切换浮动栏高亮指示条位置时停止旧动画并解绑 Completed 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
indicatorBar.RenderTransform = null;
indicatorBar.Opacity = 1.0;
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
index bc198c17..1f1a3980 100644
--- a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs
@@ -175,7 +175,7 @@ private void ForwardMouseWheelToUnderlyingWindow(MouseWheelEventArgs e)
// 记录原始 ExStyle
prevExStyle = GetWindowLongPtr(handle, GWL_EXSTYLE);
long originalStyle = prevExStyle.ToInt64();
- try { LogHelper.WriteLogToFile($"[MouseWheel] origExStyle=0x{originalStyle:X}", LogHelper.LogType.Trace); } catch { }
+ try { LogHelper.WriteLogToFile($"[MouseWheel] origExStyle=0x{originalStyle:X}", LogHelper.LogType.Trace); } catch (Exception ex) { LogService.LogException(ex); }
// 1) 加 WS_EX_TRANSPARENT,让命中测试能临时"看穿"我们到下层
long newStyle = originalStyle | WS_EX_TRANSPARENT;
@@ -184,7 +184,7 @@ private void ForwardMouseWheelToUnderlyingWindow(MouseWheelEventArgs e)
SetWindowLongPtr(handle, GWL_EXSTYLE, new IntPtr(newStyle));
exStyleChanged = true;
long after = GetWindowLongPtr(handle, GWL_EXSTYLE).ToInt64();
- try { LogHelper.WriteLogToFile($"[MouseWheel] afterExStyle=0x{after:X}", LogHelper.LogType.Trace); } catch { }
+ try { LogHelper.WriteLogToFile($"[MouseWheel] afterExStyle=0x{after:X}", LogHelper.LogType.Trace); } catch (Exception ex) { LogService.LogException(ex); }
}
// 2) 注入滚轮事件(系统会按 Z 序派发到下层,因为我们已 WS_EX_TRANSPARENT)
@@ -203,13 +203,13 @@ private void ForwardMouseWheelToUnderlyingWindow(MouseWheelEventArgs e)
}
};
bool ok = SendInput(1, new[] { input }, Marshal.SizeOf(typeof(INPUT)));
- try { LogHelper.WriteLogToFile($"[MouseWheel] SendInput ret={ok} delta={e.Delta}", LogHelper.LogType.Trace); } catch { }
+ try { LogHelper.WriteLogToFile($"[MouseWheel] SendInput ret={ok} delta={e.Delta}", LogHelper.LogType.Trace); } catch (Exception ex) { LogService.LogException(ex); }
e.Handled = true;
}
catch (Exception ex)
{
- try { LogHelper.WriteLogToFile($"[MouseWheel] 注入滚轮失败: {ex.Message}", LogHelper.LogType.Error); } catch { }
+ try { LogHelper.WriteLogToFile($"[MouseWheel] 注入滚轮失败: {ex.Message}", LogHelper.LogType.Error); } catch (Exception ex2) { LogService.LogException(ex2); }
}
finally
{
@@ -225,7 +225,7 @@ private void ForwardMouseWheelToUnderlyingWindow(MouseWheelEventArgs e)
}
catch (Exception ex)
{
- try { LogHelper.WriteLogToFile($"[MouseWheel] 恢复窗口样式失败: {ex.Message}", LogHelper.LogType.Error); } catch { }
+ try { LogHelper.WriteLogToFile($"[MouseWheel] 恢复窗口样式失败: {ex.Message}", LogHelper.LogType.Error); } catch (Exception ex2) { LogService.LogException(ex2); }
}
finally
{
@@ -475,7 +475,7 @@ private void KeyHide(object sender, ExecutedRoutedEventArgs e)
}
///
- /// 构建内置热键「名称 → 触发回调」字典(M16:HotkeyService 构造注入,
+ /// 构建内置热键「名称 → 触发回调」字典(HotkeyService 构造注入,
/// 替代原 GlobalHotkeyManager.GetActionByName 的 switch 硬编码)。
/// 键集合与原 switch 完全一致;Pen1-5 的笔型映射复刻原 SwitchToPenType:
/// 0/3/4→默认笔、1→荧光笔、2→激光笔(原为反射调用,此处直接调用,等价)。
diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
index ecdcad2f..08c68b0f 100644
--- a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
@@ -294,7 +294,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
///
/// 截取指定屏幕区域。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
/// 要截取的屏幕区域
/// 截取的位图
@@ -361,14 +361,14 @@ private BitmapSource CreateInkOverlayPreviewBitmapSource()
///
/// 在捕获位图上叠加墨迹预览。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource)
=> ScreenshotService.OverlayInkOnCapturedBitmap(capturedBitmap, captureArea, inkOverlayBitmapSource);
///
/// 将 WPF BitmapSource 转换为 System.Drawing.Bitmap。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource)
=> ScreenshotService.ConvertBitmapSourceToBitmap(bitmapSource);
@@ -743,7 +743,7 @@ private Bitmap ApplyShapeMask(Bitmap bitmap, List path, Rectangle area)
///
/// 将System.Drawing.Bitmap转换为WPF BitmapSource。
- /// 壳:本体(含备用/最简两条降级路径)已搬入 (M15)。
+ /// 壳:本体(含备用/最简两条降级路径)已搬入 。
///
/// 要转换的位图
/// 转换后的BitmapSource
diff --git a/Ink Canvas/MainWindow_cs/MW_Notification.cs b/Ink Canvas/MainWindow_cs/MW_Notification.cs
index d99a3da2..b3f4ef5f 100644
--- a/Ink Canvas/MainWindow_cs/MW_Notification.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Notification.cs
@@ -4,7 +4,7 @@
namespace Ink_Canvas
{
///
- /// 通知转发壳(M13):实现已整体搬至 ,
+ /// 通知转发壳:实现已整体搬至 ,
/// 本文件仅保留对外入口签名,方法体为一行转发。
/// 通知队列与去重逻辑由 (纯逻辑静态类)承载。
///
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index add2a301..06cdfefb 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -363,7 +363,7 @@ public void StopPPTMonitoring()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 停止监控时清理延迟退出定时器/预览缓存失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_pptManager?.StopMonitoring();
@@ -885,7 +885,7 @@ internal void StopPPTOnlyVisibilityProbeTimer()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 停止「仅PPT模式」可见性探测定时器失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -928,8 +928,7 @@ internal bool IsPowerPointSlideshowSurfacePresentWin32()
}
catch (Exception ex)
{
- // 枚举回调内:拿不到进程名就可能漏判放映窗口
- LogHelper.WriteLogToFile($"[PPT] 读取 POWERPNT 进程信息失败,放映窗口检测可能漏判: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return true;
@@ -1111,7 +1110,7 @@ private void OnPPTPresentationClose(object payload)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 处理演示文稿关闭事件时发生非 COM 异常: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1317,7 +1316,7 @@ await Application.Current.Dispatcher.InvokeAsync(async () =>
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 放映开始时初始化演示文稿墨迹管理器失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1688,10 +1687,7 @@ private List GetVideoRegionsViaCom()
MediaType = (int)shape.MediaType
});
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[SmartMode] 读取形状视频区域信息失败,该形状会被跳过: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
return regions;
}
@@ -1785,10 +1781,7 @@ private static bool IsVideoShape(Microsoft.Office.Interop.PowerPoint.Shape shape
return true;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[SmartMode] 读取 OLE 控件 ProgID 失败,将按非视频处理: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
// 无法确认是否为媒体播放器时,不视为视频,避免把普通 ActiveX 控件误判为视频
return false;
}
@@ -1801,16 +1794,10 @@ private static bool IsVideoShape(Microsoft.Office.Interop.PowerPoint.Shape shape
if ((int)(object)shape.MediaType == 3) // ppMediaTypeMovie
return true;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[SmartMode] 读取嵌入式 OLE 形状 MediaType 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[SmartMode] 判定形状是否为视频时发生异常: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return false;
}
@@ -2802,8 +2789,9 @@ private async Task OnPPTNavBarPageClickAsync(Controls.PPTNavBar bar)
ViewboxFloatingBarMarginAnimation(60);
}
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException ex)
{
+ LogService.LogException(ex);
}
catch (Exception ex)
{
@@ -3111,7 +3099,7 @@ private void ResetPPTEnhancedPreviewCache()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 取消增强预览构建任务失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -3120,7 +3108,7 @@ private void ResetPPTEnhancedPreviewCache()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 释放增强预览取消令牌失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
DisposePPTEnhancedPreviewItems(cacheToDispose);
@@ -3175,8 +3163,9 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
LogHelper.WriteLogToFile($"PPT enhanced preview preloaded {slides.Count} thumbnails.", LogHelper.LogType.Trace);
}
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException ex)
{
+ LogService.LogException(ex);
}
catch (Exception ex)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs
index ff034d72..f2414bd8 100644
--- a/Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SaveAndOpenStrokes.cs
@@ -598,7 +598,7 @@ public void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传分页 XML 墨迹文件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
}
@@ -779,7 +779,7 @@ public void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传分页 icstk 墨迹文件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
}
@@ -835,11 +835,7 @@ public void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
}
catch
{
- try { if (File.Exists(tmpPath)) File.Delete(tmpPath); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 原子写入墨迹文件失败后删除临时文件 {Path.GetFileName(tmpPath)} 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(tmpPath)) File.Delete(tmpPath); } catch (Exception ex) { LogService.LogException(ex); }
throw;
}
SavePluginPageDocumentSidecar(savePathWithName, CurrentWhiteboardIndex);
@@ -865,7 +861,7 @@ public void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传单页墨迹文件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
@@ -936,7 +932,7 @@ from point in stroke.StylusPoints
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传 XML 墨迹文件失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
}
@@ -1031,7 +1027,7 @@ private void SaveMultiPageStrokesAsXMLZip(List allPageStrokes,
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传多页 XML 压缩包失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
@@ -1144,7 +1140,7 @@ private void SaveMultiPageStrokesAsZip(List allPageStrokes, st
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 异步上传多页墨迹压缩包失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
@@ -1251,7 +1247,7 @@ private void SaveSinglePageStrokesAsImage(string savePathWithName, bool newNotic
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Screenshot] 异步上传带墨迹截图 PNG 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
} // using imgBitmap
diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
index 90884248..b66e6fe0 100644
--- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs
@@ -13,7 +13,7 @@ public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
///
/// 在切页/加页场景下使用:先捕获当前画面到内存并克隆墨迹,然后立即返回;截图与墨迹保存在后台异步执行,不阻塞切页。
/// 调用方应在调用本方法后立即执行 SaveStrokes、ClearStrokes、切页、RestoreStrokes 等逻辑。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
/// 是否隐藏保存成功通知
/// 截图文件名(可选)
@@ -22,19 +22,19 @@ private void CaptureAndEnqueueScreenshotSave(bool isHideNotification, string fil
///
/// 供插件截图服务调用的全屏捕获入口(调用方负责 Dispose 返回值)。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
internal System.Drawing.Bitmap CapturePluginFullScreen() => ScreenshotService.CaptureScreenshotToBitmap();
///
/// 供插件截图服务调用的区域捕获入口(调用方负责 Dispose 返回值)。
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
internal System.Drawing.Bitmap CapturePluginScreenArea(Rectangle area) => ScreenshotService.CaptureScreenArea(area);
///
/// 保存截图
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
/// 是否隐藏通知
/// 文件名
@@ -49,7 +49,7 @@ private void SaveScreenShot(bool isHideNotification, string fileName = null)
///
/// 保存截图到配置的保存目录
- /// 壳:本体已搬入 (M15)。
+ /// 壳:本体已搬入 。
///
///
/// 该方法会:
@@ -93,7 +93,7 @@ internal async Task SaveAreaScreenShotToDesktop()
_screenshotService.GetScreenshotSaveDirectory(),
$"{_screenshotService.GetScreenshotFileNameStem()}.png");
- // 执行段(捕获→叠墨→遮罩→落盘→剪贴板)已搬入 ScreenshotService(M15);
+ // 执行段(捕获→叠墨→遮罩→落盘→剪贴板)已搬入 ScreenshotService;
// 成功/失败通知保持原有相对顺序:落盘(+剪贴板)之后、自动保存墨迹之前。
if (!_screenshotService.SaveAreaScreenshot(screenshotResult.Value, savePath, Settings.Automation.IsCopyScreenshotToClipboard))
{
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index beeb0c81..7ceacca2 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -22,7 +22,7 @@ namespace Ink_Canvas
{
public partial class MainWindow : Ink_Canvas.Helpers.PerformanceTransparentWin
{
- // M11 寄生提取:设置存储服务,构造函数末尾初始化;壳在全部提取模块完成后删除。
+ // 寄生提取:设置存储服务,构造函数末尾初始化;壳在全部提取模块完成后删除。
private readonly Services.SettingsStore _settingsStore;
#region Behavior
@@ -124,7 +124,7 @@ private static object CreateHitokotoClient()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 创建一言 HttpClient 时设置 User-Agent 头失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
return client;
}
@@ -134,8 +134,9 @@ private static object CreateHitokotoClient()
{
LogHelper.WriteLogToFile($"无法创建 HttpClient (System.Net.Http 可能缺失): {ex.Message}", LogHelper.LogType.Warning);
}
- catch
+ catch (Exception ex2)
{
+ LogService.LogException(ex2);
}
return null;
}
@@ -157,7 +158,7 @@ private HttpClient GetHitokotoHttpClientOrNull(bool writeLog = true)
}
}
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private string BuildHitokotoRequestUrl() => _settingsStore.BuildHitokotoRequestUrl();
private async Task FetchHitokotoTextCoreAsync(HttpClient client, string requestUrl)
@@ -654,10 +655,10 @@ public void UpdatePPTUIManagerSettings()
#region Canvas
/// 笔锋下拉 UI 顺序:0 实时笔锋,1 基于点集,2 基于速率,3 关闭。与存储值 InkStyle:3,0,1,2 对应。
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private static int PenStyleUiIndexFromInkStyle(int inkStyle) => Services.SettingsStore.PenStyleUiIndexFromInkStyle(inkStyle);
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private static int InkStyleFromPenStyleUiIndex(int uiIndex) => Services.SettingsStore.InkStyleFromPenStyleUiIndex(uiIndex);
private void ComboBoxPenStyle_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -1033,7 +1034,7 @@ private void ToggleSwitchEnableTwoFingerRotation_Toggled(object sender, RoutedEv
/// 该方法会重新创建全局 Settings 实例并应用推荐值,覆盖大部分子模块配置(如外观、画布、自动化、PPT、手势、高级选项等)。
/// 在重置过程中会保留并恢复当前 Settings.Automation 中的 AutoDelSavedFiles 与 AutoDelSavedFilesDaysThreshold 两项值以避免意外删除策略变化。
///
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
public static void SetSettingsToRecommendation() => Services.SettingsStore.SetSettingsToRecommendation();
///
@@ -1054,7 +1055,7 @@ public async void BtnResetToSuggestion_Click(object sender, RoutedEventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Settings] 重置为推荐设置前的密码/TOTP 校验调用失败,将继续重置: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
try
@@ -1076,11 +1077,7 @@ public async void BtnResetToSuggestion_Click(object sender, RoutedEventArgs e)
}
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
- try { ShowNotification(Properties.MainWindowStrings.Main_Settings_ResetDone); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 重置为推荐设置完成后弹出「重置完成」通知失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { ShowNotification(Properties.MainWindowStrings.Main_Settings_ResetDone); } catch (Exception ex) { LogService.LogException(ex); }
}
private async void SpecialVersionResetToSuggestion_Click()
@@ -1153,7 +1150,7 @@ void SetIcon(ToolbarImageButton btn, bool isSolid, string solidGeom, string line
}
}
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
public string GetCorrectIcon(string iconType, bool isSolid = false) => _settingsStore.GetCorrectIcon(iconType, isSolid);
#region 浮动栏按钮显示控制
@@ -1194,7 +1191,7 @@ internal void UpdateFloatingBarButtonsVisibility()
///
/// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。
///
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
public static void SaveSettingsToFile() => Services.SettingsStore.SaveSettingsToFile();
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index 5f46ea9e..db62e507 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -176,7 +176,7 @@ private void LoadSettings(bool isStartup = false, bool skipAutoUpdateCheck = fal
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ProcGuard] 按设置应用进程保护失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
// Startup
@@ -734,7 +734,7 @@ private void LoadInkFadeSettings()
/// 4. 如果有清理操作,重新反序列化并保存
/// 5. 记录清理结果到日志
///
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private void CleanupObsoleteSettings(string userConfigJson) => _settingsStore.CleanupObsoleteSettings(userConfigJson);
///
@@ -754,15 +754,15 @@ private void LoadInkFadeSettings()
/// 7. 删除标记的键
/// 8. 设置变更标志
///
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private static void MigrateLegacyStartupMode(JObject userConfigObj, ref bool hasChanges) =>
Services.SettingsStore.MigrateLegacyStartupMode(userConfigObj, ref hasChanges);
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private static void EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(JObject defaultConfigObj) =>
Services.SettingsStore.EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
- // M11 壳:实现已搬移至 Services/Settings/SettingsStore.cs
+ // 壳:实现已搬移至 Services/Settings/SettingsStore.cs
private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges) =>
_settingsStore.RemoveObsoleteProperties(userObj, defaultObj, ref hasChanges);
diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs
index 3e7f32f8..b121a92e 100644
--- a/Ink Canvas/MainWindow_cs/MW_Timer.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs
@@ -355,7 +355,7 @@ private void AutoSaveStrokesTimer_Tick(object sender, EventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Timer] 自动保存墨迹失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -936,10 +936,7 @@ private bool HasAnyFullScreenAutoFoldApp()
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C)
return true;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Timer] 读取易记笔记版本信息失败,自动收纳判定可能不准: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
else if (Settings.Automation.IsAutoFoldInEasiCamera && windowProcessName == "EasiCamera")
@@ -980,10 +977,7 @@ private bool HasAnyFullScreenAutoFoldApp()
if (version.StartsWith("6.") && prodName == "WhiteBoard")
return true;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Timer] 读取希沃白板版本信息失败,自动收纳判定可能不准: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -1485,10 +1479,7 @@ private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEvent
// 使用 BeginInvoke 避免在 Invoke 闭包内同步等待自身调度导致 UI 死锁
Dispatcher.BeginInvoke(new Action(() => { Application.Current.Shutdown(); }));
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 静默更新安装后投递 Application.Shutdown 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
else
{
diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
index bd815543..1980c7bc 100644
--- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
@@ -8,7 +8,7 @@ namespace Ink_Canvas
{
public partial class App : Application
{
- // M12:托盘逻辑已提取至 Services/Shell/TrayIconService.cs,本文件仅保留
+ // 托盘逻辑已提取至 Services/Shell/TrayIconService.cs,本文件仅保留
// XAML wired 事件处理器与插件托盘 API 的转发壳(壳删除试点)。
// 服务的依赖装配在 App.xaml.cs 的 CreateTrayIconService,释放在 App_Exit。
private TrayIconService _trayIconService;
diff --git a/Ink Canvas/MainWindow_cs/MW_UInk.cs b/Ink Canvas/MainWindow_cs/MW_UInk.cs
index 59c84464..7e48fa50 100644
--- a/Ink Canvas/MainWindow_cs/MW_UInk.cs
+++ b/Ink Canvas/MainWindow_cs/MW_UInk.cs
@@ -265,10 +265,7 @@ private void OpenUInkFile(string path)
{
if (extractDir != null)
{
- try { if (Directory.Exists(extractDir)) Directory.Delete(extractDir, true); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UInk] 清理临时解压目录失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (Directory.Exists(extractDir)) Directory.Delete(extractDir, true); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -348,10 +345,7 @@ private void RestoreUInkMediaForPage(IEnumerable mediaList, Dictionar
{
if (mediaList == null) return;
var cacheDir = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "UInk Media");
- try { Directory.CreateDirectory(cacheDir); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UInk] 创建媒体缓存目录失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Directory.CreateDirectory(cacheDir); } catch (Exception ex) { LogService.LogException(ex); }
foreach (var m in mediaList)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_UriHandler.cs b/Ink Canvas/MainWindow_cs/MW_UriHandler.cs
index 114f2791..54be7f31 100644
--- a/Ink Canvas/MainWindow_cs/MW_UriHandler.cs
+++ b/Ink Canvas/MainWindow_cs/MW_UriHandler.cs
@@ -444,10 +444,7 @@ private void HandleUriConfigProfileSwitch(string profileName)
}
catch (Exception ex)
{
- try { File.WriteAllText(resultPath, "error: " + ex.Message, System.Text.Encoding.UTF8); } catch (Exception inner)
- {
- LogHelper.WriteLogToFile($"[Nav] 写入 URI 结果文件失败: {inner.Message}", LogHelper.LogType.Info);
- }
+ try { File.WriteAllText(resultPath, "error: " + ex.Message, System.Text.Encoding.UTF8); } catch (Exception ex2) { LogService.LogException(ex2); }
LogHelper.WriteLogToFile($"URI 切换配置方案失败: {ex.Message}", LogHelper.LogType.Error);
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs
index e9a09e14..784b89e2 100644
--- a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs
+++ b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs
@@ -462,11 +462,7 @@ private void EnterVideoPresenterSpecialMode()
}
// VideoCaptureElement 自己管理渲染(VMR9 + D3DImage),不需要清空 Source
// 若之前的预览仍在运行,先停止避免设备占用
- try { VideoPresenterFullCanvasImage?.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 进入展台特殊模式前停止上一次的实时预览失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { VideoPresenterFullCanvasImage?.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
// 重置缩放
_boothPreviewScale = 1.0;
@@ -540,31 +536,19 @@ private void ExitVideoPresenterSpecialMode()
_liveStrokesSnapshot.Clear();
foreach (var p in _capturedPhotos)
{
- try { p?.Strokes?.Clear(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 退出特殊模式时清空已拍摄照片槽位中的墨迹失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { p?.Strokes?.Clear(); } catch (Exception ex) { LogService.LogException(ex); }
}
// 清理鼠标拖动状态(防止退出时鼠标仍被捕获)
if (_isBoothMouseDragging)
{
_isBoothMouseDragging = false;
- try { inkCanvas?.ReleaseMouseCapture(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 退出特殊模式时释放画布鼠标捕获失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { inkCanvas?.ReleaseMouseCapture(); } catch (Exception ex) { LogService.LogException(ex); }
}
_boothTouchSavedInkEditingMode = null;
// 停止 VideoCaptureElement 预览,释放 DirectShow 图
- try { VideoPresenterFullCanvasImage?.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 退出特殊模式时停止展台实时预览失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { VideoPresenterFullCanvasImage?.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
// 释放摄像头属性控制占用的常驻 FilterGraphNoThread 资源。
// 必须延迟到后台线程执行:VideoCaptureElement.Stop() 是异步的(WPFMediaKit 内部异步清理 FilterGraph),
// 立即同步释放 source filter 的 COM 对象会与 VideoCaptureElement 的 DirectShow 图清理竞争设备锁,
@@ -846,7 +830,7 @@ private void RotateBoothStrokesFromBaseline(double targetAngleDegrees)
LogHelper.LogType.Info);
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
_isApplyingRotationToStrokes = true;
var prevCommitType = _currentCommitType;
@@ -882,7 +866,7 @@ private void RotateBoothStrokesFromBaseline(double targetAngleDegrees)
$"afterCenter=({afterBBox.X+afterBBox.Width/2:F1},{afterBBox.Y+afterBBox.Height/2:F1})",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
}
finally
{
@@ -938,17 +922,11 @@ private void ShowBoothTransientMessage(string message)
VideoPresenterSearchingText.Text = "正在查找展台设备...";
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 展台临时提示 2.5s 后自动隐藏失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
};
timer.Start();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 显示展台临时提示失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1176,10 +1154,7 @@ private void VideoPresenterFullCanvasImage_MediaOpened(object sender, RoutedEven
"[VideoPresenter] MediaOpened: 摄像头已打开,隐藏占位文字",
LogHelper.LogType.Info);
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] MediaOpened 处理失败(隐藏占位文字/启用拍照按钮): {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1205,10 +1180,7 @@ private void VideoPresenterFullCanvasImage_MediaFailed(object sender, WPFMediaKi
LogHelper.LogType.Error);
ErrorOccurredRelay?.Invoke(this, $"摄像头打开失败: {msg}");
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] MediaFailed 处理失败(写日志/抛出失败通知): {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -2140,20 +2112,12 @@ private Task StartVideoCaptureElementPreviewAsync(int cameraIndex)
}
// 若已在播放同一设备,先停止
- try { VideoPresenterFullCanvasImage.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 重建预览图前停止同设备的实时预览失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { VideoPresenterFullCanvasImage.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
// 关键:显式将 VideoCaptureDevice 置为 null,强制依赖属性变化回调触发 CleanUp()。
// 否则后续 VideoCaptureDevice = device(同一个 DsDevice 引用)会被 WPF 依赖属性系统判定为
// "未变化",OnVideoCaptureDeviceChanged 不触发,WPFMediaKit 不会重建 FilterGraph,
// 导致切换分辨率/帧率时 DesiredPixelWidth/Height/FPS 不生效(仍是旧分辨率)。
- try { VideoPresenterFullCanvasImage.VideoCaptureDevice = null; }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 重建预览图前把 VideoCaptureDevice 置空以强制 CleanUp 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { VideoPresenterFullCanvasImage.VideoCaptureDevice = null; } catch (Exception ex) { LogService.LogException(ex); }
// 等待 FilterGraph 完全释放设备占用:
// WPFMediaKit 的 Stop() + CleanUp() 是异步的,FilterGraph 释放需要时间。
// 立即 BeginInit/EndInit/Play 会导致设备仍被占用,新图构建失败,
@@ -2268,10 +2232,7 @@ private Task StartVideoCaptureElementPreviewAsync(int cameraIndex)
_ = StartVideoCaptureElementPreviewAsync(camIdx).ConfigureAwait(false);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] MediaOpened 兜底定时器(1.5s 未触发则重启预览)处理失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
};
fallbackTimer.Start();
}
@@ -2800,7 +2761,7 @@ private void BtnRotateImage_Click(object sender, RoutedEventArgs e)
$"scale={_boothPreviewScale}, translate=({_boothPreviewTranslateX},{_boothPreviewTranslateY})",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
VideoPresenterFullCanvasRotation.Angle = (VideoPresenterFullCanvasRotation.Angle + 90.0) % 360.0;
// 传视觉角度(cameraRotation*90 + Angle)给墨迹变换,
// 因为 GetBoothRotationMatrix 按"实时画面 0° 基准"算缩放,照片预览需用视觉角度
@@ -2813,7 +2774,7 @@ private void BtnRotateImage_Click(object sender, RoutedEventArgs e)
$"photo.ActualSize={VideoPresenterFrozenFrameImage.ActualWidth}x{VideoPresenterFrozenFrameImage.ActualHeight}",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
return;
}
@@ -2882,10 +2843,7 @@ private void SyncPhotoCorrectionAccelerationComboBox()
if (accelIdx >= 0 && accelIdx < cb.Items.Count && cb.SelectedIndex != accelIdx)
cb.SelectedIndex = accelIdx;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 同步相片校正加速度下拉框选中项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
// ====================================================================
@@ -3004,10 +2962,7 @@ private void SyncBoothBrightnessSlider()
RefreshBoothPropSlidersEnabled();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 刷新摄像头属性滑块取值失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
/// 按 _cameraService 支持情况启用/禁用全部属性滑块并刷新 tooltip。
@@ -3026,10 +2981,7 @@ private void RefreshBoothPropSlidersEnabled()
: Ink_Canvas.Properties.BoothStrings.ResourceManager.GetString(b.notSupportedTooltipKey, Ink_Canvas.Properties.BoothStrings.Culture);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 按摄像头支持情况刷新属性滑块启用状态与 tooltip 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -3826,10 +3778,7 @@ private void ClearFrozenFrame()
VideoPresenterFrozenFrameImage.RenderTransform = _frozenFrameOriginalRenderTransform;
// VideoPresenterFrozenOverlay(旧全屏蒙版)和 VideoPresenterFrozenThumbnail(侧栏小预览)均已从 XAML 移除
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 清除展台冻结画面(恢复实时预览)失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -3880,7 +3829,7 @@ private void SwitchBoothToPhotoPage(int photoIndex)
$"strokesCount={inkCanvas?.Strokes?.Count ?? 0}",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
VideoPresenterFrozenFrameImage.Source = photo.Image;
VideoPresenterFrozenFrameImage.Visibility = Visibility.Visible;
@@ -3922,18 +3871,14 @@ private void SwitchBoothToPhotoPage(int photoIndex)
$"photo.ActualSize={VideoPresenterFrozenFrameImage.ActualWidth}x{VideoPresenterFrozenFrameImage.ActualHeight}",
LogHelper.LogType.Info);
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
}
// 停止并隐藏 VideoCaptureElement,只显示冻结照片
// (Stretch=Uniform 留黑边处会看到底层实时画面,必须停止+隐藏)
if (VideoPresenterFullCanvasImage != null)
{
- try { VideoPresenterFullCanvasImage.Stop(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Booth] 切换到照片预览页时停止展台实时预览失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { VideoPresenterFullCanvasImage.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
VideoPresenterFullCanvasImage.Visibility = Visibility.Collapsed;
}
diff --git a/Ink Canvas/MainWindow_cs/MW_WindowChromeHitTest.cs b/Ink Canvas/MainWindow_cs/MW_WindowChromeHitTest.cs
index 1d7fb163..b9711cf3 100644
--- a/Ink Canvas/MainWindow_cs/MW_WindowChromeHitTest.cs
+++ b/Ink Canvas/MainWindow_cs/MW_WindowChromeHitTest.cs
@@ -59,10 +59,7 @@ internal void BuildSmartModeRects()
LogHelper.WriteLogToFile($"[SmartMode] COM 回退: Slide={_smartModeSlideWidth}x{_smartModeSlideHeight}磅", LogHelper.LogType.Info);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 回退1 经 COM 读取幻灯片尺寸失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
// 回退2:通过 _pptManager
if (_smartModeSlideWidth <= 0)
@@ -77,10 +74,7 @@ internal void BuildSmartModeRects()
LogHelper.WriteLogToFile($"[SmartMode] PPTManager 回退: Slide={_smartModeSlideWidth}x{_smartModeSlideHeight}磅", LogHelper.LogType.Info);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 回退2 经 _pptManager 读取幻灯片尺寸失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
// 回退3:使用标准 16:9 尺寸
@@ -102,25 +96,14 @@ internal void BuildSmartModeRects()
{
dynamic app = appObj;
dynamic ssw = null;
- try { ssw = app.ActivePresentation?.SlideShowWindow; }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 经 dynamic 读取 ActivePresentation.SlideShowWindow 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { ssw = app.ActivePresentation?.SlideShowWindow; } catch (Exception ex) { LogService.LogException(ex); }
if (ssw != null)
{
- try { _smartModeSlideShowHwnd = new IntPtr(ssw.HWND); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 读取放映窗口 HWND 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _smartModeSlideShowHwnd = new IntPtr(ssw.HWND); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 经 _pptManager 取 PPT 应用对象失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
if (_smartModeSlideShowHwnd == IntPtr.Zero)
{
@@ -143,11 +126,7 @@ internal void BuildSmartModeRects()
}
uint dpi = 96;
- try { dpi = PInvoke.GetDpiForWindow(new HWND(_smartModeSlideShowHwnd)); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 智慧模式 BuildSmartModeRects 获取放映窗口 DPI 失败,按 96 DPI 处理: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { dpi = PInvoke.GetDpiForWindow(new HWND(_smartModeSlideShowHwnd)); } catch (Exception ex) { LogService.LogException(ex); }
double dpiScale = dpi / 96.0;
double winLeft = winRect.left;
diff --git a/Ink Canvas/Models/TimerState.cs b/Ink Canvas/Models/TimerState.cs
index f8a529ef..4b99423e 100644
--- a/Ink Canvas/Models/TimerState.cs
+++ b/Ink Canvas/Models/TimerState.cs
@@ -3,7 +3,7 @@
namespace Ink_Canvas.Models
{
///
- /// 计时器模式(M14)。描述倒计时状态机的当前阶段。
+ /// 计时器模式。描述倒计时状态机的当前阶段。
///
internal enum TimerMode
{
@@ -18,7 +18,7 @@ internal enum TimerMode
}
///
- /// 计时器状态快照(M14,POCO)。由 在每次 tick
+ /// 计时器状态快照(POCO)。由 在每次 tick
/// 或状态变更时产生,经 StateChanged 事件推送给窗口壳;窗口只读渲染,不回写。
///
internal sealed class TimerState
diff --git a/Ink Canvas/Models/ToolMode.cs b/Ink Canvas/Models/ToolMode.cs
index 80ba2e35..40efb966 100644
--- a/Ink Canvas/Models/ToolMode.cs
+++ b/Ink Canvas/Models/ToolMode.cs
@@ -3,8 +3,8 @@
namespace Ink_Canvas.Models
{
///
- /// 工具模式(M17)。MainWindow 当前以字符串字面量表示工具模式(_currentToolMode),
- /// 本枚举是其类型化对应物;M18 起逐步替换字符串比较点。
+ /// 工具模式。MainWindow 当前以字符串字面量表示工具模式(_currentToolMode),
+ /// 本枚举是其类型化对应物,用于逐步替换字符串比较点。
///
///
/// 枚举成员名 = 字面量的 PascalCase。字面量集合经 MainWindow/ 全量 grep 穷举确认,
@@ -34,7 +34,7 @@ internal enum ToolMode
}
///
- /// 与现有字符串字面量之间的无损双向往返映射(M17)。
+ /// 与现有字符串字面量之间的无损双向往返映射。
///
internal static class ToolModeMapping
{
diff --git a/Ink Canvas/Plugins/PluginAuthorizationService.cs b/Ink Canvas/Plugins/PluginAuthorizationService.cs
index 32d301ab..9e076610 100644
--- a/Ink Canvas/Plugins/PluginAuthorizationService.cs
+++ b/Ink Canvas/Plugins/PluginAuthorizationService.cs
@@ -296,10 +296,7 @@ private void Load()
return;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 授权记录按 v2 版本化格式解析失败,回退尝试旧平铺格式: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { Helpers.LogService.LogException(ex); }
// 旧平铺格式(key = pluginId|fullPath,value = SHA-256)。
// 迁移:插件只要曾授权过任意一个 DLL,即升级为插件级信任,
@@ -320,10 +317,7 @@ private void Load()
}
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 读取插件授权记录失败(已授权列表可能为空): {_filePath}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { Helpers.LogService.LogException(ex); }
}
private void Save()
@@ -341,10 +335,7 @@ private void Save()
File.WriteAllText(_filePath, JsonSerializer.Serialize(store,
new JsonSerializerOptions { WriteIndented = true }));
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 保存插件授权记录失败(本次授权可能无法在下次启动后免询问): {_filePath}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { Helpers.LogService.LogException(ex); }
}
private static string ComputeHash(string path)
diff --git a/Ink Canvas/Plugins/PluginIpcService.cs b/Ink Canvas/Plugins/PluginIpcService.cs
index 8ce9777d..2e12ad75 100644
--- a/Ink Canvas/Plugins/PluginIpcService.cs
+++ b/Ink Canvas/Plugins/PluginIpcService.cs
@@ -316,21 +316,9 @@ public void Dispose()
if (_disposed) return;
_disposed = true;
- try { _cts.Cancel(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] PluginIpc 释放时取消 CancellationTokenSource 失败: {ex.Message}", LogHelper.LogType.Info);
- }
- try { _listenTask?.Wait(TimeSpan.FromSeconds(1)); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] PluginIpc 释放时等待监听任务结束失败(监听循环可能仍在运行): {ex.Message}", LogHelper.LogType.Info);
- }
- try { _cts.Dispose(); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] PluginIpc 释放时释放 CancellationTokenSource 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { _cts.Cancel(); } catch (Exception ex) { LogService.LogException(ex); }
+ try { _listenTask?.Wait(TimeSpan.FromSeconds(1)); } catch (Exception ex) { LogService.LogException(ex); }
+ try { _cts.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs
index 7ea4be82..ee20f7e2 100644
--- a/Ink Canvas/Plugins/PluginManager.cs
+++ b/Ink Canvas/Plugins/PluginManager.cs
@@ -235,10 +235,7 @@ private void LoadDisabledPlugins()
foreach (var id in list) _disabledPlugins.Add(id);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 读取已禁用插件列表失败(禁用状态可能丢失): {_disabledPluginsFile}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private void SaveDisabledPlugins()
@@ -251,10 +248,7 @@ private void SaveDisabledPlugins()
System.Text.Json.JsonSerializer.Serialize(_disabledPlugins.ToList(),
new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 保存已禁用插件列表失败(禁用状态可能无法持久化): {_disabledPluginsFile}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -299,10 +293,7 @@ public void LogPlugin(string pluginId, string level, string message)
DateTime.Now, level, message, Environment.NewLine);
File.AppendAllText(logFile, line);
}
- catch (Exception ex)
- {
- LogCallbackException($"写入插件 \"{pluginId}\" 的独立日志文件", ex);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1489,10 +1480,7 @@ private void TryWriteUninstallMarker(string folder)
File.WriteAllText(Path.Combine(folder, ".uninstall"), "");
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 写入 .uninstall 标记文件失败(该插件目录下次启动不会被兜底清理): {folder}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1680,10 +1668,7 @@ private void DiagnoseLivePluginReferences(string pluginId)
if (string.IsNullOrEmpty(module.FileName)) continue;
loaded.Add(module.FileName);
}
- catch (Exception ex)
- {
- LogCallbackException("诊断插件占用: 读取进程已加载模块信息失败(跳过该模块)", ex);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
catch (Exception ex)
@@ -1797,10 +1782,7 @@ private void WalkAndReportPinning(object obj, AssemblyLoadContext target,
if (AssemblyLoadContext.GetLoadContext(t.Assembly) == target && seenPath.Add(path))
Log(string.Format("Pinning reference: {0} (Type={1})", path, t.FullName));
}
- catch (Exception ex)
- {
- LogCallbackException("诊断插件占用: 检查类型是否归属目标 ALC", ex);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
else if (depth > 0)
{
@@ -2490,11 +2472,7 @@ protected override Assembly Load(AssemblyName assemblyName)
var assembly = depContext.Load(assemblyName);
if (assembly != null) return assembly;
}
- catch (Exception ex)
- {
- LogCallbackException(
- $"插件 ALC 解析: 从依赖插件 \"{dep.Id}\" 的上下文加载程序集 \"{assemblyName.Name}\"", ex);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
}
diff --git a/Ink Canvas/Plugins/PluginMarketService.cs b/Ink Canvas/Plugins/PluginMarketService.cs
index ca54e4ae..306db3b7 100644
--- a/Ink Canvas/Plugins/PluginMarketService.cs
+++ b/Ink Canvas/Plugins/PluginMarketService.cs
@@ -533,11 +533,7 @@ public async Task RequestDownloadPluginAsync(string id)
if (task.IsCancelled)
{
- try { File.Delete(tempFile); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 插件下载取消后删除临时文件失败: {tempFile}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { File.Delete(tempFile); } catch (Exception ex) { LogService.LogException(ex); }
return false;
}
@@ -548,11 +544,7 @@ public async Task RequestDownloadPluginAsync(string id)
if (!string.Equals(hash, merged.MarketEntry.DownloadSha256, StringComparison.OrdinalIgnoreCase))
{
task.Error = "文件校验失败,可能已损坏。";
- try { File.Delete(tempFile); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 插件包 SHA256 校验失败后删除临时文件失败: {tempFile}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { File.Delete(tempFile); } catch (Exception ex) { LogService.LogException(ex); }
return false;
}
}
@@ -758,10 +750,7 @@ private static string ComputeSha256(string filePath)
var json = JsonConvert.DeserializeAnonymousType(File.ReadAllText(IndexMetaPath), new { lastRefresh = "" });
if (DateTime.TryParse(json?.lastRefresh, out var dt)) return dt;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Plugin] 读取插件市场索引最后刷新时间失败(将视为无刷新时间): {IndexMetaPath}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return null;
}
diff --git a/Ink Canvas/Services/Classroom/TimerService.cs b/Ink Canvas/Services/Classroom/TimerService.cs
index 5f111e34..412ffd6c 100644
--- a/Ink Canvas/Services/Classroom/TimerService.cs
+++ b/Ink Canvas/Services/Classroom/TimerService.cs
@@ -5,7 +5,7 @@
namespace Ink_Canvas.Services.Classroom
{
///
- /// 课堂倒计时状态机(M14 寄生提取)。承载 CountdownTimerWindow / NewStyleTimerWindow
+ /// 课堂倒计时状态机(寄生提取)。承载 CountdownTimerWindow / NewStyleTimerWindow
/// 共有的纯计时逻辑:设定时间、开始/暂停/继续/重置、tick 递减、超时正计时模式。
/// 纪律:禁止引用 UI 调度器 / 主窗体 / 任何 Visual 类型;设置项经 Func 注入;
/// 定时器保持 System.Timers.Timer 原类型(回调在线程池),UI 触碰由订阅端自行调度。
@@ -75,7 +75,7 @@ public static double LegacyIntervalForTotal(TimeSpan total)
private bool isOvertimeMode = false;
private bool hasPlayedProgressiveReminder = false;
- // 无调用方的历史遗留成员,随状态机原样搬入(详见 extraction-backlog M14 节)
+ // 无调用方的历史遗留成员,随状态机原样搬入
private TimeSpan remainingTime = TimeSpan.Zero;
public int Hour { get => hour; set => hour = value; }
diff --git a/Ink Canvas/Services/Settings/SettingsStore.cs b/Ink Canvas/Services/Settings/SettingsStore.cs
index a20cdf0c..1acc8c7a 100644
--- a/Ink Canvas/Services/Settings/SettingsStore.cs
+++ b/Ink Canvas/Services/Settings/SettingsStore.cs
@@ -10,7 +10,7 @@
namespace Ink_Canvas.Services
{
///
- /// 设置存储服务(M11 寄生提取首站)。承载从 MainWindow 搬出的纯设置读写逻辑。
+ /// 设置存储服务(寄生提取首站)。承载从 MainWindow 搬出的纯设置读写逻辑。
/// 全局设置实例经 静态可达,故本类零依赖注入;
/// 禁止持有 MainWindow 或任何 Visual 类型引用。
///
diff --git a/Ink Canvas/Services/Shell/HotkeyService.cs b/Ink Canvas/Services/Shell/HotkeyService.cs
index df853ca3..fe7cb505 100644
--- a/Ink Canvas/Services/Shell/HotkeyService.cs
+++ b/Ink Canvas/Services/Shell/HotkeyService.cs
@@ -9,12 +9,12 @@
namespace Ink_Canvas.Services.Shell
{
///
- /// 全局热键配置服务(M16 寄生提取)。承载从 Helpers/GlobalHotkeyManager 搬出的
+ /// 全局热键配置服务(寄生提取)。承载从 Helpers/GlobalHotkeyManager 搬出的
/// 配置层:HotkeyConfig.json 的读写解析、默认配置定义、从配置/默认集合到注册引擎的
/// 加载编排。热键触发回调本体留在主窗口壳(MW_Hotkeys.cs),以「热键名 → 」
/// 字典在构造时注入,替代原 GetActionByName switch 的硬编码回调。
/// NHotkey 注册引擎与多屏/焦点上下文门控仍在 中,
- /// 本服务不引用主窗口类型、不做任何 UI 线程调度(M30 门禁)。
+ /// 本服务不引用主窗口类型、不做任何 UI 线程调度。
/// 注意与插件服务 Ink_Canvas.Plugins.HotkeyService 同名不同命名空间,勿混淆。
///
internal sealed class HotkeyService
@@ -426,7 +426,7 @@ private bool SaveHotkeysToConfigFile()
}
///
- /// 根据快捷键名称获取对应的动作(M16:原 switch 硬编码改为构造注入的动作字典查表)
+ /// 根据快捷键名称获取对应的动作(原 switch 硬编码改为构造注入的动作字典查表)
///
/// 快捷键名称
/// 对应的动作,如果不存在则返回null
diff --git a/Ink Canvas/Services/Shell/NotificationService.cs b/Ink Canvas/Services/Shell/NotificationService.cs
index 50d22e07..0743ab1a 100644
--- a/Ink Canvas/Services/Shell/NotificationService.cs
+++ b/Ink Canvas/Services/Shell/NotificationService.cs
@@ -12,14 +12,14 @@
namespace Ink_Canvas.Services
{
///
- /// 通知服务(M13 寄生提取)。承载从 MW_Notification.cs 搬出的通知 UI 编排逻辑:
+ /// 通知服务(寄生提取)。承载从 MW_Notification.cs 搬出的通知 UI 编排逻辑:
/// 提供者初始化、通知请求分发(Windows Toast / 灵动通知 / 旧版动画通知)、位置计算、
/// 听写勿扰抑制、启动未读公告提示与插件回调摘除。
/// 通知队列、去重与历史由既有 (纯逻辑静态类,
/// 不接触任何 UI 线程对象)承载,本服务只做 UI 呈现编排。
/// 禁止引用主窗口类型:主窗口私有状态经 委托注入,x:Name 控件经
- /// 构造函数以强类型引用注入(GLOBAL-RULES 第六条 1),禁止按名查找视觉树。
- /// UI 线程切换经注入的 UiInvoke 委托完成(M30 统一替换为 ISyncService 前的过渡形态)。
+ /// 构造函数以强类型引用注入,禁止按名查找视觉树。
+ /// UI 线程切换经注入的 UiInvoke 委托完成(统一替换为 ISyncService 前的过渡形态)。
///
internal sealed class NotificationService
{
@@ -42,7 +42,7 @@ internal sealed class Hooks
public Func IsFloatingBarChangingHideMode { get; set; }
/// 获取宿主窗口(以 Window 基类形态返回,服务不接触主窗口类型)。
public Func GetHostWindow { get; set; }
- /// 在 UI 线程上异步执行委托(默认优先级;过渡形态,M30 收敛为 ISyncService)。
+ /// 在 UI 线程上异步执行委托(默认优先级;过渡形态,后续收敛为 ISyncService)。
public Action UiInvoke { get; set; }
/// 在 UI 线程空闲优先级异步执行委托(用于公告服务延迟启动)。
public Action UiInvokeContextIdle { get; set; }
@@ -137,8 +137,9 @@ public void InitializeProviders()
await Task.Delay(TimeSpan.FromSeconds(3), _providerCancellation.Token);
await _announcementService.StartAsync(_providerCancellation.Token);
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException oce)
{
+ LogService.LogException(oce);
}
catch (Exception ex)
{
@@ -283,7 +284,7 @@ private void ApplyDynamicNotificationFloatingBarPlacement()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Notification] 计算通知悬浮栏位置失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -357,7 +358,7 @@ private void ShowLegacyNotification(string notice, int displaySeconds)
AnimationsHelper.ShowWithSlideFromBottomAndFade(_gridNotifications);
// 原实现为独立线程 Sleep 后经 UI 线程调度器隐藏;等价改写为单次计时器 +
- // UiInvoke 回 UI 线程(新类不得创建 Thread 访问 UI 线程对象,同 M12 过渡形态)。
+ // UiInvoke 回 UI 线程(新类不得创建 Thread 访问 UI 线程对象,同为过渡形态)。
StopLegacyHideTimer();
_legacyHideTimer = new Timer(_ =>
{
diff --git a/Ink Canvas/Services/Shell/ScreenshotService.cs b/Ink Canvas/Services/Shell/ScreenshotService.cs
index 0abc6fe6..28a8e9b8 100644
--- a/Ink Canvas/Services/Shell/ScreenshotService.cs
+++ b/Ink Canvas/Services/Shell/ScreenshotService.cs
@@ -18,12 +18,12 @@
namespace Ink_Canvas.Services.Shell
{
///
- /// 截图服务(M15 寄生提取)。承载从主窗口截图 partial(MW_Screenshot.cs)与
+ /// 截图服务(寄生提取)。承载从主窗口截图 partial(MW_Screenshot.cs)与
/// 插图 partial(MW_ImageInsert.cs)搬出的截图执行段:屏幕捕获(GDI CopyFromScreen)、
/// 位图加工(墨迹叠加、形状遮罩、格式转换)、编码落盘与剪贴板写入。
/// 区域选择的交互段(选区窗口、窗口显隐编排、贴图回白板编排)仍留在主窗口壳中。
/// 禁止引用主窗口类型:设置、墨迹快照、白板页索引、DPI 缩放、通知与 UI 线程调度
- /// 均经 委托注入(M30 统一收敛为 ISyncService 前的过渡形态)。
+ /// 均经 委托注入(统一收敛为 ISyncService 前的过渡形态)。
///
internal sealed class ScreenshotService
{
@@ -44,7 +44,7 @@ internal sealed class Hooks
public Func GetDpiScale { get; set; }
/// 显示截图保存成功通知(参数为保存路径,资源格式化由主窗口侧完成)。
public Action ShowScreenshotSaveSuccess { get; set; }
- /// 在 UI 线程上执行委托(过渡形态,M30 收敛为 ISyncService)。
+ /// 在 UI 线程上执行委托(过渡形态,后续收敛为 ISyncService)。
public Action UiInvoke { get; set; }
/// 截图后自动保存墨迹(对应主窗口 SaveInkCanvasStrokes(false))。
public Action SaveInkStrokesAtScreenshot { get; set; }
@@ -147,10 +147,7 @@ internal void CaptureAndEnqueueScreenshotSave(bool isHideNotification, string fi
}
catch
{
- try { if (File.Exists(tmpStrokePath)) File.Delete(tmpStrokePath); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Screenshot] 清理临时笔画文件失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(tmpStrokePath)) File.Delete(tmpStrokePath); } catch (Exception ex) { LogService.LogException(ex); }
throw;
}
}
@@ -415,7 +412,7 @@ internal void CaptureAndSaveScreenshot(string savePath, bool isHideNotification,
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Screenshot] 截图上传失败(本地文件已保存): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
});
}
diff --git a/Ink Canvas/Services/Shell/TrayIconService.cs b/Ink Canvas/Services/Shell/TrayIconService.cs
index d9c78d7a..7767cb03 100644
--- a/Ink Canvas/Services/Shell/TrayIconService.cs
+++ b/Ink Canvas/Services/Shell/TrayIconService.cs
@@ -14,11 +14,11 @@
namespace Ink_Canvas.Services
{
///
- /// 系统托盘服务(M12 寄生提取)。承载从 App(原 MW_TrayIcon.cs)搬出的托盘图标与右键菜单逻辑。
+ /// 系统托盘服务(寄生提取)。承载从 App(原 MW_TrayIcon.cs)搬出的托盘图标与右键菜单逻辑。
/// 禁止引用 MainWindow 类型:所有对 MainWindow/App 私有成员的调用经 委托注入,
/// 事件出抛方向唯一(Service → App)。
/// 计时器使用 + 注入的 UiInvoke 回到 UI 线程
- ///(M30 统一替换为 ISyncService 前的过渡形态)。
+ ///(统一替换为 ISyncService 前的过渡形态)。
///
internal sealed class TrayIconService : IDisposable
{
@@ -47,7 +47,7 @@ internal sealed class Hooks
public Action ResetFloatingBarPosition { get; set; }
/// 写入托盘临时显示的截止时间(MainWindow 静态字段)。
public Action SetTrayTemporaryShowUntilUtc { get; set; }
- /// 在 UI 线程上执行委托(过渡形态,M30 收敛为 ISyncService)。
+ /// 在 UI 线程上执行委托(过渡形态,后续收敛为 ISyncService)。
public Action UiInvoke { get; set; }
/// 打开设置窗口(反射调用 MainWindow.BtnSettings_Click)。
public Action OpenSettings { get; set; }
@@ -334,7 +334,7 @@ public void TempShowMainWindowClicked(object sender, RoutedEventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[Tray] 查找托盘「隐藏主窗口」菜单项失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
_trayTemporaryShowRestoreHideChecked = hideItem?.IsChecked == true;
diff --git a/Ink Canvas/UInk/UInkSaveService.cs b/Ink Canvas/UInk/UInkSaveService.cs
index 11d96e26..168d6f36 100644
--- a/Ink Canvas/UInk/UInkSaveService.cs
+++ b/Ink Canvas/UInk/UInkSaveService.cs
@@ -65,11 +65,7 @@ public static void SaveFull(UInkDocument doc, string mainPath,
{
if (workDir != null)
{
- try { if (Directory.Exists(workDir)) Directory.Delete(workDir, true); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UInk] 两阶段提交后删除资源并集临时目录失败: {workDir}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (Directory.Exists(workDir)) Directory.Delete(workDir, true); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -130,10 +126,7 @@ public static void SaveFull(UInkDocument doc, string mainPath,
}
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UInk] 提取旧资源包以保留旧主文件引用失败: {extraPath}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
}
@@ -150,11 +143,7 @@ private static void AtomicReplace(string tmp, string target)
private static void TryDelete(string path)
{
- try { if (File.Exists(path)) File.Delete(path); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[UInk] 删除临时文件失败: {path}, 原因: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(path)) File.Delete(path); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
diff --git a/Ink Canvas/UInk/UInkWriter.cs b/Ink Canvas/UInk/UInkWriter.cs
index c118e5d9..ea542488 100644
--- a/Ink Canvas/UInk/UInkWriter.cs
+++ b/Ink Canvas/UInk/UInkWriter.cs
@@ -27,14 +27,9 @@ public static void Save(UInkDocument doc, string path)
else
File.Move(tmp, path);
}
- catch (Exception ex)
+ catch
{
- LogHelper.WriteLogToFile($"[UInk] 写入主文件失败(临时文件 {tmp} 未提交): {path}, 原因: {ex.Message}", LogHelper.LogType.Info);
- try { if (File.Exists(tmp)) File.Delete(tmp); }
- catch (Exception cleanupEx)
- {
- LogHelper.WriteLogToFile($"[UInk] 写入主文件失败后删除临时文件失败: {tmp}, 原因: {cleanupEx.Message}", LogHelper.LogType.Info);
- }
+ try { if (File.Exists(tmp)) File.Delete(tmp); } catch (Exception cleanupEx) { Helpers.LogService.LogException(cleanupEx); }
throw;
}
}
diff --git a/Ink Canvas/Windows/AnnouncementCenterWindow.xaml.cs b/Ink Canvas/Windows/AnnouncementCenterWindow.xaml.cs
index 35cd8a6a..92d6ac5a 100644
--- a/Ink Canvas/Windows/AnnouncementCenterWindow.xaml.cs
+++ b/Ink Canvas/Windows/AnnouncementCenterWindow.xaml.cs
@@ -55,11 +55,9 @@ private void ViewDetailsButton_Click(object sender, RoutedEventArgs e)
Process.Start(new ProcessStartInfo(item.ActionUrl) { UseShellExecute = true });
return;
}
- catch (Exception ex)
+ catch (System.Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Announcement] 公告详情点击打开链接 {item.ActionUrl} 失败: {ex.Message}",
- LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs
index 018af78e..425b0365 100644
--- a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs
+++ b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs
@@ -40,7 +40,7 @@ public static Window CreateTimerWindow()
return new CountdownTimerWindow();
}
- // 计时状态机已提取至 TimerService(M14);本窗口仅订阅状态并渲染。
+ // 计时状态机已提取至 TimerService;本窗口仅订阅状态并渲染。
private readonly TimerService _timerService;
private void OnTimerStateChanged(TimerState state)
diff --git a/Ink Canvas/Windows/CrashWindow.xaml.cs b/Ink Canvas/Windows/CrashWindow.xaml.cs
index 88c388fe..835b0e7a 100644
--- a/Ink Canvas/Windows/CrashWindow.xaml.cs
+++ b/Ink Canvas/Windows/CrashWindow.xaml.cs
@@ -113,11 +113,7 @@ private static bool IsSystemThemeLight()
if (value is int i) return i == 1;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Crash] 崩溃窗口读取系统主题注册表项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return true;
}
}
diff --git a/Ink Canvas/Windows/MiniWhiteboardWindow.xaml.cs b/Ink Canvas/Windows/MiniWhiteboardWindow.xaml.cs
index 444a858e..64bacfa6 100644
--- a/Ink Canvas/Windows/MiniWhiteboardWindow.xaml.cs
+++ b/Ink Canvas/Windows/MiniWhiteboardWindow.xaml.cs
@@ -277,11 +277,7 @@ private void ApplyPenSettings()
{
MiniInkCanvas.DefaultDrawingAttributes.Color = (Color)ColorConverter.ConvertFromString(settings.PenColor);
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[MiniBoard] 迷你白板按颜色字符串设置笔色失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
MiniInkCanvas.DefaultDrawingAttributes.Width = settings.PenWidth;
diff --git a/Ink Canvas/Windows/NewStyleMinimizedTimerWindow.xaml.cs b/Ink Canvas/Windows/NewStyleMinimizedTimerWindow.xaml.cs
index e867972f..2582aed9 100644
--- a/Ink Canvas/Windows/NewStyleMinimizedTimerWindow.xaml.cs
+++ b/Ink Canvas/Windows/NewStyleMinimizedTimerWindow.xaml.cs
@@ -139,8 +139,7 @@ private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Timer] 最小化计时器窗口拖动 DragMove 失败: {ex.Message}", LogHelper.LogType.Info);
+ Helpers.LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Windows/NewStyleTimerWindow.xaml.cs b/Ink Canvas/Windows/NewStyleTimerWindow.xaml.cs
index 171935bd..e61f03db 100644
--- a/Ink Canvas/Windows/NewStyleTimerWindow.xaml.cs
+++ b/Ink Canvas/Windows/NewStyleTimerWindow.xaml.cs
@@ -125,7 +125,7 @@ private void NewStyleTimerWindow_Closed(object sender, EventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"NewStyleTimerWindow | 取消系统事件订阅失败: {ex.Message}", LogHelper.LogType.Warning);
+ LogService.LogException(ex);
}
_timerService.Dispose();
hideTimer?.Stop();
@@ -170,7 +170,7 @@ public void RefreshTheme()
#endregion
- // 计时状态机已提取至 TimerService(M14);本窗口仅订阅状态并渲染。
+ // 计时状态机已提取至 TimerService;本窗口仅订阅状态并渲染。
private void OnTimerStateChanged(TimerState state)
{
Application.Current.Dispatcher.Invoke(() => RenderTick(state));
@@ -250,7 +250,7 @@ private void RenderTick(TimerState state)
private Timer hideTimer;
private DateTime lastActivityTime;
- // 计时状态机(M14 提取)。设定时间经 Hour/Minute/Second 属性读写。
+ // 计时状态机。设定时间经 Hour/Minute/Second 属性读写。
private readonly TimerService _timerService;
public TimeSpan? GetTotalTimeSpan()
@@ -1004,8 +1004,7 @@ private void UpdateRecentTimerDisplays()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Timer] 刷新最近计时显示失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1081,9 +1080,7 @@ private void SaveRecentTimersToRegistry()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Timer] 保存最近计时记录到 {RecentTimersJsonPath} 失败: {ex.Message}",
- LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1098,8 +1095,7 @@ private void RefreshModernBorder()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Timer] 刷新计时器窗口边框资源失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
@@ -1147,8 +1143,7 @@ private void DragTimerWindow(MouseButtonEventArgs e)
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Timer] 计时器窗口拖动 DragMove 失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs
index fbfef9cf..9e8f2f3a 100644
--- a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs
+++ b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs
@@ -32,8 +32,7 @@ public void RefreshTheme()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[UI] 操作指引窗口 RefreshTheme 应用主题失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
index bb9a417e..bbe7f7f0 100644
--- a/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
+++ b/Ink Canvas/Windows/PPTQuickPanel.xaml.cs
@@ -564,7 +564,7 @@ private void InitializeAudio()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[PPT] 初始化默认音频端点音量接口失败(音量控制将不可用): {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Windows/PPTTimeCapsule.xaml.cs b/Ink Canvas/Windows/PPTTimeCapsule.xaml.cs
index 2e251812..c4983c51 100644
--- a/Ink Canvas/Windows/PPTTimeCapsule.xaml.cs
+++ b/Ink Canvas/Windows/PPTTimeCapsule.xaml.cs
@@ -122,10 +122,7 @@ public void Dispose()
{
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[PPT] 释放时取消 SystemEvents.UserPreferenceChanged 订阅失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
// 停止并释放定时器,确保不再触发回调
try
@@ -133,38 +130,27 @@ public void Dispose()
if (timeUpdateTimer != null)
{
timeUpdateTimer.Elapsed -= TimeUpdateTimer_Elapsed;
- try { timeUpdateTimer.Stop(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 释放时停止 timeUpdateTimer 失败: {ex.Message}", LogHelper.LogType.Info); }
- try { timeUpdateTimer.Dispose(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 释放时处置 timeUpdateTimer 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { timeUpdateTimer.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
+ try { timeUpdateTimer.Dispose(); } catch (Exception ex2) { LogService.LogException(ex2); }
timeUpdateTimer = null;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[PPT] 释放 timeUpdateTimer 整体失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex3) { LogService.LogException(ex3); }
try
{
if (countdownUpdateTimer != null)
{
countdownUpdateTimer.Elapsed -= CountdownUpdateTimer_Elapsed;
- try { countdownUpdateTimer.Stop(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 释放时停止 countdownUpdateTimer 失败: {ex.Message}", LogHelper.LogType.Info); }
- try { countdownUpdateTimer.Dispose(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 释放时处置 countdownUpdateTimer 失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { countdownUpdateTimer.Stop(); } catch (Exception ex) { LogService.LogException(ex); }
+ try { countdownUpdateTimer.Dispose(); } catch (Exception ex2) { LogService.LogException(ex2); }
countdownUpdateTimer = null;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[PPT] 释放 countdownUpdateTimer 整体失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex3) { LogService.LogException(ex3); }
// 停止动画
- try { StopColonBlinkAnimation(); }
- catch (Exception ex) { LogHelper.WriteLogToFile($"[PPT] 释放时停止冒号闪烁动画失败: {ex.Message}", LogHelper.LogType.Info); }
+ try { StopColonBlinkAnimation(); } catch (Exception ex) { LogService.LogException(ex); }
}
private void InitializeTimers()
@@ -893,10 +879,7 @@ private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceCha
ApplyTheme();
}), DispatcherPriority.Normal);
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[PPT] 系统主题变更后向 UI 线程投递 ApplyTheme 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private void ApplyTheme()
diff --git a/Ink Canvas/Windows/PrivacyAgreementWindow.xaml.cs b/Ink Canvas/Windows/PrivacyAgreementWindow.xaml.cs
index d7ad426b..b6f064c8 100644
--- a/Ink Canvas/Windows/PrivacyAgreementWindow.xaml.cs
+++ b/Ink Canvas/Windows/PrivacyAgreementWindow.xaml.cs
@@ -129,11 +129,7 @@ private static bool IsSystemThemeLight()
if (value is int i) return i == 1;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 隐私说明窗口读取系统主题注册表项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return true;
}
}
diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs b/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs
index c7ae054b..75128b27 100644
--- a/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs
+++ b/Ink Canvas/Windows/SettingsViews/Helpers/AutoStartHelper.cs
@@ -21,11 +21,7 @@ public static bool StartAutomaticallyCreate(string exeName)
shortcut.Save();
return true;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Settings] 创建开机自启快捷方式 {exeName}.lnk 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { Ink_Canvas.Helpers.LogService.LogException(ex); }
return false;
}
@@ -38,11 +34,7 @@ public static bool StartAutomaticallyDel(string exeName)
".lnk");
return true;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Settings] 删除开机自启快捷方式 {exeName}.lnk 失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { Ink_Canvas.Helpers.LogService.LogException(ex); }
return false;
}
diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs b/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs
index 93b2c0ac..5855ef8e 100644
--- a/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs
+++ b/Ink Canvas/Windows/SettingsViews/Helpers/SettingsManager.cs
@@ -67,13 +67,7 @@ public static void SaveSettingsToFile()
}
catch
{
- try { if (File.Exists(tmpPath)) File.Delete(tmpPath); }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Settings] 保存设置失败后清理临时文件 {tmpPath} 失败: {ex.Message}",
- LogHelper.LogType.Info);
- }
+ try { if (File.Exists(tmpPath)) File.Delete(tmpPath); } catch (Exception ex) { LogService.LogException(ex); }
// 回退到直接覆盖,保持旧行为仍可用
ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, text));
}
diff --git a/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs b/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs
index 1d02d8b6..e276f95e 100644
--- a/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs
+++ b/Ink Canvas/Windows/SettingsViews/Helpers/WindowSettingsHelper.cs
@@ -89,10 +89,7 @@ private static void StopPPTOnlyVisibilityProbeTimer()
{
_pptOnlyVisibilityProbeTimer?.Stop();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"停止仅PPT可见性探测计时器失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private static void PPTOnlyVisibilityProbeTimer_Tick(object sender, EventArgs e)
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/AboutPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/AboutPage.xaml.cs
index e5922564..3404b68a 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/AboutPage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/AboutPage.xaml.cs
@@ -101,9 +101,7 @@ private void UsageRefreshTimer_Tick(object sender, EventArgs e)
var n = System.Threading.Interlocked.Increment(ref _usageRefreshDiagCount);
if (n == 1 || n % 100 == 0)
{
- LogHelper.WriteLogToFile(
- $"[Settings] 关于页面刷新累计使用时长失败(累计 {n} 次): {ex.Message}",
- LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
}
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CloudStoragePage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/CloudStoragePage.xaml.cs
index 9330c947..8d446829 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/CloudStoragePage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/CloudStoragePage.xaml.cs
@@ -639,8 +639,9 @@ private async Task TestConnectionAsync()
SetConnectionStatus(string.Format(CloudStorageStrings.CloudStorage_ConnectedFormat, whiteboards.Count), Color.FromRgb(34, 197, 94));
LoadClasses(whiteboards, result.User);
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException ex)
{
+ LogService.LogException(ex);
}
catch (Exception ex)
{
@@ -669,8 +670,7 @@ private void CancelConnectionTest()
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile(
- $"[Settings] 取消云存储连接测试失败: {ex.Message}", LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
finally
{
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/FloatingBarThemeMarketPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/FloatingBarThemeMarketPage.xaml.cs
index fdd2fe6b..42deda20 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/FloatingBarThemeMarketPage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/FloatingBarThemeMarketPage.xaml.cs
@@ -49,13 +49,7 @@ private async void InstallButton_Click(object sender, RoutedEventArgs e)
if (installed)
{
// mark this entry as installed immediately so UI reflects state even if subsequent refresh fails
- try { entry.IsInstalled = true; }
- catch (System.Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 主题市场安装成功后回写条目 {entry?.Manifest?.Id} 已安装标记失败: {ex.Message}",
- LogHelper.LogType.Info);
- }
+ try { entry.IsInstalled = true; } catch (System.Exception ex) { LogService.LogException(ex); }
var mainWindow = Application.Current.MainWindow as MainWindow;
mainWindow?.FloatingBarThemeService?.LoadThemes();
@@ -68,20 +62,8 @@ private async void InstallButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"FloatingBarThemeMarketPage | Refresh after install failed: {ex}");
// refresh failed: revert temporary installed flag and re-enable button so user can retry
- try { entry.IsInstalled = false; }
- catch (System.Exception rollbackEx)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 主题市场刷新失败后回滚条目 {entry?.Manifest?.Id} 已安装标记失败: {rollbackEx.Message}",
- LogHelper.LogType.Info);
- }
- try { button.IsEnabled = true; }
- catch (System.Exception buttonEx)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 主题市场刷新失败后恢复安装按钮可用状态失败: {buttonEx.Message}",
- LogHelper.LogType.Info);
- }
+ try { entry.IsInstalled = false; } catch (System.Exception ex2) { LogService.LogException(ex2); }
+ try { button.IsEnabled = true; } catch (System.Exception ex3) { LogService.LogException(ex3); }
}
// 如果设置窗口中的主题管理页存在,则让它也刷新(使安装的主题立刻在管理页可见)
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PPTPageFlipPreviewPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/PPTPageFlipPreviewPage.xaml.cs
index 955a2b90..e742b856 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/PPTPageFlipPreviewPage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/PPTPageFlipPreviewPage.xaml.cs
@@ -175,11 +175,7 @@ private void ClosePreviewWindow()
{
PPTPageFlipPreviewOverlayWindow.ActiveInstance.Close();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 关闭 PPT 翻页预览浮层窗口失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
if (PPTPageFlipPreviewWindow.ActiveInstance != null)
{
@@ -188,11 +184,7 @@ private void ClosePreviewWindow()
WindowTopmostManager.UnregisterWindow(PPTPageFlipPreviewWindow.ActiveInstance);
PPTPageFlipPreviewWindow.ActiveInstance.Close();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[UI] 注销置顶并关闭 PPT 翻页预览窗口失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PluginPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/PluginPage.xaml.cs
index cc7c1c16..26e9d8d6 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/PluginPage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/PluginPage.xaml.cs
@@ -354,12 +354,7 @@ private void OpenFolder_Click(object sender, RoutedEventArgs e)
if (Directory.Exists(info.PluginFolderPath))
Process.Start(new ProcessStartInfo { FileName = info.PluginFolderPath, UseShellExecute = true });
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Settings] 打开插件目录 {info.PluginFolderPath} 失败: {ex.Message}",
- LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private void TogglePluginLoad_Click(object sender, RoutedEventArgs e)
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StoragePage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/StoragePage.xaml.cs
index 9c65b5eb..8ef0c9e4 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/StoragePage.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/Pages/StoragePage.xaml.cs
@@ -180,11 +180,7 @@ private void UpdateUI()
}
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile(
- $"[Settings] 统计存储占用时读取磁盘容量失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
DiskPercentTextBlock.Text = "—";
}
@@ -356,8 +352,7 @@ private static void TryDeleteDirectory(string path)
}
// 上述遍历/删除辅助方法对磁盘上每个文件调用一次,坏设备(掉盘、权限拒绝、文件被占)
- // 下会连续抛成千上万次,直接写日志会瞬间刷爆 5MB 日志并触发 LogHelper 清空整个
- // Logs 目录把现场证据一起清掉,因此统一节流:只记第 1 次和每 100 次。
+ // 下会连续抛成千上万次,直接写日志会瞬间刷爆日志文件,因此统一节流:只记第 1 次和每 100 次。
private static int _storageDiagCount;
private static void LogStorageDiagException(string what, Exception ex)
@@ -365,9 +360,7 @@ private static void LogStorageDiagException(string what, Exception ex)
var n = System.Threading.Interlocked.Increment(ref _storageDiagCount);
if (n == 1 || n % 100 == 0)
{
- LogHelper.WriteLogToFile(
- $"[Settings] 存储清理/统计时 {what}(累计 {n} 次): {ex.Message}",
- LogHelper.LogType.Info);
+ LogService.LogException(ex);
}
}
diff --git a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs
index 68a23794..35290789 100644
--- a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs
+++ b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs
@@ -266,10 +266,7 @@ private void ApplyCurrentTheme()
};
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, elementTheme);
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 应用设置窗口主题失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private static bool IsSystemThemeLight()
@@ -282,10 +279,7 @@ private static bool IsSystemThemeLight()
if (themeKey?.GetValue("AppsUseLightTheme") is int v) return v == 1;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 读取系统深浅色注册表项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return false;
}
@@ -537,10 +531,7 @@ private void OnRootFrameNavigated(object sender, NavigationEventArgs e)
(rootFrame.Content as FloatingBarThemePage)?.RefreshThemes();
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 导航后刷新浮动栏主题列表失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -552,10 +543,7 @@ public void RefreshFloatingBarThemePage()
{
(rootFrame.Content as FloatingBarThemePage)?.RefreshThemes();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 外部请求刷新浮动栏主题页失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -761,10 +749,7 @@ private void EnsureSearchIndexBuilt()
{
if (!feRoot.IsLoaded)
{
- try { feRoot.ApplyTemplate(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 预展开页面模板失败,搜索索引可能不完整 (tag={tag}): {ex.Message}", LogHelper.LogType.Info);
- }
+ try { feRoot.ApplyTemplate(); } catch (Exception ex) { LogService.LogException(ex); }
}
CollectEntriesFromPage(feRoot, tag);
}
@@ -867,10 +852,7 @@ private void NavigateToSearchEntry(SearchEntry entry)
{
Dispatcher.BeginInvoke(new Action(() =>
{
- try { fe.BringIntoView(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 滚动到搜索命中的设置项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { fe.BringIntoView(); } catch (Exception ex) { LogService.LogException(ex); }
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
@@ -1098,10 +1080,7 @@ public void HighlightSetting(string settingKey)
private void FlashHighlight(FrameworkElement target)
{
- try { target.BringIntoView(); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 高亮前滚动到目标设置项失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { target.BringIntoView(); } catch (Exception ex) { LogService.LogException(ex); }
Dispatcher.BeginInvoke(new Action(() =>
{
@@ -1140,17 +1119,11 @@ private void FlashHighlight(FrameworkElement target)
layer.Remove(adorner);
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 高亮闪烁切换可见性失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
};
timer.Start();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 挂载设置项高亮装饰器失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex2) { LogService.LogException(ex2); }
}), DispatcherPriority.Background);
}
@@ -1188,10 +1161,7 @@ private void FlashWithEffect(FrameworkElement target)
};
timer.Start();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 设置项高亮(Effect 退路方案)失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1247,10 +1217,7 @@ private void HookSettingsCardInputHandlers(FrameworkElement root)
root.PreviewTouchUp -= SettingsCard_TouchUp;
root.PreviewTouchUp += SettingsCard_TouchUp;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 挂载设置项右键/长按事件失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private void SettingsCard_RightButtonUp(object sender, MouseButtonEventArgs e)
@@ -1295,10 +1262,7 @@ private void SettingsCard_TouchDown(object sender, TouchEventArgs e)
// 长按触发后取消后续的触摸提升(避免立即触发点击)
e.TouchDevice.Capture(null);
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 长按后释放触摸捕获失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
CopySettingUriFromElement(target);
};
@@ -1325,10 +1289,7 @@ private void SettingsCard_TouchMove(object sender, TouchEventArgs e)
_longPressTimer.Stop();
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 处理设置项触摸移动失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
private void SettingsCard_TouchUp(object sender, TouchEventArgs e)
@@ -1338,10 +1299,7 @@ private void SettingsCard_TouchUp(object sender, TouchEventArgs e)
_longPressTimer?.Stop();
_longPressTimer = null;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 停止设置项长按计时器失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
///
@@ -1380,10 +1338,7 @@ private void CopySettingUriFromElement(FrameworkElement target)
string pageTag = GetCurrentPageTag();
string uri = BuildSettingsUri(pageTag, key);
- try { Clipboard.SetText(uri); } catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 复制设置项 URL 到剪贴板失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ try { Clipboard.SetText(uri); } catch (Exception ex2) { LogService.LogException(ex2); }
ShowCopyUriInfoBar();
}
@@ -1404,10 +1359,7 @@ private static string GetSettingsHeaderText(FrameworkElement target)
if (target is iNKORE.UI.WPF.Modern.Controls.SettingsExpander se)
return (se.Header as string)?.Trim() ?? se.Header?.ToString()?.Trim();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 读取设置项标题文本失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
return null;
}
@@ -1433,17 +1385,11 @@ private void ShowCopyUriInfoBar()
CopyUriInfoBar.IsOpen = false;
CopyUriInfoBar.Visibility = Visibility.Collapsed;
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 收起复制成功提示条失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
};
_copyUriInfoBarTimer.Start();
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 显示复制成功提示条失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex2) { LogService.LogException(ex2); }
}
#endregion
@@ -1508,10 +1454,7 @@ public void UpdateUpdateBadgeVisibility()
badge.Visibility = (hasUpdate && !_updateBadgeDismissed) ? Visibility.Visible : Visibility.Collapsed;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 更新「更新」导航项徽标失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
}
public void UpdateAnnouncementUnreadBadge()
@@ -1527,10 +1470,7 @@ public void UpdateAnnouncementUnreadBadge()
AnnouncementUnreadInfoBadge.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[Settings] 更新公告未读徽标失败: {ex.Message}", LogHelper.LogType.Info);
- }
+ catch (Exception ex) { LogService.LogException(ex); }
});
}
}
diff --git a/InkCanvas.IACoreHelper/InkCanvas.IACoreHelper.csproj b/InkCanvas.IACoreHelper/InkCanvas.IACoreHelper.csproj
index 80109aa9..368a3af8 100644
--- a/InkCanvas.IACoreHelper/InkCanvas.IACoreHelper.csproj
+++ b/InkCanvas.IACoreHelper/InkCanvas.IACoreHelper.csproj
@@ -61,6 +61,7 @@
+
diff --git a/InkCanvas.IACoreHelper/LogService.cs b/InkCanvas.IACoreHelper/LogService.cs
new file mode 100644
index 00000000..2e498c48
--- /dev/null
+++ b/InkCanvas.IACoreHelper/LogService.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace InkCanvas.IACoreHelper
+{
+ ///
+ /// 重构期吞异常治理用最小日志设施。
+ /// 写入 %AppData%/InkCanvasForClass/logs/refactor.log,单文件上限 1MB,滚动保留 3 个。
+ /// 与主程序集 Ink_Canvas.Helpers.LogService 同逻辑;本程序集未引用主程序集,故保留本地副本。
+ ///
+ internal static class LogService
+ {
+ private const long MaxFileSizeBytes = 1024 * 1024;
+ private const int RetainedFiles = 3;
+ private static readonly object WriteLock = new object();
+
+ private static string LogDirectory =>
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "InkCanvasForClass", "logs");
+
+ private static string LogFile => Path.Combine(LogDirectory, "refactor.log");
+
+ public static void LogException(Exception ex, [CallerMemberName] string member = "")
+ {
+ try
+ {
+ lock (WriteLock)
+ {
+ Directory.CreateDirectory(LogDirectory);
+ RollIfNeeded();
+ File.AppendAllText(LogFile,
+ $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{member}] {ex}{Environment.NewLine}");
+ }
+ }
+ catch
+ {
+ // 日志设施自身失败时静默放弃,绝不影响主流程。
+ }
+ }
+
+ private static void RollIfNeeded()
+ {
+ if (!File.Exists(LogFile) || new FileInfo(LogFile).Length < MaxFileSizeBytes) return;
+ for (var i = RetainedFiles - 1; i >= 1; i--)
+ {
+ var older = $"{LogFile}.{i}";
+ var newer = $"{LogFile}.{i + 1}";
+ if (File.Exists(newer)) File.Delete(newer);
+ if (File.Exists(older)) File.Move(older, newer);
+ }
+ File.Move(LogFile, $"{LogFile}.1");
+ }
+ }
+}
diff --git a/InkCanvas.IACoreHelper/Program.cs b/InkCanvas.IACoreHelper/Program.cs
index bebdb066..7465b11d 100644
--- a/InkCanvas.IACoreHelper/Program.cs
+++ b/InkCanvas.IACoreHelper/Program.cs
@@ -49,7 +49,7 @@ static void Main(string[] args)
{
parent.WaitForExit();
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
finally
{
parentExited.Set();
@@ -76,7 +76,7 @@ static void Main(string[] args)
// 主线程阻塞直到父进程消失。守护线程一旦 Set,主线程退出,进程随之终止。
parentExited.Wait();
}
- try { parent.Dispose(); } catch { }
+ try { parent.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
Environment.Exit(0);
}
catch (Exception ex)
@@ -104,7 +104,7 @@ private static void RunPipeServer(string pipeName, string sharedMemoryNamePrefix
if (!shouldExit())
continue;
var server = currentServer;
- try { server?.Dispose(); } catch { }
+ try { server?.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
return;
}
})
@@ -226,7 +226,7 @@ private static void RunPipeServer(string pipeName, string sharedMemoryNamePrefix
}
finally
{
- try { server.Dispose(); } catch { }
+ try { server.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
currentServer = null;
}
}
diff --git a/InkCanvas.LiquidGlassMagHost/LogService.cs b/InkCanvas.LiquidGlassMagHost/LogService.cs
new file mode 100644
index 00000000..aecd47fa
--- /dev/null
+++ b/InkCanvas.LiquidGlassMagHost/LogService.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace InkCanvas.LiquidGlassMagHost
+{
+ ///
+ /// 重构期吞异常治理用最小日志设施。
+ /// 写入 %AppData%/InkCanvasForClass/logs/refactor.log,单文件上限 1MB,滚动保留 3 个。
+ /// 与主程序集 Ink_Canvas.Helpers.LogService 同逻辑;本程序集未引用主程序集,故保留本地副本。
+ ///
+ internal static class LogService
+ {
+ private const long MaxFileSizeBytes = 1024 * 1024;
+ private const int RetainedFiles = 3;
+ private static readonly object WriteLock = new object();
+
+ private static string LogDirectory =>
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "InkCanvasForClass", "logs");
+
+ private static string LogFile => Path.Combine(LogDirectory, "refactor.log");
+
+ public static void LogException(Exception ex, [CallerMemberName] string member = "")
+ {
+ try
+ {
+ lock (WriteLock)
+ {
+ Directory.CreateDirectory(LogDirectory);
+ RollIfNeeded();
+ File.AppendAllText(LogFile,
+ $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{member}] {ex}{Environment.NewLine}");
+ }
+ }
+ catch
+ {
+ // 日志设施自身失败时静默放弃,绝不影响主流程。
+ }
+ }
+
+ private static void RollIfNeeded()
+ {
+ if (!File.Exists(LogFile) || new FileInfo(LogFile).Length < MaxFileSizeBytes) return;
+ for (var i = RetainedFiles - 1; i >= 1; i--)
+ {
+ var older = $"{LogFile}.{i}";
+ var newer = $"{LogFile}.{i + 1}";
+ if (File.Exists(newer)) File.Delete(newer);
+ if (File.Exists(older)) File.Move(older, newer);
+ }
+ File.Move(LogFile, $"{LogFile}.1");
+ }
+ }
+}
diff --git a/InkCanvas.LiquidGlassMagHost/MagnifierHost.cs b/InkCanvas.LiquidGlassMagHost/MagnifierHost.cs
index 38adc2bf..50f4ac64 100644
--- a/InkCanvas.LiquidGlassMagHost/MagnifierHost.cs
+++ b/InkCanvas.LiquidGlassMagHost/MagnifierHost.cs
@@ -243,7 +243,7 @@ private static void ShutdownMag()
DestroyHost();
if (_magInitialized)
{
- try { MagUninitialize(); } catch { }
+ try { MagUninitialize(); } catch (Exception ex) { LogService.LogException(ex); }
_magInitialized = false;
}
}
diff --git a/InkCanvas.PowerPointAddIn/Core/PPTController.cs b/InkCanvas.PowerPointAddIn/Core/PPTController.cs
index 5f2d30ff..d88375b6 100644
--- a/InkCanvas.PowerPointAddIn/Core/PPTController.cs
+++ b/InkCanvas.PowerPointAddIn/Core/PPTController.cs
@@ -43,13 +43,13 @@ public PPTState GetState()
{
var pres = _application.ActivePresentation;
state.PresentationName = pres.Name;
- try { state.PresentationFullName = pres.FullName; } catch { }
+ try { state.PresentationFullName = pres.FullName; } catch (Exception ex) { LogService.LogException(ex); }
state.TotalSlides = pres.Slides.Count;
state.HasHiddenSlides = HasHiddenSlides(pres);
state.HasAutoPlayTimings = HasAutoPlayTimings(pres);
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
try
{
@@ -59,7 +59,7 @@ public PPTState GetState()
state.SlideIndex = _application.SlideShowWindows[1].View.CurrentShowPosition;
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
return state;
}
@@ -314,7 +314,7 @@ private static bool IsVideoShape(Microsoft.Office.Interop.PowerPoint.Shape shape
return true;
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
// 无法确认是否为媒体播放器时,不视为视频,避免把普通 ActiveX 控件误判为视频
return false;
}
@@ -327,10 +327,10 @@ private static bool IsVideoShape(Microsoft.Office.Interop.PowerPoint.Shape shape
if ((int)(object)shape.MediaType == 3) // ppMediaTypeMovie
return true;
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
}
}
- catch { }
+ catch (Exception ex2) { LogService.LogException(ex2); }
return false;
}
@@ -345,7 +345,7 @@ private static bool HasHiddenSlides(Microsoft.Office.Interop.PowerPoint.Presenta
return true;
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
return false;
}
@@ -360,7 +360,7 @@ private static bool HasAutoPlayTimings(Microsoft.Office.Interop.PowerPoint.Prese
return true;
}
}
- catch { }
+ catch (Exception ex) { LogService.LogException(ex); }
return false;
}
}
diff --git a/InkCanvas.PowerPointAddIn/IPC/PipeHost.cs b/InkCanvas.PowerPointAddIn/IPC/PipeHost.cs
index 2ea47d7a..ad2a543d 100644
--- a/InkCanvas.PowerPointAddIn/IPC/PipeHost.cs
+++ b/InkCanvas.PowerPointAddIn/IPC/PipeHost.cs
@@ -55,7 +55,7 @@ public void Start()
public void Stop()
{
- try { _cts?.Cancel(); } catch { }
+ try { _cts?.Cancel(); } catch (Exception ex) { LogService.LogException(ex); }
_cts?.Dispose();
_cts = null;
_clientConnected = false;
@@ -98,7 +98,7 @@ private async Task AcceptLoop(CancellationToken token)
{
_clientConnected = false;
_currentPipe = null;
- try { pipe?.Dispose(); } catch { }
+ try { pipe?.Dispose(); } catch (Exception ex) { LogService.LogException(ex); }
}
}
}
diff --git a/InkCanvas.PowerPointAddIn/InkCanvas.PowerPointAddIn.csproj b/InkCanvas.PowerPointAddIn/InkCanvas.PowerPointAddIn.csproj
index 78ebd198..aa29b2b0 100644
--- a/InkCanvas.PowerPointAddIn/InkCanvas.PowerPointAddIn.csproj
+++ b/InkCanvas.PowerPointAddIn/InkCanvas.PowerPointAddIn.csproj
@@ -215,6 +215,9 @@
ThisAddIn.Designer.xml
+
+ Code
+
Code
diff --git a/InkCanvas.PowerPointAddIn/LogService.cs b/InkCanvas.PowerPointAddIn/LogService.cs
new file mode 100644
index 00000000..5028093f
--- /dev/null
+++ b/InkCanvas.PowerPointAddIn/LogService.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace InkCanvas.PowerPointAddIn
+{
+ ///
+ /// 重构期吞异常治理用最小日志设施。
+ /// 写入 %AppData%/InkCanvasForClass/logs/refactor.log,单文件上限 1MB,滚动保留 3 个。
+ /// 与主程序集 Ink_Canvas.Helpers.LogService 同逻辑;本程序集未引用主程序集,故保留本地副本。
+ ///
+ internal static class LogService
+ {
+ private const long MaxFileSizeBytes = 1024 * 1024;
+ private const int RetainedFiles = 3;
+ private static readonly object WriteLock = new object();
+
+ private static string LogDirectory =>
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "InkCanvasForClass", "logs");
+
+ private static string LogFile => Path.Combine(LogDirectory, "refactor.log");
+
+ public static void LogException(Exception ex, [CallerMemberName] string member = "")
+ {
+ try
+ {
+ lock (WriteLock)
+ {
+ Directory.CreateDirectory(LogDirectory);
+ RollIfNeeded();
+ File.AppendAllText(LogFile,
+ $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{member}] {ex}{Environment.NewLine}");
+ }
+ }
+ catch
+ {
+ // 日志设施自身失败时静默放弃,绝不影响主流程。
+ }
+ }
+
+ private static void RollIfNeeded()
+ {
+ if (!File.Exists(LogFile) || new FileInfo(LogFile).Length < MaxFileSizeBytes) return;
+ for (var i = RetainedFiles - 1; i >= 1; i--)
+ {
+ var older = $"{LogFile}.{i}";
+ var newer = $"{LogFile}.{i + 1}";
+ if (File.Exists(newer)) File.Delete(newer);
+ if (File.Exists(older)) File.Move(older, newer);
+ }
+ File.Move(LogFile, $"{LogFile}.1");
+ }
+ }
+}
diff --git a/package.json b/package.json
index be9accea..bd9dcbbd 100644
--- a/package.json
+++ b/package.json
@@ -1,4 +1,8 @@
{
+ "scripts": {
+ "test": "dotnet test \"Ink Canvas.sln\" -c Debug --no-restore",
+ "build": "dotnet build \"Ink Canvas.sln\" -c Debug"
+ },
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1"