using Ink_Canvas.Helpers; using Ink_Canvas.Helpers.Plugins; using Ink_Canvas.Windows; using iNKORE.UI.WPF.Modern; using iNKORE.UI.WPF.Modern.Controls; using Microsoft.Win32; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Forms; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Threading; using Application = System.Windows.Application; using Brushes = System.Windows.Media.Brushes; using Button = System.Windows.Controls.Button; using Cursor = System.Windows.Input.Cursor; using Cursors = System.Windows.Input.Cursors; using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs; using File = System.IO.File; using GroupBox = System.Windows.Controls.GroupBox; using MessageBox = System.Windows.MessageBox; using Point = System.Windows.Point; namespace Ink_Canvas { public partial class MainWindow : Window { // 新增:每一页一个Canvas对象 private List whiteboardPages = new List(); private int currentPageIndex; private System.Windows.Controls.Canvas currentCanvas; private AutoUpdateHelper.UpdateLineGroup AvailableLatestLineGroup; // 全局快捷键管理器 private GlobalHotkeyManager _globalHotkeyManager; // 墨迹渐隐管理器 private InkFadeManager _inkFadeManager; #region Window Initialization public MainWindow() { /* 处于画板模式内:Topmost == false / currentMode != 0 处于 PPT 放映内:BtnPPTSlideShowEnd.Visibility */ InitializeComponent(); BlackboardLeftSide.Visibility = Visibility.Collapsed; BlackboardCenterSide.Visibility = Visibility.Collapsed; BlackboardRightSide.Visibility = Visibility.Collapsed; BorderTools.Visibility = Visibility.Collapsed; BorderSettings.Visibility = Visibility.Collapsed; LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; BorderSettings.Margin = new Thickness(0, 0, 0, 0); TwoFingerGestureBorder.Visibility = Visibility.Collapsed; BoardTwoFingerGestureBorder.Visibility = Visibility.Collapsed; BorderDrawShape.Visibility = Visibility.Collapsed; BoardBorderDrawShape.Visibility = Visibility.Collapsed; GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; //if (!App.StartArgs.Contains("-o")) ViewBoxStackPanelMain.Visibility = Visibility.Collapsed; ViewBoxStackPanelShapes.Visibility = Visibility.Collapsed; var workingArea = Screen.PrimaryScreen.WorkingArea; // 考虑快捷调色盘的宽度,确保浮动栏有足够空间 double floatingBarWidth = 284; // 基础宽度 if (Settings.Appearance.IsShowQuickColorPalette) { // 根据显示模式调整宽度 if (Settings.Appearance.QuickColorPaletteDisplayMode == 0) { // 单行显示模式,自适应宽度,但需要足够空间显示6个颜色 floatingBarWidth = Math.Max(floatingBarWidth, 120); } else { // 双行显示模式,宽度较大 floatingBarWidth = Math.Max(floatingBarWidth, 820); } } ViewboxFloatingBar.Margin = new Thickness( (workingArea.Width - floatingBarWidth) / 2, workingArea.Bottom - 60 - workingArea.Top, -2000, -200); // 新增:只在屏幕模式下初始化浮动栏动画 if (currentMode == 0) { ViewboxFloatingBarMarginAnimation(100, true); } try { if (File.Exists("debug.ini")) Label.Visibility = Visibility.Visible; } catch (Exception ex) { LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); } try { if (File.Exists("Log.txt")) { var fileInfo = new FileInfo("Log.txt"); var fileSizeInKB = fileInfo.Length / 1024; if (fileSizeInKB > 512) try { File.Delete("Log.txt"); LogHelper.WriteLogToFile( "The Log.txt file has been successfully deleted. Original file size: " + fileSizeInKB + " KB"); } catch (Exception ex) { LogHelper.WriteLogToFile( ex + " | Can not delete the Log.txt file. File size: " + fileSizeInKB + " KB", LogHelper.LogType.Error); } } } catch (Exception ex) { LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); } InitTimers(); timeMachine.OnRedoStateChanged += TimeMachine_OnRedoStateChanged; timeMachine.OnUndoStateChanged += TimeMachine_OnUndoStateChanged; inkCanvas.Strokes.StrokesChanged += StrokesOnStrokesChanged; SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; try { if (File.Exists("SpecialVersion.ini")) SpecialVersionResetToSuggestion_Click(); } catch (Exception ex) { LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); } CheckColorTheme(true); CheckPenTypeUIState(); // 初始化墨迹平滑管理器 _inkSmoothingManager = new InkSmoothingManager(Dispatcher); // 初始化墨迹渐隐管理器 _inkFadeManager = new InkFadeManager(this); // 注册输入事件 inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown; inkCanvas.StylusDown += inkCanvas_StylusDown; inkCanvas.MouseRightButtonUp += InkCanvas_MouseRightButtonUp; // 初始化第一页Canvas var firstCanvas = new System.Windows.Controls.Canvas(); whiteboardPages.Add(firstCanvas); InkCanvasGridForInkReplay.Children.Add(firstCanvas); currentPageIndex = 0; ShowPage(currentPageIndex); // 手动实现触摸滑动 double leftTouchStartY = 0; double leftScrollStartOffset = 0; bool leftIsTouching = false; BlackBoardLeftSidePageListScrollViewer.TouchDown += (s, e) => { leftIsTouching = true; leftTouchStartY = e.GetTouchPoint(BlackBoardLeftSidePageListScrollViewer).Position.Y; leftScrollStartOffset = BlackBoardLeftSidePageListScrollViewer.VerticalOffset; BlackBoardLeftSidePageListScrollViewer.CaptureTouch(e.TouchDevice); e.Handled = true; }; BlackBoardLeftSidePageListScrollViewer.TouchMove += (s, e) => { if (leftIsTouching) { double currentY = e.GetTouchPoint(BlackBoardLeftSidePageListScrollViewer).Position.Y; double delta = leftTouchStartY - currentY; BlackBoardLeftSidePageListScrollViewer.ScrollToVerticalOffset(leftScrollStartOffset + delta); e.Handled = true; } }; BlackBoardLeftSidePageListScrollViewer.TouchUp += (s, e) => { leftIsTouching = false; BlackBoardLeftSidePageListScrollViewer.ReleaseTouchCapture(e.TouchDevice); e.Handled = true; }; double rightTouchStartY = 0; double rightScrollStartOffset = 0; bool rightIsTouching = false; BlackBoardRightSidePageListScrollViewer.TouchDown += (s, e) => { rightIsTouching = true; rightTouchStartY = e.GetTouchPoint(BlackBoardRightSidePageListScrollViewer).Position.Y; rightScrollStartOffset = BlackBoardRightSidePageListScrollViewer.VerticalOffset; BlackBoardRightSidePageListScrollViewer.CaptureTouch(e.TouchDevice); e.Handled = true; }; BlackBoardRightSidePageListScrollViewer.TouchMove += (s, e) => { if (rightIsTouching) { double currentY = e.GetTouchPoint(BlackBoardRightSidePageListScrollViewer).Position.Y; double delta = rightTouchStartY - currentY; BlackBoardRightSidePageListScrollViewer.ScrollToVerticalOffset(rightScrollStartOffset + delta); e.Handled = true; } }; BlackBoardRightSidePageListScrollViewer.TouchUp += (s, e) => { rightIsTouching = false; BlackBoardRightSidePageListScrollViewer.ReleaseTouchCapture(e.TouchDevice); e.Handled = true; }; // 初始化无焦点模式开关 ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode; ApplyNoFocusMode(); // 初始化窗口置顶开关 ToggleSwitchAlwaysOnTop.IsOn = Settings.Advanced.IsAlwaysOnTop; ApplyAlwaysOnTop(); // 添加窗口激活事件处理,确保置顶状态在窗口重新激活时得到保持 Activated += Window_Activated; Deactivated += Window_Deactivated; // 为浮动栏按钮添加触摸事件支持 AddTouchSupportToFloatingBarButtons(); // 为滑块控件添加触摸事件支持 AddTouchSupportToSliders(); } #endregion #region Ink Canvas Functions private Color Ink_DefaultColor = Colors.Red; private DrawingAttributes drawingAttributes; private InkSmoothingManager _inkSmoothingManager; private void loadPenCanvas() { try { //drawingAttributes = new DrawingAttributes(); drawingAttributes = inkCanvas.DefaultDrawingAttributes; drawingAttributes.Color = Ink_DefaultColor; drawingAttributes.Height = 2.5; drawingAttributes.Width = 2.5; drawingAttributes.IsHighlighter = false; // 默认使用高级贝塞尔曲线平滑,如果未启用则使用原来的FitToCurve if (Settings.Canvas.UseAdvancedBezierSmoothing) { drawingAttributes.FitToCurve = false; } else { drawingAttributes.FitToCurve = Settings.Canvas.FitToCurve; } inkCanvas.Gesture += InkCanvas_Gesture; } catch { } } //ApplicationGesture lastApplicationGesture = ApplicationGesture.AllGestures; private DateTime lastGestureTime = DateTime.Now; private void InkCanvas_Gesture(object sender, InkCanvasGestureEventArgs e) { var gestures = e.GetGestureRecognitionResults(); try { foreach (var gest in gestures) //Trace.WriteLine(string.Format("Gesture: {0}, Confidence: {1}", gest.ApplicationGesture, gest.RecognitionConfidence)); // 只有在PPT放映模式下才响应翻页手势 if (StackPanelPPTControls.Visibility == Visibility.Visible && BtnPPTSlideShowEnd.Visibility == Visibility.Visible && PPTManager?.IsInSlideShow == true) { if (gest.ApplicationGesture == ApplicationGesture.Left) { // 直接发送翻页请求到PPT放映软件 SendKeyToPPTSlideShow(false); // 下一页 } if (gest.ApplicationGesture == ApplicationGesture.Right) { // 直接发送翻页请求到PPT放映软件 SendKeyToPPTSlideShow(true); // 上一页 } } } catch { } } private void inkCanvas_EditingModeChanged(object sender, RoutedEventArgs e) { var inkCanvas1 = sender as InkCanvas; if (inkCanvas1 == null) return; // 使用辅助方法设置光标 SetCursorBasedOnEditingMode(inkCanvas1); if (Settings.Canvas.IsShowCursor) { if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink || inkCanvas1.EditingMode == InkCanvasEditingMode.Select || drawingShapeMode != 0) inkCanvas1.ForceCursor = true; else inkCanvas1.ForceCursor = false; } else { // 套索选择模式下始终强制显示光标,即使用户设置不显示光标 if (inkCanvas1.EditingMode == InkCanvasEditingMode.Select) { inkCanvas1.ForceCursor = true; } else { inkCanvas1.ForceCursor = false; } } if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink) forcePointEraser = !forcePointEraser; // 处理高级橡皮擦覆盖层的启用/禁用 var eraserOverlay = FindName("AdvancedEraserOverlay") as Border; if (eraserOverlay != null) { if (inkCanvas1.EditingMode == InkCanvasEditingMode.EraseByPoint) { // 橡皮擦模式下启用覆盖层 eraserOverlay.IsHitTestVisible = true; Trace.WriteLine("Advanced Eraser: Overlay enabled in eraser mode"); } else { // 其他模式下禁用覆盖层 eraserOverlay.IsHitTestVisible = false; // 同时禁用高级橡皮擦系统 DisableAdvancedEraserSystem(); Trace.WriteLine("Advanced Eraser: Overlay disabled in non-eraser mode"); } } } #endregion Ink Canvas #region Definations and Loading public static Settings Settings = new Settings(); public static string settingsFileName = "Settings.json"; private bool isLoaded; private bool forcePointEraser; private void Window_Loaded(object sender, RoutedEventArgs e) { loadPenCanvas(); //加载设置 LoadSettings(true); // 检查保存路径是否可用,不可用则修正 try { string savePath = Settings.Automation.AutoSavedStrokesLocation; bool needFix = false; if (string.IsNullOrWhiteSpace(savePath) || !Directory.Exists(savePath)) { needFix = true; } else { // 检查是否可写 try { string testFile = Path.Combine(savePath, "test.tmp"); File.WriteAllText(testFile, "test"); File.Delete(testFile); } catch { needFix = true; } } if (needFix) { string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves"); Settings.Automation.AutoSavedStrokesLocation = newPath; if (!Directory.Exists(newPath)) Directory.CreateDirectory(newPath); SaveSettingsToFile(); LogHelper.WriteLogToFile($"自动修正保存路径为: {newPath}"); } } catch (Exception ex) { LogHelper.WriteLogToFile($"检测或修正保存路径时出错: {ex.Message}", LogHelper.LogType.Error); } // 加载自定义背景颜色 LoadCustomBackgroundColor(); // 注册设置面板滚动事件 if (SettingsPanelScrollViewer != null) { SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; } // 初始化PPT管理器 InitializePPTManagers(); // 如果启用PPT支持,开始监控 if (Settings.PowerPointSettings.PowerPointSupport) { StartPPTMonitoring(); } // 如果启用PowerPoint联动增强功能,启动进程守护 if (Settings.PowerPointSettings.EnablePowerPointEnhancement) { StartPowerPointProcessMonitoring(); } // HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow(); if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed; ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light; SystemEvents_UserPreferenceChanged(null, null); //TextBlockVersion.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString(); LogHelper.WriteLogToFile("Ink Canvas Loaded", LogHelper.LogType.Event); isLoaded = true; BlackBoardLeftSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; BlackBoardRightSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5; BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5; // 应用颜色主题,这将考虑自定义背景色 CheckColorTheme(true); BtnWhiteBoardSwitchPrevious.IsEnabled = CurrentWhiteboardIndex != 1; BorderInkReplayToolBox.Visibility = Visibility.Collapsed; // 提前加载IA库,优化第一笔等待时间 if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess) { var strokeEmpty = new StrokeCollection(); InkRecognizeHelper.RecognizeShape(strokeEmpty); } SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged; // 自动收纳到侧边栏 if (Settings.Startup.IsFoldAtStartup) { FoldFloatingBar_MouseUp(new object(), null); } // 恢复崩溃后操作设置 if (App.CrashAction == App.CrashActionType.SilentRestart) RadioCrashSilentRestart.IsChecked = true; else RadioCrashNoAction.IsChecked = true; // 如果当前不是黑板模式,则切换到黑板模式 if (currentMode == 0) { // 延迟执行,确保UI已完全加载 Dispatcher.BeginInvoke(new Action(() => { // 重新加载自定义背景颜色 LoadCustomBackgroundColor(); // 模拟点击切换按钮进入黑板模式 if (GridTransparencyFakeBackground.Background != Brushes.Transparent) { BtnSwitch_Click(BtnSwitch, null); } // 确保背景颜色正确设置为黑板颜色 CheckColorTheme(true); }), DispatcherPriority.Loaded); } // 初始化插件系统 InitializePluginSystem(); // 确保开关和设置同步 ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode; ApplyNoFocusMode(); ToggleSwitchAlwaysOnTop.IsOn = Settings.Advanced.IsAlwaysOnTop; ApplyAlwaysOnTop(); // 初始化UIElement选择系统 // 初始化剪贴板监控 InitializeClipboardMonitoring(); // 初始化全局快捷键管理器 InitializeGlobalHotkeyManager(); // 初始化墨迹渐隐管理器 InitializeInkFadeManager(); // 处理命令行参数中的文件路径 HandleCommandLineFileOpen(); // 初始化文件关联状态显示 InitializeFileAssociationStatus(); // 检查模式设置并应用 CheckMainWindowVisibility(); } private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) { if (!Settings.Advanced.IsEnableResolutionChangeDetection) return; ShowNotification($"检测到显示器信息变化,变为{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height})"); new Thread(() => { var isFloatingBarOutsideScreen = false; var isInPPTPresentationMode = false; Dispatcher.Invoke(() => { isFloatingBarOutsideScreen = IsOutsideOfScreenHelper.IsOutsideOfScreen(ViewboxFloatingBar); isInPPTPresentationMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible; }); if (isFloatingBarOutsideScreen) dpiChangedDelayAction.DebounceAction(3000, null, () => { if (!isFloatingBarFolded) { if (isInPPTPresentationMode) ViewboxFloatingBarMarginAnimation(60); else ViewboxFloatingBarMarginAnimation(100, true); } }); }).Start(); } public DelayAction dpiChangedDelayAction = new DelayAction(); private void MainWindow_OnDpiChanged(object sender, DpiChangedEventArgs e) { if (e.OldDpi.DpiScaleX != e.NewDpi.DpiScaleX && e.OldDpi.DpiScaleY != e.NewDpi.DpiScaleY && Settings.Advanced.IsEnableDPIChangeDetection) { ShowNotification($"系统DPI发生变化,从 {e.OldDpi.DpiScaleX}x{e.OldDpi.DpiScaleY} 变化为 {e.NewDpi.DpiScaleX}x{e.NewDpi.DpiScaleY}"); new Thread(() => { var isFloatingBarOutsideScreen = false; var isInPPTPresentationMode = false; Dispatcher.Invoke(() => { isFloatingBarOutsideScreen = IsOutsideOfScreenHelper.IsOutsideOfScreen(ViewboxFloatingBar); isInPPTPresentationMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible; }); if (isFloatingBarOutsideScreen) dpiChangedDelayAction.DebounceAction(3000, null, () => { if (!isFloatingBarFolded) { if (isInPPTPresentationMode) ViewboxFloatingBarMarginAnimation(60); else ViewboxFloatingBarMarginAnimation(100, true); } }); }).Start(); } } private void Window_Closing(object sender, CancelEventArgs e) { LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event); if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp) { // 第一个确认对话框 var result1 = MessageBox.Show("是否继续关闭 InkCanvasForClass,这将丢失当前未保存的墨迹。", "InkCanvasForClass", MessageBoxButton.OKCancel, MessageBoxImage.Warning); if (result1 == MessageBoxResult.Cancel) { e.Cancel = true; LogHelper.WriteLogToFile("Ink Canvas closing cancelled at first confirmation", LogHelper.LogType.Event); return; } // 第二个确认对话框 var result2 = MessageBox.Show("真的狠心关闭 InkCanvasForClass吗?", "InkCanvasForClass", MessageBoxButton.OKCancel, MessageBoxImage.Error); if (result2 == MessageBoxResult.Cancel) { e.Cancel = true; LogHelper.WriteLogToFile("Ink Canvas closing cancelled at second confirmation", LogHelper.LogType.Event); return; } // 第三个最终确认对话框 var result3 = MessageBox.Show("最后确认:确定要关闭 InkCanvasForClass 吗?", "InkCanvasForClass", MessageBoxButton.OKCancel, MessageBoxImage.Question); if (result3 == MessageBoxResult.Cancel) { e.Cancel = true; LogHelper.WriteLogToFile("Ink Canvas closing cancelled at final confirmation", LogHelper.LogType.Event); return; } // 所有确认都通过,允许关闭 e.Cancel = false; LogHelper.WriteLogToFile("Ink Canvas closing confirmed by user", LogHelper.LogType.Event); } if (e.Cancel) LogHelper.WriteLogToFile("Ink Canvas closing cancelled", LogHelper.LogType.Event); } [DllImport("user32.dll", SetLastError = true)] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); private void MainWindow_OnSizeChanged(object sender, SizeChangedEventArgs e) { if (Settings.Advanced.IsEnableForceFullScreen) { if (isLoaded) ShowNotification( $"检测到窗口大小变化,已自动恢复到全屏:{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}(缩放比例为{Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth}x{Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight})"); WindowState = WindowState.Maximized; MoveWindow(new WindowInteropHelper(this).Handle, 0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, true); } } private void Window_Closed(object sender, EventArgs e) { SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged; // 释放PPT管理器资源 DisposePPTManagers(); // 清理剪贴板监控 CleanupClipboardMonitoring(); ClipboardNotification.Stop(); // 清理全局快捷键管理器 if (_globalHotkeyManager != null) { _globalHotkeyManager.Dispose(); _globalHotkeyManager = null; } // 清理墨迹渐隐管理器 if (_inkFadeManager != null) { _inkFadeManager.ClearAllFadingStrokes(); _inkFadeManager = null; } // 停止置顶维护定时器 StopTopmostMaintenance(); // 从Z-Order管理器中移除主窗口 WindowZOrderManager.UnregisterWindow(this); LogHelper.WriteLogToFile("Ink Canvas closed", LogHelper.LogType.Event); // 检查是否有待安装的更新 CheckPendingUpdates(); } private void CheckPendingUpdates() { try { // 如果有可用的更新版本且启用了自动更新 if (AvailableLatestVersion != null && Settings.Startup.IsAutoUpdate) { // 检查更新文件是否已下载 string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); string statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{AvailableLatestVersion}Status.txt"); if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true") { LogHelper.WriteLogToFile($"AutoUpdate | Installing pending update v{AvailableLatestVersion} on application close"); // 设置为用户主动退出,避免被看门狗判定为崩溃 App.IsAppExitByUser = true; // 创建批处理脚本并启动,软件关闭后会执行更新操作 AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true); } } } catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | Error checking pending updates: {ex.Message}", LogHelper.LogType.Error); } } // 辅助方法:使用多线路组下载更新 private async Task DownloadUpdateWithFallback(string version, AutoUpdateHelper.UpdateLineGroup primaryGroup, UpdateChannel channel) { try { // 如果主要线路组可用,直接使用 if (primaryGroup != null) { LogHelper.WriteLogToFile($"AutoUpdate | 使用主要线路组下载: {primaryGroup.GroupName}"); return await AutoUpdateHelper.DownloadSetupFile(version, primaryGroup); } // 如果主要线路组不可用,获取所有可用线路组 LogHelper.WriteLogToFile("AutoUpdate | 主要线路组不可用,获取所有可用线路组"); var availableGroups = await AutoUpdateHelper.GetAvailableLineGroupsOrdered(channel); if (availableGroups.Count == 0) { LogHelper.WriteLogToFile("AutoUpdate | 没有可用的线路组", LogHelper.LogType.Error); return false; } LogHelper.WriteLogToFile($"AutoUpdate | 使用 {availableGroups.Count} 个可用线路组进行下载"); return await AutoUpdateHelper.DownloadSetupFileWithFallback(version, availableGroups); } catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error); return false; } } private async void AutoUpdate() { // 清除之前的更新状态,确保使用新通道重新检查 AvailableLatestVersion = null; AvailableLatestLineGroup = null; // 使用当前选择的更新通道检查更新 var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel); AvailableLatestVersion = remoteVersion; AvailableLatestLineGroup = lineGroup; // 声明下载状态变量,用于整个方法 bool isDownloadSuccessful = false; if (AvailableLatestVersion != null) { // 检测到新版本 LogHelper.WriteLogToFile($"AutoUpdate | New version available: {AvailableLatestVersion}"); // 检查是否是用户选择跳过的版本 if (!string.IsNullOrEmpty(Settings.Startup.SkippedVersion) && Settings.Startup.SkippedVersion == AvailableLatestVersion) { LogHelper.WriteLogToFile($"AutoUpdate | Version {AvailableLatestVersion} was marked to be skipped by the user"); return; // 跳过此版本,不执行更新操作 } // 如果检测到的版本与跳过的版本不同,则清除跳过版本记录 // 这确保用户只能跳过当前最新版本,而不是永久跳过所有更新 if (!string.IsNullOrEmpty(Settings.Startup.SkippedVersion) && Settings.Startup.SkippedVersion != AvailableLatestVersion) { LogHelper.WriteLogToFile($"AutoUpdate | Detected new version {AvailableLatestVersion} different from skipped version {Settings.Startup.SkippedVersion}, clearing skip record"); Settings.Startup.SkippedVersion = ""; SaveSettingsToFile(); } // 获取当前版本 string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 如果启用了静默更新,则自动下载更新而不显示提示 if (Settings.Startup.IsAutoUpdateWithSilence) { LogHelper.WriteLogToFile("AutoUpdate | Silent update enabled, downloading update automatically without notification"); // 静默下载更新,使用多线路组下载功能 isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); if (isDownloadSuccessful) { LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will install when conditions are met"); // 启动检查定时器,定期检查是否可以安装 timerCheckAutoUpdateWithSilence.Start(); } else { LogHelper.WriteLogToFile("AutoUpdate | Silent update download failed", LogHelper.LogType.Error); } return; } // 如果没有启用静默更新,则显示常规更新窗口 string releaseDate = DateTime.Now.ToString("yyyy年MM月dd日"); // 从服务器获取更新日志 string releaseNotes = await AutoUpdateHelper.GetUpdateLog(Settings.Startup.UpdateChannel); // 如果获取失败,使用默认文本 if (string.IsNullOrEmpty(releaseNotes)) { releaseNotes = $@"# InkCanvasForClass v{AvailableLatestVersion}更新 无法获取更新日志,但新版本已准备就绪。"; } // 创建并显示更新窗口 HasNewUpdateWindow updateWindow = new HasNewUpdateWindow(currentVersion, AvailableLatestVersion, releaseDate, releaseNotes); updateWindow.Owner = this; bool? dialogResult = updateWindow.ShowDialog(); // 如果窗口被关闭但没有点击按钮,则不执行任何操作 if (dialogResult != true) { LogHelper.WriteLogToFile("AutoUpdate | Update dialog closed without selection"); return; } // 不再从更新窗口获取自动更新设置 // 根据用户选择处理更新 switch (updateWindow.Result) { case HasNewUpdateWindow.UpdateResult.UpdateNow: // 立即更新:显示下载进度,下载完成后立即安装 LogHelper.WriteLogToFile("AutoUpdate | User chose to update now"); // 显示下载进度提示 MessageBox.Show("开始下载更新,请稍候...", "正在更新", MessageBoxButton.OK, MessageBoxImage.Information); // 下载更新文件,使用多线路组下载功能 isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); if (isDownloadSuccessful) { // 下载成功,提示用户准备安装 MessageBoxResult result = MessageBox.Show("更新已下载完成,点击确定后将关闭软件并安装新版本!", "安装更新", MessageBoxButton.OKCancel, MessageBoxImage.Information); // 只有当用户点击确定按钮后才关闭软件 if (result == MessageBoxResult.OK) { // 设置为用户主动退出,避免被看门狗判定为崩溃 App.IsAppExitByUser = true; // 准备批处理脚本 AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true); // 修改为静默模式,避免重复启动进程 // 关闭软件,让安装程序接管 Application.Current.Shutdown(); } else { LogHelper.WriteLogToFile("AutoUpdate | User cancelled update installation"); } } else { // 下载失败 MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error); } break; case HasNewUpdateWindow.UpdateResult.UpdateLater: // 稍后更新:静默下载,在软件关闭时自动安装 LogHelper.WriteLogToFile("AutoUpdate | User chose to update later"); // 不管设置如何,都进行下载,使用多线路组下载功能 isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); if (isDownloadSuccessful) { LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will install when application closes"); // 设置标志,在应用程序关闭时安装 Settings.Startup.IsAutoUpdate = true; Settings.Startup.IsAutoUpdateWithSilence = true; // 启动检查定时器 timerCheckAutoUpdateWithSilence.Start(); // 通知用户 MessageBox.Show("更新已下载完成,将在软件关闭时自动安装。", "更新已准备就绪", MessageBoxButton.OK, MessageBoxImage.Information); } else { LogHelper.WriteLogToFile("AutoUpdate | Update download failed", LogHelper.LogType.Error); MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error); } break; case HasNewUpdateWindow.UpdateResult.SkipVersion: // 跳过该版本:记录到设置中 LogHelper.WriteLogToFile($"AutoUpdate | User chose to skip version {AvailableLatestVersion}"); // 记录要跳过的版本号 Settings.Startup.SkippedVersion = AvailableLatestVersion; // 保存设置到文件 SaveSettingsToFile(); // 通知用户 MessageBox.Show($"已设置跳过版本 {AvailableLatestVersion},在下次发布新版本之前不会再提示更新。", "已跳过此版本", MessageBoxButton.OK, MessageBoxImage.Information); break; } } else { AutoUpdateHelper.DeleteUpdatesFolder(); } } // 新增:崩溃后操作设置按钮事件 private void RadioCrashAction_Checked(object sender, RoutedEventArgs e) { if (RadioCrashSilentRestart != null && RadioCrashSilentRestart.IsChecked == true) { App.CrashAction = App.CrashActionType.SilentRestart; Settings.Startup.CrashAction = 0; } else if (RadioCrashNoAction != null && RadioCrashNoAction.IsChecked == true) { App.CrashAction = App.CrashActionType.NoAction; Settings.Startup.CrashAction = 1; } SaveSettingsToFile(); // 强制同步全局变量,防止后台逻辑未及时感知 App.SyncCrashActionFromSettings(); } // 添加一个辅助方法,根据当前编辑模式设置光标 public void SetCursorBasedOnEditingMode(InkCanvas canvas) { // 套索选择模式下光标始终显示,无论用户设置如何 if (canvas.EditingMode == InkCanvasEditingMode.Select) { canvas.UseCustomCursor = true; canvas.ForceCursor = true; canvas.Cursor = Cursors.Cross; System.Windows.Forms.Cursor.Show(); return; } // 其他模式按照用户设置处理 if (Settings.Canvas.IsShowCursor) { canvas.UseCustomCursor = true; canvas.ForceCursor = true; // 根据编辑模式设置不同的光标 if (canvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { canvas.Cursor = Cursors.Cross; } else if (canvas.EditingMode == InkCanvasEditingMode.Ink) { var sri = Application.GetResourceStream(new Uri("Resources/Cursors/Pen.cur", UriKind.Relative)); if (sri != null) canvas.Cursor = new Cursor(sri.Stream); } // 确保光标可见,无论是鼠标、触控还是手写笔 System.Windows.Forms.Cursor.Show(); // 确保手写笔模式下也能显示光标 if (Tablet.TabletDevices.Count > 0) { foreach (TabletDevice device in Tablet.TabletDevices) { if (device.Type == TabletDeviceType.Stylus) { // 手写笔设备存在,强制显示光标 System.Windows.Forms.Cursor.Show(); break; } } } } else { canvas.UseCustomCursor = false; canvas.ForceCursor = false; System.Windows.Forms.Cursor.Show(); } } // 鼠标输入 private void inkCanvas_PreviewMouseDown(object sender, MouseButtonEventArgs e) { // 使用辅助方法设置光标 SetCursorBasedOnEditingMode(sender as InkCanvas); // 检查是否点击了空白区域或其他非图片元素 var hitTest = e.OriginalSource; if (!(hitTest is Image) && !(hitTest is MediaElement)) { // 如果当前有选中的元素,取消选中状态 if (currentSelectedElement != null) { // 取消选中元素 UnselectElement(currentSelectedElement); currentSelectedElement = null; // 重置为选择模式,确保用户可以继续选择其他元素 SetCurrentToolMode(InkCanvasEditingMode.Select); // 更新模式缓存 UpdateCurrentToolMode("select"); // 刷新浮动栏高光显示 SetFloatingBarHighlightPosition("select"); } } } // 手写笔输入 private void inkCanvas_StylusDown(object sender, StylusDownEventArgs e) { // 使用辅助方法设置光标 SetCursorBasedOnEditingMode(sender as InkCanvas); } // 触摸结束,恢复光标 #endregion Definations and Loading #region Navigation Sidebar Methods // 侧边栏导航按钮事件处理 private void NavStartup_Click(object sender, RoutedEventArgs e) { // 切换到启动设置页面 ShowSettingsSection("startup"); } private void NavCanvas_Click(object sender, RoutedEventArgs e) { // 切换到画布设置页面 ShowSettingsSection("canvas"); } private void NavGesture_Click(object sender, RoutedEventArgs e) { // 切换到手势设置页面 ShowSettingsSection("gesture"); } private void NavInkRecognition_Click(object sender, RoutedEventArgs e) { // 切换到墨迹识别设置页面 ShowSettingsSection("inkrecognition"); } private void NavCrashAction_Click(object sender, RoutedEventArgs e) { // 切换到崩溃处理设置页面 ShowSettingsSection("crashaction"); } private void NavPPT_Click(object sender, RoutedEventArgs e) { // 切换到PPT设置页面 ShowSettingsSection("ppt"); } private void NavAdvanced_Click(object sender, RoutedEventArgs e) { // 切换到高级设置页面 ShowSettingsSection("advanced"); } private void NavAutomation_Click(object sender, RoutedEventArgs e) { // 切换到自动化设置页面 ShowSettingsSection("automation"); } private void NavRandomWindow_Click(object sender, RoutedEventArgs e) { // 切换到随机窗口设置页面 ShowSettingsSection("randomwindow"); } private void NavAbout_Click(object sender, RoutedEventArgs e) { // 切换到关于页面 ShowSettingsSection("about"); // 刷新设备信息 RefreshDeviceInfo(); } // 新增:个性化设置 private void NavTheme_Click(object sender, RoutedEventArgs e) { // 切换到个性化设置页面 ShowSettingsSection("theme"); } // 新增:快捷键设置 private void NavShortcuts_Click(object sender, RoutedEventArgs e) { OpenHotkeySettingsWindow(); } private void BtnCloseSettings_Click(object sender, RoutedEventArgs e) { // 关闭设置面板 BorderSettings.Visibility = Visibility.Collapsed; // 设置蒙版为不可点击,并清除背景 BorderSettingsMask.IsHitTestVisible = false; BorderSettingsMask.Background = null; // 确保清除蒙层背景 } /// /// 刷新设备信息按钮点击事件 /// private void RefreshDeviceInfo_Click(object sender, RoutedEventArgs e) { RefreshDeviceInfo(); } /// /// 刷新设备信息显示 /// private void RefreshDeviceInfo() { try { // 获取设备ID string deviceId = DeviceIdentifier.GetDeviceId(); DeviceIdTextBlock.Text = deviceId; // 获取使用频率 var usageFrequency = DeviceIdentifier.GetUsageFrequency(); string frequencyText; switch (usageFrequency) { case DeviceIdentifier.UsageFrequency.High: frequencyText = "高频用户"; break; case DeviceIdentifier.UsageFrequency.Medium: frequencyText = "中频用户"; break; case DeviceIdentifier.UsageFrequency.Low: frequencyText = "低频用户"; break; default: frequencyText = "未知"; break; } UsageFrequencyTextBlock.Text = frequencyText; // 获取更新优先级 var updatePriority = DeviceIdentifier.GetUpdatePriority(); string priorityText; switch (updatePriority) { case DeviceIdentifier.UpdatePriority.High: priorityText = "高优先级(优先推送更新)"; break; case DeviceIdentifier.UpdatePriority.Medium: priorityText = "中优先级(正常推送更新)"; break; case DeviceIdentifier.UpdatePriority.Low: priorityText = "低优先级(延迟推送更新)"; break; default: priorityText = "未知"; break; } UpdatePriorityTextBlock.Text = priorityText; // 获取使用统计(秒级精度) var (launchCount, totalSeconds, avgSessionSeconds, _) = DeviceIdentifier.GetUsageStats(); LaunchCountTextBlock.Text = launchCount.ToString(); // 使用新的格式化方法显示秒级精度的使用时长 string totalUsageText = DeviceIdentifier.FormatDuration(totalSeconds); TotalUsageTextBlock.Text = totalUsageText; LogHelper.WriteLogToFile($"MainWindow | 设备信息已刷新 - ID: {deviceId}, 频率: {frequencyText}, 优先级: {priorityText}"); } catch (Exception ex) { LogHelper.WriteLogToFile($"MainWindow | 刷新设备信息失败: {ex.Message}", LogHelper.LogType.Error); // 显示错误信息 DeviceIdTextBlock.Text = "获取失败"; UsageFrequencyTextBlock.Text = "获取失败"; UpdatePriorityTextBlock.Text = "获取失败"; LaunchCountTextBlock.Text = "获取失败"; TotalUsageTextBlock.Text = "获取失败"; } } // 新增:折叠侧边栏 private void CollapseNavSidebar_Click(object sender, RoutedEventArgs e) { // 折叠/展开侧边栏 var columnDefinitions = ((Grid)BorderSettings.Child).ColumnDefinitions; if (columnDefinitions[0].Width.Value == 50) { // 折叠侧边栏 columnDefinitions[0].Width = new GridLength(0); } else { // 展开侧边栏 columnDefinitions[0].Width = new GridLength(50); } } // 新增:显示侧边栏 private void ShowNavSidebar_Click(object sender, RoutedEventArgs e) { // 确保侧边栏展开 var columnDefinitions = ((Grid)BorderSettings.Child).ColumnDefinitions; columnDefinitions[0].Width = new GridLength(50); } // 辅助方法:显示指定的设置部分 private async void ShowSettingsSection(string sectionTag) { // 显示设置面板 BorderSettings.Visibility = Visibility.Visible; // 设置蒙版为可点击,并添加半透明背景 BorderSettingsMask.IsHitTestVisible = true; BorderSettingsMask.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); // 获取SettingsPanelScrollViewer中的所有GroupBox var stackPanel = SettingsPanelScrollViewer.Content as StackPanel; if (stackPanel == null) return; // 确保所有GroupBox都是可见的 foreach (var child in stackPanel.Children) { if (child is GroupBox groupBox) { groupBox.Visibility = Visibility.Visible; } } // 确保UI完全更新 await Dispatcher.InvokeAsync(() => { }, DispatcherPriority.Render); // 根据传入的sectionTag滚动到相应的设置部分 GroupBox targetGroupBox = null; switch (sectionTag.ToLower()) { case "startup": targetGroupBox = FindGroupBoxByHeader(stackPanel, "启动"); break; case "canvas": targetGroupBox = FindGroupBoxByHeader(stackPanel, "画板和墨迹"); break; case "gesture": targetGroupBox = FindGroupBoxByHeader(stackPanel, "手势"); break; case "inkrecognition": targetGroupBox = GroupBoxInkRecognition; break; case "crashaction": targetGroupBox = FindGroupBoxByHeader(stackPanel, "崩溃后操作"); break; case "ppt": targetGroupBox = FindGroupBoxByHeader(stackPanel, "PPT联动"); break; case "advanced": targetGroupBox = FindGroupBoxByHeader(stackPanel, "高级设置"); break; case "automation": targetGroupBox = FindGroupBoxByHeader(stackPanel, "自动化"); break; case "randomwindow": targetGroupBox = GroupBoxRandWindow; break; case "theme": targetGroupBox = GroupBoxAppearanceNewUI; break; case "shortcuts": // 快捷键设置部分可能尚未实现 targetGroupBox = FindGroupBoxByHeader(stackPanel, "快捷键"); break; case "about": targetGroupBox = FindGroupBoxByHeader(stackPanel, "关于"); break; case "plugins": targetGroupBox = GroupBoxPlugins; break; default: // 默认滚动到顶部 SettingsPanelScrollViewer.ScrollToTop(); return; } // 如果找到目标GroupBox,则滚动到它的位置 if (targetGroupBox != null) { // 使用动画平滑滚动到目标位置 ScrollToElement(targetGroupBox); // 高亮显示当前选中的导航项 UpdateNavigationButtonState(sectionTag); } else { // 如果没有找到目标GroupBox,则滚动到顶部 SettingsPanelScrollViewer.ScrollToTop(); } } // 根据Header文本查找GroupBox private GroupBox FindGroupBoxByHeader(StackPanel parent, string headerText) { foreach (var child in parent.Children) { if (child is GroupBox groupBox) { // 查找GroupBox的Header if (groupBox.Header is TextBlock headerTextBlock && headerTextBlock.Text != null && headerTextBlock.Text.Contains(headerText)) { return groupBox; } } } return null; } // 平滑滚动到指定元素 private async void ScrollToElement(FrameworkElement element) { if (element == null || SettingsPanelScrollViewer == null) return; try { // 暂时禁用滚动事件处理 SettingsPanelScrollViewer.ScrollChanged -= SettingsPanelScrollViewer_ScrollChanged; // 记录当前滚动位置 double originalOffset = SettingsPanelScrollViewer.VerticalOffset; // 将ScrollViewer内部的位置信息重置到顶部(不会触发视觉更新) SettingsPanelScrollViewer.ScrollToHome(); // 使用Dispatcher进行延迟处理,确保布局更新 await Dispatcher.InvokeAsync(() => { try { // 强制更新布局 SettingsPanelScrollViewer.UpdateLayout(); // 获取元素相对于顶部的准确位置 Point elementPosition = element.TransformToAncestor(SettingsPanelScrollViewer).Transform(new Point(0, 0)); // 计算目标位置,减去一些偏移,使元素不会贴在顶部 double targetPosition = elementPosition.Y - 20; // 确保目标位置不小于0 targetPosition = Math.Max(0, targetPosition); // 直接设置滚动位置,不使用动画 SettingsPanelScrollViewer.ScrollToVerticalOffset(targetPosition); } catch (Exception) { // 如果出现异常,恢复到原来的滚动位置 SettingsPanelScrollViewer.ScrollToVerticalOffset(originalOffset); } finally { // 重新启用滚动事件处理 SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; } }, DispatcherPriority.Render); } catch (Exception) { // 确保在异常情况下也重新启用滚动事件处理 SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; } } // 滚动条变化事件处理 private void SettingsPanelScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { // 可以在这里添加滚动事件的处理逻辑,如果需要的话 } // 更新导航按钮状态 private void UpdateNavigationButtonState(string activeTag) { // 清除所有导航按钮的Tag属性 ClearAllNavButtonTags(); // 设置当前活动按钮的Tag属性 switch (activeTag.ToLower()) { case "startup": SetNavButtonTag("startup"); break; case "canvas": SetNavButtonTag("canvas"); break; case "gesture": SetNavButtonTag("gesture"); break; case "inkrecognition": SetNavButtonTag("inkrecognition"); break; case "crashaction": SetNavButtonTag("crashaction"); break; case "ppt": SetNavButtonTag("ppt"); break; case "advanced": SetNavButtonTag("advanced"); break; case "automation": SetNavButtonTag("automation"); break; case "randomwindow": SetNavButtonTag("randomwindow"); break; case "theme": SetNavButtonTag("theme"); break; case "shortcuts": SetNavButtonTag("shortcuts"); break; case "about": SetNavButtonTag("about"); break; case "plugins": SetNavButtonTag("plugins"); break; } } // 清除所有导航按钮的Tag属性 private void ClearAllNavButtonTags() { var grid = BorderSettings.Child as Grid; if (grid == null) return; var navSidebar = grid.Children[0] as Border; if (navSidebar == null) return; var navGrid = navSidebar.Child as Grid; if (navGrid == null) return; var scrollViewer = navGrid.Children[1] as ScrollViewer; if (scrollViewer == null) return; var stackPanel = scrollViewer.Content as StackPanel; if (stackPanel == null) return; foreach (var child in stackPanel.Children) { if (child is Button button) { button.Tag = null; } } } // 设置导航按钮的Tag属性 private void SetNavButtonTag(string tag) { var grid = BorderSettings.Child as Grid; if (grid == null) return; var navSidebar = grid.Children[0] as Border; if (navSidebar == null) return; var navGrid = navSidebar.Child as Grid; if (navGrid == null) return; var scrollViewer = navGrid.Children[1] as ScrollViewer; if (scrollViewer == null) return; var stackPanel = scrollViewer.Content as StackPanel; if (stackPanel == null) return; foreach (var child in stackPanel.Children) { if (child is Button button) { // 检查按钮的ToolTip属性,根据tag设置对应的按钮 string buttonTag = button.Tag as string; // 如果按钮的Tag与要设置的tag匹配,则设置Tag if (buttonTag != null && buttonTag.ToLower() == tag.ToLower()) { button.Tag = tag; return; } } } } // 根据Header文本查找并显示GroupBox private void ShowGroupBoxByHeader(StackPanel parent, string headerText) { foreach (var child in parent.Children) { if (child is GroupBox groupBox) { // 查找GroupBox的Header if (groupBox.Header is TextBlock headerTextBlock && headerTextBlock.Text != null && headerTextBlock.Text.Contains(headerText)) { groupBox.Visibility = Visibility.Visible; return; } } } } #endregion Navigation Sidebar Methods #region 插件??? // 添加插件系统初始化方法 private void InitializePluginSystem() { try { // 初始化插件管理器 PluginManager.Instance.Initialize(); LogHelper.WriteLogToFile("插件系统已初始化"); } catch (Exception ex) { LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error); } } // 添加插件管理导航点击事件处理 private void NavPlugins_Click(object sender, RoutedEventArgs e) { ShowSettingsSection("plugins"); } // 添加打开插件管理器按钮点击事件 private void BtnOpenPluginManager_Click(object sender, RoutedEventArgs e) { try { // 暂时隐藏设置面板 BorderSettings.Visibility = Visibility.Hidden; BorderSettingsMask.Visibility = Visibility.Hidden; // 创建并显示插件设置窗口 PluginSettingsWindow pluginSettingsWindow = new PluginSettingsWindow(); // 设置窗口关闭事件,用于在插件管理窗口关闭后恢复设置面板 pluginSettingsWindow.Closed += (s, args) => { // 恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; BorderSettingsMask.Visibility = Visibility.Visible; }; // 显示插件设置窗口 pluginSettingsWindow.ShowDialog(); } catch (Exception ex) { // 确保在发生错误时也恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; BorderSettingsMask.Visibility = Visibility.Visible; LogHelper.WriteLogToFile($"打开插件管理器时出错: {ex.Message}", LogHelper.LogType.Error); MessageBox.Show($"打开插件管理器时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion 插件??? #region 新设置窗口 // 添加打开新设置窗口按钮点击事件 private void BtnOpenNewSettings_Click(object sender, RoutedEventArgs e) { if (isOpeningOrHidingSettingsPane) return; HideSubPanels(); { var settingsWindow = new SettingsWindow(); settingsWindow.Owner = this; settingsWindow.ShowDialog(); } } #endregion 新设置窗口 // 在MainWindow类中添加: private void ApplyCurrentEraserShape() { double k = 1; switch (Settings.Canvas.EraserSize) { case 0: k = Settings.Canvas.EraserShapeType == 0 ? 0.5 : 0.7; break; case 1: k = Settings.Canvas.EraserShapeType == 0 ? 0.8 : 0.9; break; case 3: k = Settings.Canvas.EraserShapeType == 0 ? 1.25 : 1.2; break; case 4: k = Settings.Canvas.EraserShapeType == 0 ? 1.5 : 1.3; break; } if (Settings.Canvas.EraserShapeType == 0) { inkCanvas.EraserShape = new EllipseStylusShape(k * 90, k * 90); } else if (Settings.Canvas.EraserShapeType == 1) { inkCanvas.EraserShape = new RectangleStylusShape(k * 90 * 0.6, k * 90); } } // 显示指定页 private void ShowPage(int index) { if (index < 0 || index >= whiteboardPages.Count) return; // 只切换可见性 for (int i = 0; i < whiteboardPages.Count; i++) { whiteboardPages[i].Visibility = (i == index) ? Visibility.Visible : Visibility.Collapsed; } currentCanvas = whiteboardPages[index]; currentPageIndex = index; } // 新建页面 private void AddNewPage() { var newCanvas = new System.Windows.Controls.Canvas(); whiteboardPages.Add(newCanvas); InkCanvasGridForInkReplay.Children.Add(newCanvas); ShowPage(whiteboardPages.Count - 1); } // 删除当前页面 private void DeleteCurrentPage() { if (whiteboardPages.Count <= 1) return; InkCanvasGridForInkReplay.Children.Remove(currentCanvas); whiteboardPages.RemoveAt(currentPageIndex); if (currentPageIndex >= whiteboardPages.Count) currentPageIndex = whiteboardPages.Count - 1; ShowPage(currentPageIndex); } // 快速面板退出PPT放映按钮事件 private void ExitPPTSlideShow_MouseUp(object sender, MouseButtonEventArgs e) { // 直接调用PPT放映结束按钮的逻辑 BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null); } private void HistoryRollbackButton_Click(object sender, RoutedEventArgs e) { // 收起设置面板(与插件面板一致) BorderSettings.Visibility = Visibility.Hidden; BorderSettingsMask.Visibility = Visibility.Hidden; var win = new HistoryRollbackWindow(Settings.Startup.UpdateChannel); win.ShowDialog(); // 可选:回滚窗口关闭后恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; BorderSettingsMask.Visibility = Visibility.Visible; } [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] private static extern bool BringWindowToTop(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll")] private static extern uint GetCurrentProcessId(); private const int GWL_EXSTYLE = -20; private const int WS_EX_NOACTIVATE = 0x08000000; private const int WS_EX_TOPMOST = 0x00000008; private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOSIZE = 0x0001; private const uint SWP_NOACTIVATE = 0x0010; private const uint SWP_SHOWWINDOW = 0x0040; private const uint SWP_NOOWNERZORDER = 0x0200; // 添加定时器来维护置顶状态 private DispatcherTimer topmostMaintenanceTimer; private bool isTopmostMaintenanceEnabled; private void ApplyNoFocusMode() { var hwnd = new WindowInteropHelper(this).Handle; int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); if (Settings.Advanced.IsNoFocusMode) { SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE); } else { SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_NOACTIVATE); } } private void ApplyAlwaysOnTop() { try { var hwnd = new WindowInteropHelper(this).Handle; if (Settings.Advanced.IsAlwaysOnTop) { // 先设置WPF的Topmost属性 Topmost = true; // 使用更强的Win32 API调用来确保置顶 // 1. 设置窗口样式为置顶 int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); // 2. 使用SetWindowPos确保窗口在最顶层 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); // 3. 如果启用了无焦点模式,需要特殊处理 if (Settings.Advanced.IsNoFocusMode) { // 启动置顶维护定时器 StartTopmostMaintenance(); } else { // 停止置顶维护定时器 StopTopmostMaintenance(); } } else { // 取消置顶时 // 1. 先使用Win32 API取消置顶 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); // 2. 移除置顶窗口样式 int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TOPMOST); // 3. 停止置顶维护定时器 StopTopmostMaintenance(); // 注意:这里不直接设置Topmost,让其他代码根据模式决定 // 添加调试日志 LogHelper.WriteLogToFile("应用窗口置顶: 取消置顶", LogHelper.LogType.Trace); } } catch (Exception ex) { LogHelper.WriteLogToFile($"应用窗口置顶失败: {ex.Message}", LogHelper.LogType.Error); } } /// /// 启动置顶维护定时器 /// private void StartTopmostMaintenance() { if (isTopmostMaintenanceEnabled) return; if (topmostMaintenanceTimer == null) { topmostMaintenanceTimer = new DispatcherTimer(); topmostMaintenanceTimer.Interval = TimeSpan.FromMilliseconds(500); // 每500ms检查一次 topmostMaintenanceTimer.Tick += TopmostMaintenanceTimer_Tick; } topmostMaintenanceTimer.Start(); isTopmostMaintenanceEnabled = true; LogHelper.WriteLogToFile("启动置顶维护定时器", LogHelper.LogType.Trace); } /// /// 停止置顶维护定时器 /// private void StopTopmostMaintenance() { if (topmostMaintenanceTimer != null && isTopmostMaintenanceEnabled) { topmostMaintenanceTimer.Stop(); isTopmostMaintenanceEnabled = false; LogHelper.WriteLogToFile("停止置顶维护定时器", LogHelper.LogType.Trace); } } /// /// 置顶维护定时器事件 /// private void TopmostMaintenanceTimer_Tick(object sender, EventArgs e) { try { if (!Settings.Advanced.IsAlwaysOnTop || !Settings.Advanced.IsNoFocusMode) { StopTopmostMaintenance(); return; } var hwnd = new WindowInteropHelper(this).Handle; if (hwnd == IntPtr.Zero) return; // 检查窗口是否仍然可见且不是最小化状态 if (!IsWindow(hwnd) || !IsWindowVisible(hwnd) || IsIconic(hwnd)) { return; } // 检查是否有子窗口在前景 var foregroundWindow = GetForegroundWindow(); if (foregroundWindow != hwnd) { // 检查前景窗口是否是当前应用程序的子窗口 var foregroundWindowProcessId = GetWindowThreadProcessId(foregroundWindow, out uint processId); var currentProcessId = GetCurrentProcessId(); if (processId == currentProcessId) { // 如果有子窗口在前景,暂停置顶维护 return; } // 如果窗口不在最顶层且没有子窗口,重新设置置顶 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); // 确保窗口样式正确 int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); if ((exStyle & WS_EX_TOPMOST) == 0) { SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); } } } catch (Exception ex) { LogHelper.WriteLogToFile($"置顶维护定时器出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 根据窗口置顶设置和当前模式设置窗口的Topmost属性 /// /// 当前模式是否需要窗口置顶 public void SetTopmostBasedOnSettings(bool shouldBeTopmost) { if (Settings.Advanced.IsAlwaysOnTop) { // 如果启用了窗口置顶设置,则始终置顶 Topmost = true; ApplyAlwaysOnTop(); } else { // 如果未启用窗口置顶设置,则根据当前模式决定 Topmost = shouldBeTopmost; if (!shouldBeTopmost) { ApplyAlwaysOnTop(); // 确保取消置顶 } } } private void ToggleSwitchNoFocusMode_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; var toggle = sender as ToggleSwitch; Settings.Advanced.IsNoFocusMode = toggle != null && toggle.IsOn; SaveSettingsToFile(); ApplyNoFocusMode(); // 如果启用了窗口置顶,需要重新应用置顶设置以处理无焦点模式的变化 if (Settings.Advanced.IsAlwaysOnTop) { ApplyAlwaysOnTop(); } } private void ToggleSwitchAlwaysOnTop_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; var toggle = sender as ToggleSwitch; Settings.Advanced.IsAlwaysOnTop = toggle != null && toggle.IsOn; SaveSettingsToFile(); ApplyAlwaysOnTop(); } private void Window_Activated(object sender, EventArgs e) { // 窗口激活时,如果启用了置顶功能,重新应用置顶设置 if (Settings.Advanced.IsAlwaysOnTop) { // 使用Dispatcher.BeginInvoke确保在UI线程上执行 Dispatcher.BeginInvoke(new Action(() => { ApplyAlwaysOnTop(); }), DispatcherPriority.Loaded); } } /// /// 窗口失去焦点时的处理 /// private void Window_Deactivated(object sender, EventArgs e) { // 窗口失去焦点时,如果启用了置顶功能且处于无焦点模式,重新应用置顶设置 if (Settings.Advanced.IsAlwaysOnTop && Settings.Advanced.IsNoFocusMode) { // 使用Dispatcher.BeginInvoke确保在UI线程上执行 Dispatcher.BeginInvoke(new Action(() => { ApplyAlwaysOnTop(); }), DispatcherPriority.Loaded); } } #region 全局快捷键管理 /// /// 初始化墨迹渐隐管理器 /// private void InitializeInkFadeManager() { try { // 确保墨迹渐隐管理器已初始化 if (_inkFadeManager == null) { _inkFadeManager = new InkFadeManager(this); } // 同步设置状态 _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; _inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime); LogHelper.WriteLogToFile("墨迹渐隐管理器已初始化", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"初始化墨迹渐隐管理器时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 初始化全局快捷键管理器 /// private void InitializeGlobalHotkeyManager() { try { _globalHotkeyManager = new GlobalHotkeyManager(this); // 启动时加载快捷键,但默认为鼠标模式,禁用快捷键以放行键盘操作 _globalHotkeyManager.EnableHotkeyRegistration(); // 启动时默认为鼠标模式,禁用快捷键 _globalHotkeyManager.UpdateHotkeyStateForToolMode(true); LogHelper.WriteLogToFile("全局快捷键管理器已初始化,启动时默认为鼠标模式并禁用快捷键", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"初始化全局快捷键管理器时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 打开快捷键设置窗口 /// private void OpenHotkeySettingsWindow() { try { if (_globalHotkeyManager == null) { MessageBox.Show("快捷键管理器尚未初始化,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } // 暂时隐藏设置面板 BorderSettings.Visibility = Visibility.Hidden; BorderSettingsMask.Visibility = Visibility.Hidden; // 创建快捷键设置窗口 var hotkeySettingsWindow = new HotkeySettingsWindow(this, _globalHotkeyManager); // 设置窗口关闭事件,用于在快捷键设置窗口关闭后恢复设置面板 hotkeySettingsWindow.Closed += (s, e) => { // 恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; BorderSettingsMask.Visibility = Visibility.Visible; }; // 显示快捷键设置窗口 hotkeySettingsWindow.ShowDialog(); } catch (Exception ex) { // 确保在发生错误时也恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; BorderSettingsMask.Visibility = Visibility.Visible; LogHelper.WriteLogToFile($"打开快捷键设置窗口时出错: {ex.Message}", LogHelper.LogType.Error); MessageBox.Show($"打开快捷键设置窗口时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion #region 墨迹渐隐功能 /// /// 墨迹渐隐开关切换事件处理 /// private void ToggleSwitchEnableInkFade_Toggled(object sender, RoutedEventArgs e) { try { Settings.Canvas.EnableInkFade = ToggleSwitchEnableInkFade.IsOn; _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; // 同步批注子面板中的开关状态 if (ToggleSwitchInkFadeInPanel != null) { ToggleSwitchInkFadeInPanel.IsOn = Settings.Canvas.EnableInkFade; } // 同步普通画笔面板中的开关状态 if (ToggleSwitchInkFadeInPanel2 != null) { ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade; } LogHelper.WriteLogToFile($"墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 墨迹渐隐时间滑块值改变事件处理 /// private void InkFadeTimeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { try { Settings.Canvas.InkFadeTime = (int)e.NewValue; if (_inkFadeManager != null) { _inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime); } LogHelper.WriteLogToFile($"墨迹渐隐时间已更新为 {Settings.Canvas.InkFadeTime}ms", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"更新墨迹渐隐时间时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 批注子面板中墨迹渐隐开关切换事件处理 /// private void ToggleSwitchInkFadeInPanel_Toggled(object sender, RoutedEventArgs e) { try { Settings.Canvas.EnableInkFade = ToggleSwitchInkFadeInPanel.IsOn; _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; // 同步设置面板中的开关状态 if (ToggleSwitchEnableInkFade != null) { ToggleSwitchEnableInkFade.IsOn = Settings.Canvas.EnableInkFade; } // 同步普通画笔面板中的开关状态 if (ToggleSwitchInkFadeInPanel2 != null) { ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade; } LogHelper.WriteLogToFile($"批注子面板中墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"批注子面板中切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// PPT放映模式显示手势按钮开关切换事件处理 /// private void ToggleSwitchShowGestureButtonInSlideShow_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; var toggle = sender as ToggleSwitch; Settings.PowerPointSettings.ShowGestureButtonInSlideShow = toggle != null && toggle.IsOn; SaveSettingsToFile(); // 如果当前在PPT放映模式,需要立即更新手势按钮的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdateGestureButtonVisibilityInPPTMode(); } LogHelper.WriteLogToFile($"PPT放映模式显示手势按钮已{(Settings.PowerPointSettings.ShowGestureButtonInSlideShow ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { LogHelper.WriteLogToFile($"切换PPT放映模式显示手势按钮时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 更新PPT模式下手势按钮的显示状态 /// private void UpdateGestureButtonVisibilityInPPTMode() { try { if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow) { // 如果启用了PPT放映模式显示手势按钮,则检查是否在批注模式下显示手势按钮 CheckEnableTwoFingerGestureBtnVisibility(true); } else { // 如果禁用了PPT放映模式显示手势按钮,则隐藏手势按钮 EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed; } } catch (Exception ex) { LogHelper.WriteLogToFile($"更新PPT模式下手势按钮显示状态时出错: {ex.Message}", LogHelper.LogType.Error); } } #endregion #region PPT翻页直接传递 /// /// 直接发送翻页请求到PPT放映软件,让PPT软件处理翻页 /// /// 是否为上一页 private void SendKeyToPPTSlideShow(bool isPrevious) { try { // 查找PPT放映窗口并发送按键 var pptWindows = Process.GetProcessesByName("POWERPNT"); var wpsWindows = Process.GetProcessesByName("wpp"); foreach (var process in pptWindows.Concat(wpsWindows)) { if (process.MainWindowHandle != IntPtr.Zero) { // 激活PPT窗口 SetForegroundWindow(process.MainWindowHandle); // 发送翻页按键消息 int keyCode = isPrevious ? 0x21 : 0x22; // VK_PRIOR : VK_NEXT // 发送按键按下和释放消息 PostMessage(process.MainWindowHandle, 0x0100, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYDOWN PostMessage(process.MainWindowHandle, 0x0101, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYUP break; } } } catch (Exception) { // 如果直接发送失败,回退到原来的方法 if (isPrevious) BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null); else BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null); } } #endregion /// /// 初始化文件关联状态显示 /// private void InitializeFileAssociationStatus() { try { bool isRegistered = FileAssociationManager.IsFileAssociationRegistered(); if (isRegistered) { TextBlockFileAssociationStatus.Text = "✓ .icstk文件关联已注册"; TextBlockFileAssociationStatus.Foreground = new SolidColorBrush(Colors.LightGreen); } else { TextBlockFileAssociationStatus.Text = "✗ .icstk文件关联未注册"; TextBlockFileAssociationStatus.Foreground = new SolidColorBrush(Colors.LightCoral); } } catch (Exception ex) { TextBlockFileAssociationStatus.Text = "✗ 检查文件关联状态时出错"; TextBlockFileAssociationStatus.Foreground = new SolidColorBrush(Colors.LightCoral); LogHelper.WriteLogToFile($"初始化文件关联状态显示时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 处理命令行参数中的文件路径 /// private void HandleCommandLineFileOpen() { try { // 检查启动参数中是否有.icstk文件 string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(App.StartArgs); if (!string.IsNullOrEmpty(icstkFile)) { LogHelper.WriteLogToFile($"检测到命令行参数中的.icstk文件: {icstkFile}", LogHelper.LogType.Event); // 延迟执行,确保UI已完全加载 Dispatcher.BeginInvoke(new Action(() => { try { // 打开文件 OpenSingleStrokeFile(icstkFile); ShowNotification($"已加载墨迹文件: {Path.GetFileName(icstkFile)}"); } catch (Exception ex) { LogHelper.WriteLogToFile($"打开命令行参数中的文件失败: {ex.Message}", LogHelper.LogType.Error); ShowNotification("打开墨迹文件失败"); } }), DispatcherPriority.Loaded); } } catch (Exception ex) { LogHelper.WriteLogToFile($"处理命令行文件打开时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 集中管理工具模式切换和快捷键状态更新 /// 避免在每个工具按钮点击时重复刷新快捷键状态 /// /// 新的编辑模式 /// 可选的额外操作委托 internal void SetCurrentToolMode(InkCanvasEditingMode newMode, Action additionalActions = null) { try { // 执行模式切换 inkCanvas.EditingMode = newMode; // 根据模式确定是否为鼠标模式(无工具模式) bool isMouseMode = newMode == InkCanvasEditingMode.None; // 更新快捷键状态 if (_globalHotkeyManager != null) { _globalHotkeyManager.UpdateHotkeyStateForToolMode(isMouseMode); } // 在PPT放映模式下,工具模式切换时需要更新手势按钮的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdateGestureButtonVisibilityInPPTMode(); } // 执行额外的操作(如果有) additionalActions?.Invoke(); LogHelper.WriteLogToFile($"工具模式已切换到: {newMode}, 鼠标模式: {isMouseMode}", LogHelper.LogType.Trace); } catch (Exception ex) { LogHelper.WriteLogToFile($"设置工具模式时出错: {ex.Message}", LogHelper.LogType.Error); } } #region 滑块触摸支持 /// /// 为所有滑块控件添加触摸和手写笔事件支持 /// private void AddTouchSupportToSliders() { try { // 获取所有滑块控件并添加触摸支持 var sliders = new List { InkFadeTimeSlider, AutoStraightenLineThresholdSlider, LineStraightenSensitivitySlider, LineEndpointSnappingThresholdSlider, ViewboxFloatingBarScaleTransformValueSlider, ViewboxFloatingBarOpacityValueSlider, ViewboxFloatingBarOpacityInPPTValueSlider, PPTButtonLeftPositionValueSlider, PPTButtonRightPositionValueSlider, PPTButtonLBPositionValueSlider, PPTButtonRBPositionValueSlider, TouchMultiplierSlider, NibModeBoundsWidthSlider, FingerModeBoundsWidthSlider, SideControlMinimumAutomationSlider, RandWindowOnceCloseLatencySlider, RandWindowOnceMaxStudentsSlider, BoardInkWidthSlider, BoardInkAlphaSlider, BoardHighlighterWidthSlider, InkWidthSlider, InkAlphaSlider, HighlighterWidthSlider }; foreach (var slider in sliders) { if (slider != null) { AddTouchSupportToSlider(slider); } } LogHelper.WriteLogToFile("已为所有滑块控件添加触摸支持", LogHelper.LogType.Trace); } catch (Exception ex) { LogHelper.WriteLogToFile($"添加滑块触摸支持时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 为单个滑块控件添加触摸和手写笔事件支持 /// /// 要添加触摸支持的滑块控件 private void AddTouchSupportToSlider(Slider slider) { if (slider == null) return; // 启用触摸和手写笔支持 slider.IsManipulationEnabled = true; // 添加触摸事件 - 使用更简单直接的方法 slider.TouchDown += (s, e) => HandleSliderTouch(s, e, slider); slider.TouchMove += (s, e) => HandleSliderTouch(s, e, slider); slider.TouchUp += (s, e) => HandleSliderTouchEnd(s, e, slider); // 添加手写笔事件 slider.StylusDown += (s, e) => HandleSliderStylus(s, e, slider); slider.StylusMove += (s, e) => HandleSliderStylus(s, e, slider); slider.StylusUp += (s, e) => HandleSliderStylusEnd(s, e, slider); } /// /// 处理滑块触摸事件(按下和移动) /// private void HandleSliderTouch(object sender, TouchEventArgs e, Slider slider) { if (slider == null) return; // 捕获触摸设备 if (e.RoutedEvent == TouchDownEvent) { slider.CaptureTouch(e.TouchDevice); } // 计算触摸位置对应的滑块值 var touchPoint = e.GetTouchPoint(slider); // 使用更精确的位置计算方法 UpdateSliderValueFromPositionImproved(slider, touchPoint.Position); e.Handled = true; } /// /// 处理滑块触摸结束事件 /// private void HandleSliderTouchEnd(object sender, TouchEventArgs e, Slider slider) { if (slider == null) return; // 释放触摸捕获 slider.ReleaseTouchCapture(e.TouchDevice); e.Handled = true; } /// /// 处理滑块手写笔事件(按下和移动) /// private void HandleSliderStylus(object sender, StylusEventArgs e, Slider slider) { if (slider == null) return; // 捕获手写笔设备 if (e.RoutedEvent == StylusDownEvent) { slider.CaptureStylus(); } // 计算手写笔位置对应的滑块值 var stylusPoint = e.GetStylusPoints(slider); if (stylusPoint.Count > 0) { UpdateSliderValueFromPositionImproved(slider, stylusPoint[0].ToPoint()); } e.Handled = true; } /// /// 处理滑块手写笔结束事件 /// private void HandleSliderStylusEnd(object sender, StylusEventArgs e, Slider slider) { if (slider == null) return; // 释放手写笔捕获 slider.ReleaseStylusCapture(); e.Handled = true; } /// /// 根据触摸/手写笔位置更新滑块值(改进版本) /// /// 滑块控件 /// 触摸/手写笔位置 private void UpdateSliderValueFromPositionImproved(Slider slider, Point position) { if (slider == null) return; try { // 获取滑块的轨道元素 var track = slider.Template.FindName("PART_Track", slider) as Track; if (track == null) { // 如果找不到轨道,使用简单方法 UpdateSliderValueFromPosition(slider, position); return; } // 获取轨道的实际边界 var trackBounds = track.TransformToAncestor(slider).TransformBounds(new Rect(0, 0, track.ActualWidth, track.ActualHeight)); double relativePosition = 0; if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal) { // 水平滑块 if (trackBounds.Width > 0) { // 计算相对于轨道的相对位置 var relativeX = position.X - trackBounds.X; relativePosition = Math.Max(0, Math.Min(1, relativeX / trackBounds.Width)); } } else { // 垂直滑块 if (trackBounds.Height > 0) { // 计算相对于轨道的相对位置 var relativeY = position.Y - trackBounds.Y; relativePosition = Math.Max(0, Math.Min(1, relativeY / trackBounds.Height)); } } // 计算新的滑块值 var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum); // 如果启用了吸附到刻度,则调整到最近的刻度 if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0) { var tickCount = (int)((slider.Maximum - slider.Minimum) / slider.TickFrequency); var tickIndex = (int)Math.Round(relativePosition * tickCount); newValue = slider.Minimum + tickIndex * slider.TickFrequency; } // 更新滑块值 slider.Value = Math.Max(slider.Minimum, Math.Min(slider.Maximum, newValue)); } catch (Exception ex) { // 如果改进方法失败,回退到简单方法 UpdateSliderValueFromPosition(slider, position); LogHelper.WriteLogToFile($"更新滑块值时出错,使用回退方法: {ex.Message}", LogHelper.LogType.Error); } } /// /// 根据触摸/手写笔位置更新滑块值(简单版本) /// /// 滑块控件 /// 触摸/手写笔位置 private void UpdateSliderValueFromPosition(Slider slider, Point position) { if (slider == null) return; try { // 使用更简单直接的方法计算滑块值 double relativePosition = 0; if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal) { // 水平滑块 - 使用滑块的实际宽度 var sliderWidth = slider.ActualWidth; if (sliderWidth > 0) { // 考虑滑块的边距和拇指大小 var thumbSize = 20; // 假设拇指大小约为20像素 var effectiveWidth = sliderWidth - thumbSize; var adjustedX = position.X - thumbSize / 2; relativePosition = Math.Max(0, Math.Min(1, adjustedX / effectiveWidth)); } } else { // 垂直滑块 - 使用滑块的实际高度 var sliderHeight = slider.ActualHeight; if (sliderHeight > 0) { // 考虑滑块的边距和拇指大小 var thumbSize = 20; // 假设拇指大小约为20像素 var effectiveHeight = sliderHeight - thumbSize; var adjustedY = position.Y - thumbSize / 2; relativePosition = Math.Max(0, Math.Min(1, adjustedY / effectiveHeight)); } } // 计算新的滑块值 var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum); // 如果启用了吸附到刻度,则调整到最近的刻度 if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0) { var tickCount = (int)((slider.Maximum - slider.Minimum) / slider.TickFrequency); var tickIndex = (int)Math.Round(relativePosition * tickCount); newValue = slider.Minimum + tickIndex * slider.TickFrequency; } // 更新滑块值 slider.Value = Math.Max(slider.Minimum, Math.Min(slider.Maximum, newValue)); } catch (Exception ex) { LogHelper.WriteLogToFile($"更新滑块值时出错: {ex.Message}", LogHelper.LogType.Error); } } #endregion #region 模式切换相关 /// /// 模式切换开关事件处理 /// private void ToggleSwitchMode_Toggled(object sender, RoutedEventArgs e) { try { var toggle = sender as ToggleSwitch; if (toggle != null) { Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn; // 如果切换到仅PPT模式,立即隐藏主窗口 if (Settings.ModeSettings.IsPPTOnlyMode) { Hide(); LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); } else { // 如果切换到正常模式,显示主窗口 Show(); LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event); } } } catch (Exception ex) { LogHelper.WriteLogToFile($"切换模式时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 检查是否应该显示主窗口(基于PPT模式和PPT放映状态) /// private void CheckMainWindowVisibility() { try { if (Settings.ModeSettings.IsPPTOnlyMode) { // 仅PPT模式下,只有在PPT放映时才显示 bool isInSlideShow = BtnPPTSlideShowEnd.Visibility == Visibility.Visible; if (isInSlideShow && !IsVisible) { Show(); LogHelper.WriteLogToFile("PPT放映开始,显示主窗口(仅PPT模式)", LogHelper.LogType.Trace); } else if (!isInSlideShow && IsVisible) { Hide(); LogHelper.WriteLogToFile("PPT放映结束,隐藏主窗口(仅PPT模式)", LogHelper.LogType.Trace); } } else { // 正常模式下,确保主窗口可见 if (!IsVisible) { Show(); } } } catch (Exception ex) { LogHelper.WriteLogToFile($"检查主窗口可见性时出错: {ex.Message}", LogHelper.LogType.Error); } } #endregion } }