diff --git a/.all-contributorsrc b/.all-contributorsrc index dd376fcb..e21644e4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -19,15 +19,6 @@ "code" ] }, - { - "login": "Hydro11451", - "name": "Hydrogen", - "avatar_url": "https://avatars.githubusercontent.com/u/214308559?v=4", - "profile": "http://hydro11451.qzz.io", - "contributions": [ - "code" - ] - }, { "login": "CreeperAWA", "name": "CreeperAWA", diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f6c695cc..0f826f70 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -39,14 +39,14 @@ body: 2. 3. validations: - required: false + required: true - type: textarea id: expected attributes: label: 期望结果 | Expected Behavior description: 你期望的正确行为或结果 | What did you expect to happen? validations: - required: false + required: true - type: textarea id: extra attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index ac8291f1..276b7afb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -20,7 +20,7 @@ body: label: 需求动机 | Motivation description: 为什么需要这个功能?| Why do you need this feature? validations: - required: false + required: true - type: textarea id: design attributes: diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt index dfebfd9a..a3caf1e9 100644 --- a/AutomaticUpdateVersionControl.txt +++ b/AutomaticUpdateVersionControl.txt @@ -1 +1 @@ -1.7.10.0 +1.7.11.0 diff --git a/Ink Canvas/App.xaml b/Ink Canvas/App.xaml index 1ba8b030..0eecad8a 100644 --- a/Ink Canvas/App.xaml +++ b/Ink Canvas/App.xaml @@ -32,7 +32,7 @@ - + diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 3f9f8c90..2e4f8e12 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -33,7 +33,9 @@ namespace Ink_Canvas public static string[] StartArgs; public static string RootPath = Environment.GetEnvironmentVariable("APPDATA") + "\\Ink Canvas\\"; - + + // 新增:标记是否通过--board参数启动 + public static bool StartWithBoardMode = false; // 新增:保存看门狗进程对象 private static Process watchdogProcess; // 新增:标记是否为软件内主动退出 @@ -292,6 +294,27 @@ namespace Ink_Canvas { string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机"; WriteCrashLog($"系统会话即将结束: {reason}"); + + // 清理PowerPoint进程守护 + try + { + // 获取主窗口实例并清理PowerPoint进程守护 + var mainWindow = Current.MainWindow as MainWindow; + if (mainWindow != null) + { + // 通过反射调用StopPowerPointProcessMonitoring方法 + var method = mainWindow.GetType().GetMethod("StopPowerPointProcessMonitoring", + BindingFlags.NonPublic | BindingFlags.Instance); + method?.Invoke(mainWindow, null); + + WriteCrashLog("PowerPoint进程守护已在系统关机时清理"); + } + } + catch (Exception ex) + { + WriteCrashLog($"清理PowerPoint进程守护失败: {ex.Message}"); + } + DeviceIdentifier.SaveUsageStatsOnShutdown(); } @@ -408,7 +431,7 @@ namespace Ink_Canvas try { // 优先从 Settings.json 直接读取 - var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.json"); + var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json"); if (File.Exists(settingsPath)) { var json = File.ReadAllText(settingsPath); @@ -474,6 +497,14 @@ namespace Ink_Canvas // 检查是否为最终应用启动(更新后的应用) bool isFinalApp = e.Args.Contains("--final-app"); bool skipMutexCheck = e.Args.Contains("--skip-mutex-check"); + + // 检查是否通过--board参数启动 + bool hasBoardArg = e.Args.Contains("--board"); + if (hasBoardArg) + { + StartWithBoardMode = true; + LogHelper.WriteLogToFile("App | 检测到--board参数,将直接进入白板模式"); + } // 记录最终应用启动状态 if (isFinalApp) @@ -633,13 +664,13 @@ namespace Ink_Canvas if (!ret && !e.Args.Contains("-m")) //-m multiple { LogHelper.NewLog("Detected existing instance"); - + // 检查是否有.icstk文件参数 string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(e.Args); if (!string.IsNullOrEmpty(icstkFile)) { LogHelper.WriteLogToFile($"检测到已运行实例,尝试通过IPC发送文件: {icstkFile}", LogHelper.LogType.Event); - + // 尝试通过IPC发送文件路径给已运行实例 if (FileAssociationManager.TrySendFileToExistingInstance(icstkFile)) { @@ -650,11 +681,26 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("通过IPC发送文件路径失败", LogHelper.LogType.Warning); } } + // 检查是否有--board参数 + else if (hasBoardArg) + { + LogHelper.WriteLogToFile("检测到已运行实例且有--board参数,尝试通过IPC发送白板模式命令", LogHelper.LogType.Event); + + // 尝试通过IPC发送白板模式命令给已运行实例 + if (FileAssociationManager.TrySendBoardModeCommandToExistingInstance()) + { + LogHelper.WriteLogToFile("白板模式命令已通过IPC发送给已运行实例", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("通过IPC发送白板模式命令失败", LogHelper.LogType.Warning); + } + } else { LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event); } - + LogHelper.NewLog("Ink Canvas automatically closed"); IsAppExitByUser = true; // 多开时标记为用户主动退出 // 写入退出信号,确保看门狗不会重启 @@ -783,7 +829,7 @@ namespace Ink_Canvas } // 备份路径更改为软件根目录下的saves/RegistryBackups文件夹 - string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups"); + string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups"); LogHelper.WriteLogToFile($"备份路径: {backupPath}"); if (!Directory.Exists(backupPath)) @@ -1297,7 +1343,7 @@ namespace Ink_Canvas try { // 准备备份目录 - string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups"); + string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups"); if (!Directory.Exists(backupPath)) { Directory.CreateDirectory(backupPath); diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index dff64f25..094e2735 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.10.0")] -[assembly: AssemblyFileVersion("1.7.10.0")] +[assembly: AssemblyVersion("1.7.11.0")] +[assembly: AssemblyFileVersion("1.7.11.0")] diff --git a/Ink Canvas/FloatingWindowInterceptorManager.cs b/Ink Canvas/FloatingWindowInterceptorManager.cs new file mode 100644 index 00000000..9eb9f6ea --- /dev/null +++ b/Ink Canvas/FloatingWindowInterceptorManager.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Ink_Canvas.Helpers; + +namespace Ink_Canvas +{ + /// + /// 悬浮窗拦截管理器 + /// + public class FloatingWindowInterceptorManager : IDisposable + { + #region 私有字段 + + private FloatingWindowInterceptor _interceptor; + private bool _isInitialized; + private bool _disposed; + private FloatingWindowInterceptorSettings _settings; + + #endregion + + #region 事件 + + public event EventHandler WindowIntercepted; + public event EventHandler WindowRestored; + + #endregion + + #region 公共属性 + + public bool IsEnabled => _interceptor != null && _settings != null && _settings.IsEnabled; + public bool IsRunning => _interceptor != null && _interceptor.IsRunning; + + #endregion + + #region 公共方法 + + /// + /// 初始化拦截器 + /// + public void Initialize(FloatingWindowInterceptorSettings settings) + { + if (_isInitialized) return; + + try + { + _settings = settings ?? new FloatingWindowInterceptorSettings(); + _interceptor = new FloatingWindowInterceptor(); + + // 订阅事件 + _interceptor.WindowIntercepted += OnWindowIntercepted; + _interceptor.WindowRestored += OnWindowRestored; + + // 应用配置 + ApplySettings(); + + _isInitialized = true; + + // 如果设置了自动启动,则启动拦截器 + if (_settings.AutoStart && _settings.IsEnabled) + { + Start(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 启动拦截器 + /// + public void Start() + { + if (!_isInitialized || _settings == null) return; + + if (_interceptor == null) return; + + try + { + _interceptor.Start(_settings.ScanIntervalMs); + LogHelper.WriteLogToFile("悬浮窗拦截器已启动", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 停止拦截器 + /// + public void Stop() + { + if (_interceptor == null) return; + + try + { + _interceptor.Stop(); + LogHelper.WriteLogToFile("悬浮窗拦截器已停止", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"停止悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 设置拦截规则 + /// + public void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled) + { + if (_interceptor == null || _settings == null) return; + + try + { + _interceptor.SetInterceptRule(type, enabled); + + // 更新设置 + var ruleName = type.ToString(); + if (_settings.InterceptRules.ContainsKey(ruleName)) + { + _settings.InterceptRules[ruleName] = enabled; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 获取拦截规则 + /// + public FloatingWindowInterceptor.InterceptRule GetInterceptRule(FloatingWindowInterceptor.InterceptType type) + { + return _interceptor?.GetInterceptRule(type); + } + + /// + /// 获取所有拦截规则 + /// + public Dictionary GetAllRules() + { + return _interceptor?.GetAllRules() ?? new Dictionary(); + } + + /// + /// 手动扫描一次 + /// + public void ScanOnce() + { + if (_interceptor == null) return; + + try + { + _interceptor.ScanOnce(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"手动扫描失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + + /// + /// 恢复所有被拦截的窗口 + /// + public void RestoreAllWindows() + { + if (_interceptor == null) return; + + try + { + _interceptor.RestoreAllWindows(); + LogHelper.WriteLogToFile("已恢复所有被拦截的窗口", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"恢复窗口失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 应用设置 + /// + public void ApplySettings() + { + if (_interceptor == null || _settings == null) return; + + try + { + // 应用拦截规则设置 + foreach (var kvp in _settings.InterceptRules) + { + if (Enum.TryParse(kvp.Key, out var type)) + { + _interceptor.SetInterceptRule(type, kvp.Value); + } + } + + // 如果启用了拦截器,则启动 + if (_settings.IsEnabled && !IsRunning) + { + Start(); + } + // 如果禁用了拦截器,则停止 + else if (!_settings.IsEnabled && IsRunning) + { + Stop(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用设置失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 更新扫描间隔 + /// + public void UpdateScanInterval(int intervalMs) + { + if (_interceptor == null || _settings == null) return; + + try + { + _settings.ScanIntervalMs = intervalMs; + + // 如果正在运行,重启以应用新间隔 + if (IsRunning) + { + Stop(); + Start(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新扫描间隔失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 获取拦截统计信息 + /// + public InterceptStatistics GetStatistics() + { + if (_interceptor == null || _settings == null) return new InterceptStatistics(); + + try + { + var rules = GetAllRules(); + var enabledRules = rules.Count(r => r.Value.IsEnabled); + var totalRules = rules.Count; + + return new InterceptStatistics + { + TotalRules = totalRules, + EnabledRules = enabledRules, + IsRunning = IsRunning, + ScanIntervalMs = _settings.ScanIntervalMs + }; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取统计信息失败: {ex.Message}", LogHelper.LogType.Error); + return new InterceptStatistics(); + } + } + + #endregion + + #region 私有方法 + + private void OnWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e) + { + try + { + // 记录日志 + LogHelper.WriteLogToFile($"拦截窗口: {e.WindowTitle} ({e.InterceptType})", LogHelper.LogType.Event); + + // 显示通知(如果启用) + if (_settings != null && _settings.ShowNotifications) + { + ShowNotification($"已拦截悬浮窗: {e.Rule.Description}"); + } + + // 触发事件 + WindowIntercepted?.Invoke(this, e); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + private void OnWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e) + { + try + { + // 记录日志 + LogHelper.WriteLogToFile($"恢复窗口: {e.InterceptType}", LogHelper.LogType.Event); + + // 触发事件 + WindowRestored?.Invoke(this, e); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + private void ShowNotification(string message) + { + try + { + // 这里可以集成系统通知或自定义通知 + // 暂时使用调试输出 + System.Diagnostics.Debug.WriteLine($"通知: {message}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示通知失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion + + #region 辅助类 + + public class InterceptStatistics + { + public int TotalRules { get; set; } + public int EnabledRules { get; set; } + public bool IsRunning { get; set; } + public int ScanIntervalMs { get; set; } + } + + #endregion + + #region IDisposable + + public void Dispose() + { + if (_disposed) return; + + try + { + Stop(); + _interceptor?.Dispose(); + _interceptor = null; + _isInitialized = false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error); + } + finally + { + _disposed = true; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs new file mode 100644 index 00000000..da9430dc --- /dev/null +++ b/Ink Canvas/Helpers/CameraService.cs @@ -0,0 +1,315 @@ +using AForge.Video; +using AForge.Video.DirectShow; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Windows.Media.Imaging; +using System.Windows.Threading; + +namespace Ink_Canvas.Helpers +{ + public class CameraService : IDisposable + { + private VideoCaptureDevice _videoSource; + private bool _isCapturing; + private Bitmap _currentFrame; + private readonly object _frameLock = new object(); + private Dispatcher _dispatcher; + + public event EventHandler FrameReceived; + public event EventHandler ErrorOccurred; + + public bool IsCapturing => _isCapturing; + public List AvailableCameras { get; private set; } + public FilterInfo CurrentCamera { get; private set; } + + public CameraService() + { + _dispatcher = Dispatcher.CurrentDispatcher; + AvailableCameras = new List(); + RefreshCameraList(); + } + + /// + /// 刷新可用摄像头列表 + /// + public void RefreshCameraList() + { + try + { + AvailableCameras.Clear(); + var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); + + foreach (FilterInfo device in videoDevices) + { + AvailableCameras.Add(device); + } + + LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}"); + } + } + + /// + /// 开始摄像头预览 + /// + /// 摄像头索引 + public bool StartPreview(int cameraIndex = 0) + { + try + { + if (AvailableCameras.Count == 0) + { + RefreshCameraList(); + if (AvailableCameras.Count == 0) + { + ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备"); + return false; + } + } + + if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count) + { + ErrorOccurred?.Invoke(this, "摄像头索引超出范围"); + return false; + } + + // 停止当前预览 + StopPreview(); + + CurrentCamera = AvailableCameras[cameraIndex]; + _videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString); + + // 设置视频源事件处理 + _videoSource.NewFrame += VideoSource_NewFrame; + + // 启动视频源 + _videoSource.Start(); + + _isCapturing = true; + LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}"); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}"); + return false; + } + } + + /// + /// 停止摄像头预览 + /// + public void StopPreview() + { + try + { + if (_videoSource != null && _videoSource.IsRunning) + { + _videoSource.SignalToStop(); + _videoSource.WaitForStop(); + _videoSource.NewFrame -= VideoSource_NewFrame; + _videoSource = null; + } + + _isCapturing = false; + LogHelper.WriteLogToFile("摄像头预览已停止"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 切换到指定摄像头 + /// + /// 摄像头索引 + public bool SwitchCamera(int cameraIndex) + { + try + { + if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count) + { + ErrorOccurred?.Invoke(this, "摄像头索引超出范围"); + return false; + } + + return StartPreview(cameraIndex); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}"); + return false; + } + } + + /// + /// 获取当前帧的BitmapSource(WPF格式),直接返回可用的WPF位图 + /// + public BitmapSource GetCurrentFrameAsBitmapSource() + { + lock (_frameLock) + { + if (_currentFrame == null) + return null; + + try + { + // 验证位图有效性 + if (_currentFrame.Width <= 0 || _currentFrame.Height <= 0) + return null; + + // 使用更安全的方法转换位图 + var bitmapData = _currentFrame.LockBits( + new Rectangle(0, 0, _currentFrame.Width, _currentFrame.Height), + ImageLockMode.ReadOnly, + _currentFrame.PixelFormat); + + try + { + // 根据像素格式选择合适的WPF像素格式 + System.Windows.Media.PixelFormat wpfPixelFormat; + switch (_currentFrame.PixelFormat) + { + case PixelFormat.Format24bppRgb: + wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24; + break; + case PixelFormat.Format32bppArgb: + wpfPixelFormat = System.Windows.Media.PixelFormats.Bgra32; + break; + case PixelFormat.Format32bppRgb: + wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr32; + break; + default: + wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24; + break; + } + + var bitmapSource = BitmapSource.Create( + bitmapData.Width, + bitmapData.Height, + _currentFrame.HorizontalResolution, + _currentFrame.VerticalResolution, + wpfPixelFormat, + null, + bitmapData.Scan0, + bitmapData.Stride * bitmapData.Height, + bitmapData.Stride); + + bitmapSource.Freeze(); + return bitmapSource; + } + finally + { + _currentFrame.UnlockBits(bitmapData); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + } + + + /// + /// 视频源新帧事件处理 + /// + private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs) + { + try + { + lock (_frameLock) + { + // 释放之前的帧 + _currentFrame?.Dispose(); + + // 创建新的位图,避免Clone的问题 + var sourceFrame = eventArgs.Frame; + + if (sourceFrame != null) + { + try + { + var width = sourceFrame.Width; + var height = sourceFrame.Height; + + if (width > 0 && height > 0) + { + _currentFrame = new Bitmap(width, height, PixelFormat.Format24bppRgb); + using (var graphics = Graphics.FromImage(_currentFrame)) + { + graphics.DrawImage(sourceFrame, 0, 0); + } + } + else + { + _currentFrame = null; + } + } + catch (Exception frameEx) + { + LogHelper.WriteLogToFile($"处理源帧失败: {frameEx.Message}", LogHelper.LogType.Error); + _currentFrame = null; + } + } + else + { + _currentFrame = null; + } + } + + // 在UI线程中触发事件 + _dispatcher.BeginInvoke(new Action(() => + { + FrameReceived?.Invoke(this, _currentFrame); + })); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}"); + } + } + + /// + /// 获取摄像头名称列表 + /// + public List GetCameraNames() + { + return AvailableCameras.Select(camera => camera.Name).ToList(); + } + + /// + /// 检查是否有可用摄像头 + /// + public bool HasAvailableCameras() + { + if (AvailableCameras.Count == 0) + { + RefreshCameraList(); + } + return AvailableCameras.Count > 0; + } + + public void Dispose() + { + StopPreview(); + + lock (_frameLock) + { + _currentFrame?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs index 586dcc0c..0fc47e56 100644 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -17,7 +17,7 @@ namespace Ink_Canvas.Helpers // 文件路径策略 private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat"); private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.enc"); - private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "saves", "usage_stats_backup.enc"); + private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "Saves", "usage_stats_backup.enc"); private static readonly string DeviceId; private static readonly object fileLock = new object(); @@ -1092,7 +1092,7 @@ namespace Ink_Canvas.Helpers { int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion); LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}"); - + // 当版本号代数差异大于3时自动更新 if (versionDiff > 3) { diff --git a/Ink Canvas/Helpers/FileAssociationManager.cs b/Ink Canvas/Helpers/FileAssociationManager.cs index e950e521..cc5460ad 100644 --- a/Ink Canvas/Helpers/FileAssociationManager.cs +++ b/Ink Canvas/Helpers/FileAssociationManager.cs @@ -1,12 +1,12 @@ +using Microsoft.Win32; using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Security; -using System.Windows; -using Microsoft.Win32; -using System.Threading; using System.Text; +using System.Threading; +using System.Windows; namespace Ink_Canvas.Helpers { @@ -19,11 +19,12 @@ namespace Ink_Canvas.Helpers private const string FileTypeName = "InkCanvasStrokesFile"; private const string AppName = "Ink Canvas"; private const string AppDescription = "Ink Canvas Strokes File"; - + // IPC相关常量 private const string IpcMutexName = "InkCanvasFileAssociationIpc"; private const string IpcEventName = "InkCanvasFileAssociationEvent"; private const string IpcFilePrefix = "InkCanvasFileAssociation_"; + private const string IpcBoardModePrefix = "InkCanvasBoardMode_"; private const int IpcTimeout = 5000; // 5秒超时 /// @@ -34,19 +35,19 @@ namespace Ink_Canvas.Helpers try { string exePath = Process.GetCurrentProcess().MainModule.FileName; - + // 注册文件类型 using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName)) { fileTypeKey.SetValue("", AppDescription); fileTypeKey.SetValue("FriendlyTypeName", AppDescription); - + // 设置默认图标 using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon")) { defaultIconKey.SetValue("", $"\"{exePath}\",0"); } - + // 设置打开命令 using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell")) using (RegistryKey openKey = shellKey.CreateSubKey("open")) @@ -55,16 +56,16 @@ namespace Ink_Canvas.Helpers commandKey.SetValue("", $"\"{exePath}\" \"%1\""); } } - + // 注册文件扩展名 using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension)) { extensionKey.SetValue("", FileTypeName); } - + // 刷新系统文件关联缓存 RefreshSystemFileAssociations(); - + LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event); return true; } @@ -94,13 +95,13 @@ namespace Ink_Canvas.Helpers { // 删除文件扩展名关联 Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false); - + // 删除文件类型定义 Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false); - + // 刷新系统文件关联缓存 RefreshSystemFileAssociations(); - + LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event); return true; } @@ -121,21 +122,21 @@ namespace Ink_Canvas.Helpers using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension)) { if (extensionKey == null) return false; - + string fileType = extensionKey.GetValue("") as string; if (string.IsNullOrEmpty(fileType)) return false; - + using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType)) { if (fileTypeKey == null) return false; - + using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command")) { if (shellKey == null) return false; - + string command = shellKey.GetValue("") as string; if (string.IsNullOrEmpty(command)) return false; - + // 检查命令是否指向当前应用程序 string currentExePath = Process.GetCurrentProcess().MainModule.FileName; return command.Contains(currentExePath); @@ -147,7 +148,7 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error); } - + return false; } @@ -184,11 +185,11 @@ namespace Ink_Canvas.Helpers public static string GetIcstkFileFromArgs(string[] args) { if (args == null || args.Length == 0) return null; - + foreach (string arg in args) { if (string.IsNullOrEmpty(arg)) continue; - + // 检查是否为.icstk文件 if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase)) { @@ -204,7 +205,7 @@ namespace Ink_Canvas.Helpers } } } - + return null; } @@ -218,24 +219,24 @@ namespace Ink_Canvas.Helpers try { LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event); - + // 创建IPC文件 string tempDir = Path.GetTempPath(); string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp"; string ipcFilePath = Path.Combine(tempDir, ipcFileName); - + // 写入文件路径到IPC文件 File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8); - + // 创建事件通知已运行实例 using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName)) { ipcEvent.Set(); } - + // 等待一段时间让已运行实例处理文件 Thread.Sleep(1000); - + // 清理IPC文件 try { @@ -248,7 +249,7 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); } - + LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event); return true; } @@ -259,6 +260,56 @@ namespace Ink_Canvas.Helpers } } + /// + /// 尝试通过IPC将白板模式命令发送给已运行的实例 + /// + /// 是否成功发送 + public static bool TrySendBoardModeCommandToExistingInstance() + { + try + { + LogHelper.WriteLogToFile("尝试通过IPC发送白板模式命令给已运行实例", LogHelper.LogType.Event); + + // 创建IPC文件 + string tempDir = Path.GetTempPath(); + string ipcFileName = IpcBoardModePrefix + Guid.NewGuid().ToString("N") + ".tmp"; + string ipcFilePath = Path.Combine(tempDir, ipcFileName); + + // 写入白板模式命令到IPC文件 + File.WriteAllText(ipcFilePath, "BOARD_MODE", Encoding.UTF8); + + // 创建事件通知已运行实例 + using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName)) + { + ipcEvent.Set(); + } + + // 等待一段时间让已运行实例处理命令 + Thread.Sleep(1000); + + // 清理IPC文件 + try + { + if (File.Exists(ipcFilePath)) + { + File.Delete(ipcFilePath); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); + } + + LogHelper.WriteLogToFile("IPC白板模式命令发送完成", LogHelper.LogType.Event); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"通过IPC发送白板模式命令失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + /// /// 启动IPC监听器,等待其他实例发送文件路径 /// @@ -271,7 +322,7 @@ namespace Ink_Canvas.Helpers try { LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event); - + using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName)) { while (true) @@ -281,11 +332,11 @@ namespace Ink_Canvas.Helpers { // 处理IPC文件 ProcessIpcFiles(); - + // 重置事件 ipcEvent.Reset(); } - + // 检查应用是否还在运行 if (Application.Current == null || Application.Current.Dispatcher == null) { @@ -299,7 +350,7 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error); } }); - + ipcThread.IsBackground = true; ipcThread.Start(); } @@ -317,19 +368,20 @@ namespace Ink_Canvas.Helpers try { string tempDir = Path.GetTempPath(); - string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp"); + // 处理文件路径IPC文件 + string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp"); foreach (string ipcFile in ipcFiles) { try { // 读取文件路径 string filePath = File.ReadAllText(ipcFile, Encoding.UTF8); - + if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) { LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event); - + // 在UI线程中处理文件打开 Application.Current.Dispatcher.BeginInvoke(new Action(() => { @@ -348,14 +400,65 @@ namespace Ink_Canvas.Helpers } })); } - + // 删除IPC文件 File.Delete(ipcFile); } catch (Exception ex) { LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); - + + // 尝试删除损坏的IPC文件 + try + { + if (File.Exists(ipcFile)) + { + File.Delete(ipcFile); + } + } + catch { } + } + } + + // 处理白板模式命令IPC文件 + string[] boardModeFiles = Directory.GetFiles(tempDir, IpcBoardModePrefix + "*.tmp"); + foreach (string ipcFile in boardModeFiles) + { + try + { + // 读取命令内容 + string command = File.ReadAllText(ipcFile, Encoding.UTF8); + + if (command == "BOARD_MODE") + { + LogHelper.WriteLogToFile("IPC接收到白板模式命令", LogHelper.LogType.Event); + + // 在UI线程中处理白板模式切换 + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + try + { + // 获取主窗口并切换到白板模式 + if (Application.Current.MainWindow is MainWindow mainWindow) + { + mainWindow.SwitchToBoardMode(); + mainWindow.ShowNotification("已切换到白板模式"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"IPC处理白板模式切换失败: {ex.Message}", LogHelper.LogType.Error); + } + })); + } + + // 删除IPC文件 + File.Delete(ipcFile); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理白板模式IPC文件失败: {ex.Message}", LogHelper.LogType.Warning); + // 尝试删除损坏的IPC文件 try { @@ -377,4 +480,4 @@ namespace Ink_Canvas.Helpers [DllImport("shell32.dll")] private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs new file mode 100644 index 00000000..33ffafac --- /dev/null +++ b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs @@ -0,0 +1,762 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Threading; + +namespace Ink_Canvas.Helpers +{ + /// + /// 悬浮窗拦截器 - 检测和隐藏指定的悬浮窗 + /// + public class FloatingWindowInterceptor : IDisposable + { + #region Windows API Declarations + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); + + [DllImport("user32.dll")] + private static extern uint 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 IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.RECT lpRect); + + [DllImport("kernel32.dll")] + private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll")] + private static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll")] + private static extern int GetProcessImageFileName(IntPtr hProcess, StringBuilder lpImageFileName, int nSize); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + private const int SW_HIDE = 0; + private const int SW_SHOW = 1; + private const int SW_MINIMIZE = 6; + private const int SW_RESTORE = 9; + + private const int GWL_EXSTYLE = -20; + private const uint WS_EX_TOOLWINDOW = 0x00000080; + private const uint WS_EX_APPWINDOW = 0x00040000; + + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_NOZORDER = 0x0004; + private const uint SWP_HIDEWINDOW = 0x0080; + + private const uint PROCESS_QUERY_INFORMATION = 0x0400; + private const uint PROCESS_VM_READ = 0x0010; + + #endregion + + #region 拦截规则定义 + + /// + /// 拦截规则类型 + /// + public enum InterceptType + { + /// + /// 希沃白板3 桌面悬浮窗 + /// + SeewoWhiteboard3Floating, + /// + /// 希沃白板5 桌面悬浮窗 + /// + SeewoWhiteboard5Floating, + /// + /// 希沃白板5C 桌面悬浮窗 + /// + SeewoWhiteboard5CFloating, + /// + /// 希沃品课教师端 桌面悬浮窗 + /// + SeewoPincoSideBarFloating, + /// + /// 希沃品课教师端 画笔悬浮窗(包括PPT控件) + /// + SeewoPincoDrawingFloating, + /// + /// 希沃PPT小工具 + /// + SeewoPPTFloating, + /// + /// AiClass 桌面悬浮窗 + /// + AiClassFloating, + /// + /// 鸿合屏幕书写 + /// + HiteAnnotationFloating, + /// + /// 畅言智慧课堂 桌面悬浮窗 + /// + ChangYanFloating, + /// + /// 畅言智慧课堂 PPT悬浮窗 + /// + ChangYanPptFloating, + /// + /// 天喻教育云互动课堂 桌面悬浮窗(包括PPT控件) + /// + IntelligentClassFloating, + /// + /// 希沃桌面 画笔悬浮窗 + /// + SeewoDesktopAnnotationFloating, + /// + /// 希沃桌面 侧栏悬浮窗 + /// + SeewoDesktopSideBarFloating + } + + /// + /// 拦截规则 + /// + public class InterceptRule + { + public InterceptType Type { get; set; } + public string ProcessName { get; set; } + public string WindowTitlePattern { get; set; } + public string ClassNamePattern { get; set; } + public bool IsEnabled { get; set; } + public bool RequiresAdmin { get; set; } + public string Description { get; set; } + } + + #endregion + + #region 私有字段 + + private readonly Dictionary _interceptRules; + private readonly Dictionary _interceptedWindows; + private readonly Timer _scanTimer; + private readonly Dispatcher _dispatcher; + private bool _isRunning; + private bool _disposed; + + #endregion + + #region 公共属性 + + public bool IsRunning => _isRunning; + + #endregion + + #region 事件 + + public event EventHandler WindowIntercepted; + public event EventHandler WindowRestored; + + #endregion + + #region 构造函数 + + public FloatingWindowInterceptor() + { + _interceptRules = new Dictionary(); + _interceptedWindows = new Dictionary(); + _dispatcher = Dispatcher.CurrentDispatcher; + + InitializeRules(); + _scanTimer = new Timer(ScanForWindows, null, Timeout.Infinite, Timeout.Infinite); + } + + #endregion + + #region 初始化 + + private void InitializeRules() + { + // 希沃白板3 桌面悬浮窗 + _interceptRules[InterceptType.SeewoWhiteboard3Floating] = new InterceptRule + { + Type = InterceptType.SeewoWhiteboard3Floating, + ProcessName = "EasiNote", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃白板3 桌面悬浮窗" + }; + + // 希沃白板5 桌面悬浮窗 + _interceptRules[InterceptType.SeewoWhiteboard5Floating] = new InterceptRule + { + Type = InterceptType.SeewoWhiteboard5Floating, + ProcessName = "EasiNote", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃白板5 桌面悬浮窗" + }; + + // 希沃白板5C 桌面悬浮窗 + _interceptRules[InterceptType.SeewoWhiteboard5CFloating] = new InterceptRule + { + Type = InterceptType.SeewoWhiteboard5CFloating, + ProcessName = "EasiNote5C", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃白板5C 桌面悬浮窗" + }; + + // 希沃品课教师端 桌面悬浮窗 + _interceptRules[InterceptType.SeewoPincoSideBarFloating] = new InterceptRule + { + Type = InterceptType.SeewoPincoSideBarFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃品课教师端 桌面悬浮窗" + }; + + // 希沃品课教师端 画笔悬浮窗 + _interceptRules[InterceptType.SeewoPincoDrawingFloating] = new InterceptRule + { + Type = InterceptType.SeewoPincoDrawingFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)" + }; + + // 希沃PPT小工具 + _interceptRules[InterceptType.SeewoPPTFloating] = new InterceptRule + { + Type = InterceptType.SeewoPPTFloating, + ProcessName = "SeewoPPT", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃PPT小工具" + }; + + // AiClass 桌面悬浮窗 + _interceptRules[InterceptType.AiClassFloating] = new InterceptRule + { + Type = InterceptType.AiClassFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "AiClass 桌面悬浮窗" + }; + + // 鸿合屏幕书写 + _interceptRules[InterceptType.HiteAnnotationFloating] = new InterceptRule + { + Type = InterceptType.HiteAnnotationFloating, + ProcessName = "HiteVision", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "鸿合屏幕书写" + }; + + // 畅言智慧课堂 桌面悬浮窗 + _interceptRules[InterceptType.ChangYanFloating] = new InterceptRule + { + Type = InterceptType.ChangYanFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 桌面悬浮窗" + }; + + // 畅言智慧课堂 PPT悬浮窗 + _interceptRules[InterceptType.ChangYanPptFloating] = new InterceptRule + { + Type = InterceptType.ChangYanPptFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = true, + Description = "畅言智慧课堂 PPT悬浮窗" + }; + + // 天喻教育云互动课堂 桌面悬浮窗 + _interceptRules[InterceptType.IntelligentClassFloating] = new InterceptRule + { + Type = InterceptType.IntelligentClassFloating, + ProcessName = "ClassIn", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)" + }; + + // 希沃桌面 画笔悬浮窗 + _interceptRules[InterceptType.SeewoDesktopAnnotationFloating] = new InterceptRule + { + Type = InterceptType.SeewoDesktopAnnotationFloating, + ProcessName = "Seewo", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = false, + Description = "希沃桌面 画笔悬浮窗" + }; + + // 希沃桌面 侧栏悬浮窗 + _interceptRules[InterceptType.SeewoDesktopSideBarFloating] = new InterceptRule + { + Type = InterceptType.SeewoDesktopSideBarFloating, + ProcessName = "Seewo", + WindowTitlePattern = "", + ClassNamePattern = "", + IsEnabled = true, + RequiresAdmin = true, + Description = "希沃桌面 侧栏悬浮窗" + }; + + } + + #endregion + + #region 公共方法 + + /// + /// 启动拦截器 + /// + public void Start(int scanIntervalMs = 5000) + { + if (_isRunning) return; + + _isRunning = true; + _scanTimer.Change(0, scanIntervalMs); + } + + /// + /// 停止拦截器 + /// + public void Stop() + { + if (!_isRunning) return; + + _isRunning = false; + _scanTimer.Change(Timeout.Infinite, Timeout.Infinite); + + // 恢复所有被拦截的窗口 + RestoreAllWindows(); + } + + /// + /// 设置拦截规则 + /// + public void SetInterceptRule(InterceptType type, bool enabled) + { + if (_interceptRules.ContainsKey(type)) + { + _interceptRules[type].IsEnabled = enabled; + } + } + + /// + /// 获取拦截规则 + /// + public InterceptRule GetInterceptRule(InterceptType type) + { + return _interceptRules.ContainsKey(type) ? _interceptRules[type] : null; + } + + /// + /// 获取所有拦截规则 + /// + public Dictionary GetAllRules() + { + return new Dictionary(_interceptRules); + } + + /// + /// 手动扫描一次 + /// + public void ScanOnce() + { + ScanForWindows(null); + } + + /// + /// 恢复所有被拦截的窗口 + /// + public void RestoreAllWindows() + { + var windowsToRestore = new List(_interceptedWindows.Keys); + foreach (var hWnd in windowsToRestore) + { + RestoreWindow(hWnd); + } + } + + /// + /// 恢复指定窗口 + /// + public bool RestoreWindow(IntPtr hWnd) + { + if (!_interceptedWindows.ContainsKey(hWnd)) return false; + + if (IsWindow(hWnd)) + { + ShowWindow(hWnd, SW_RESTORE); + _interceptedWindows.Remove(hWnd); + + WindowRestored?.Invoke(this, new WindowRestoredEventArgs + { + WindowHandle = hWnd, + InterceptType = _interceptedWindows[hWnd] + }); + + return true; + } + + _interceptedWindows.Remove(hWnd); + return false; + } + + #endregion + + #region 私有方法 + + private void ScanForWindows(object state) + { + if (!_isRunning) return; + + try + { + EnumWindows(EnumWindowsCallback, IntPtr.Zero); + } + catch (Exception ex) + { + // 记录错误但不中断扫描 + LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + } + } + + private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam) + { + try + { + // 基本检查 + if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) return true; + + // 检查是否已经被拦截 + if (_interceptedWindows.ContainsKey(hWnd)) return true; + + // 获取窗口信息 + var windowInfo = GetWindowInfo(hWnd); + if (windowInfo == null) return true; + + // 检查窗口样式,过滤掉系统窗口和主窗口 + var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + var style = GetWindowLong(hWnd, -16); // GWL_STYLE + + // 跳过工具窗口 + if ((exStyle & WS_EX_TOOLWINDOW) != 0) + { + return true; + } + + // 跳过主窗口(有标题栏和系统菜单的窗口) + const uint WS_CAPTION = 0x00C00000; + const uint WS_SYSMENU = 0x00080000; + if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0) + { + return true; + } + + // 检查窗口大小,跳过过大的窗口 + var rect = new ForegroundWindowInfo.RECT(); + GetWindowRect(hWnd, out rect); + var width = rect.Right - rect.Left; + var height = rect.Bottom - rect.Top; + + if (width > 600 || height > 400) + { + return true; + } + + // 检查是否匹配拦截规则 + foreach (var rule in _interceptRules.Values) + { + if (!rule.IsEnabled) continue; + + if (MatchesRule(windowInfo, rule)) + { + InterceptWindow(hWnd, rule); + break; + } + } + + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + return true; + } + } + + private WindowInfo GetWindowInfo(IntPtr hWnd) + { + try + { + // 获取进程ID + GetWindowThreadProcessId(hWnd, out uint processId); + if (processId == 0) return null; + + // 获取进程信息 + var process = Process.GetProcessById((int)processId); + if (process == null) return null; + + // 获取窗口标题 + var titleBuilder = new StringBuilder(256); + GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity); + + // 获取窗口类名 + var classBuilder = new StringBuilder(256); + GetClassName(hWnd, classBuilder, classBuilder.Capacity); + + return new WindowInfo + { + Handle = hWnd, + ProcessId = processId, + ProcessName = process.ProcessName, + WindowTitle = titleBuilder.ToString(), + ClassName = classBuilder.ToString(), + Process = process + }; + } + catch + { + return null; + } + } + + private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule) + { + try + { + // 检查进程名(如果指定了进程名) + if (!string.IsNullOrEmpty(rule.ProcessName)) + { + if (!windowInfo.ProcessName.ToLower().Contains(rule.ProcessName.ToLower())) + { + return false; + } + } + + // 检查窗口标题(如果指定了模式) + if (!string.IsNullOrEmpty(rule.WindowTitlePattern)) + { + if (!windowInfo.WindowTitle.ToLower().Contains(rule.WindowTitlePattern.ToLower())) + { + return false; + } + } + + // 检查类名(如果指定了模式) + if (!string.IsNullOrEmpty(rule.ClassNamePattern)) + { + if (!windowInfo.ClassName.ToLower().Contains(rule.ClassNamePattern.ToLower())) + { + return false; + } + } + + // 如果所有检查都通过,就认为是目标窗口 + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + private void InterceptWindow(IntPtr hWnd, InterceptRule rule) + { + try + { + // 使用多种方法隐藏窗口 + // 方法1:移动到屏幕外 + SetWindowPos(hWnd, IntPtr.Zero, -2000, -2000, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW); + + // 方法2:最小化窗口 + ShowWindow(hWnd, SW_MINIMIZE); + + // 方法3:隐藏窗口 + ShowWindow(hWnd, SW_HIDE); + + // 记录拦截的窗口 + _interceptedWindows[hWnd] = rule.Type; + + // 触发事件 + WindowIntercepted?.Invoke(this, new WindowInterceptedEventArgs + { + WindowHandle = hWnd, + InterceptType = rule.Type, + Rule = rule, + WindowTitle = GetWindowTitle(hWnd) + }); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"拦截窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + } + } + + private string GetWindowTitle(IntPtr hWnd) + { + try + { + var titleBuilder = new StringBuilder(256); + GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity); + return titleBuilder.ToString(); + } + catch + { + return "Unknown"; + } + } + + private bool IsMainWindow(IntPtr hWnd) + { + try + { + // 检查是否有父窗口 + var parent = GetWindow(hWnd, 4); // GW_OWNER + if (parent != IntPtr.Zero) return false; + + // 检查窗口样式 + var style = GetWindowLong(hWnd, -16); // GWL_STYLE + var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + + // 主窗口通常有 WS_CAPTION 和 WS_SYSMENU + const uint WS_CAPTION = 0x00C00000; + const uint WS_SYSMENU = 0x00080000; + + if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0) + { + return true; // 这可能是主窗口 + } + + // 检查窗口大小,主窗口通常比较大 + var rect = new ForegroundWindowInfo.RECT(); + GetWindowRect(hWnd, out rect); + var width = rect.Right - rect.Left; + var height = rect.Bottom - rect.Top; + + // 如果窗口很大,可能是主窗口 + if (width > 800 && height > 600) + { + return true; + } + + return false; + } + catch + { + return false; + } + } + + #endregion + + #region 辅助类 + + private class WindowInfo + { + public IntPtr Handle { get; set; } + public uint ProcessId { get; set; } + public string ProcessName { get; set; } + public string WindowTitle { get; set; } + public string ClassName { get; set; } + public Process Process { get; set; } + } + + #endregion + + #region 事件参数类 + + public class WindowInterceptedEventArgs : EventArgs + { + public IntPtr WindowHandle { get; set; } + public InterceptType InterceptType { get; set; } + public InterceptRule Rule { get; set; } + public string WindowTitle { get; set; } + } + + public class WindowRestoredEventArgs : EventArgs + { + public IntPtr WindowHandle { get; set; } + public InterceptType InterceptType { get; set; } + } + + #endregion + + #region IDisposable + + public void Dispose() + { + if (_disposed) return; + + Stop(); + _scanTimer?.Dispose(); + + // 恢复所有被拦截的窗口 + RestoreAllWindows(); + + _disposed = true; + } + + #endregion + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs index a5640467..70d6ecb6 100644 --- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs +++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs @@ -21,7 +21,7 @@ namespace Ink_Canvas.Helpers private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键 // 配置文件路径 - private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json"); + private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json"); #endregion #region Constructor @@ -30,6 +30,9 @@ namespace Ink_Canvas.Helpers _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); _registeredHotkeys = new Dictionary(); _hotkeysShouldBeRegistered = true; // 启动时注册热键 + + // 启动时确保配置文件存在 + EnsureConfigFileExists(); } #endregion @@ -280,6 +283,17 @@ namespace Ink_Canvas.Helpers return; } + // 如果配置文件不存在,先创建默认配置文件 + if (!File.Exists(HotkeyConfigFile)) + { + LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile("配置文件不存在,创建默认配置文件并注册默认快捷键"); + CreateDefaultConfigFile(); + RegisterDefaultHotkeys(); + _hotkeysShouldBeRegistered = true; + return; + } + // 尝试从配置文件加载 if (LoadHotkeysFromConfigFile()) { @@ -289,19 +303,9 @@ namespace Ink_Canvas.Helpers } else { - // 如果配置文件不存在或加载失败,使用默认快捷键 - if (!File.Exists(HotkeyConfigFile)) - { - LogHelper.WriteLogToFile("配置文件不存在,注册默认快捷键"); - RegisterDefaultHotkeys(); - _hotkeysShouldBeRegistered = true; - } - else - { - LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning); - RegisterDefaultHotkeys(); - _hotkeysShouldBeRegistered = true; - } + LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning); + RegisterDefaultHotkeys(); + _hotkeysShouldBeRegistered = true; } } catch (Exception ex) @@ -353,9 +357,6 @@ namespace Ink_Canvas.Helpers } else { - LogHelper.WriteLogToFile("快捷键注册功能已经启用,重新加载快捷键设置"); - // 即使已经启用,也要重新加载快捷键设置以确保快捷键正常工作 - LoadHotkeysFromSettings(); } } catch (Exception ex) @@ -375,7 +376,6 @@ namespace Ink_Canvas.Helpers if (_hotkeysShouldBeRegistered) { _hotkeysShouldBeRegistered = false; - LogHelper.WriteLogToFile("禁用快捷键注册功能"); // 注销所有快捷键 UnregisterAllHotkeys(); @@ -413,14 +413,12 @@ namespace Ink_Canvas.Helpers { // 鼠标模式下禁用快捷键,让键盘操作放行 DisableHotkeyRegistration(); - LogHelper.WriteLogToFile("切换到鼠标模式,禁用快捷键以放行键盘操作"); } } else { // 非鼠标模式下启用快捷键 EnableHotkeyRegistration(); - LogHelper.WriteLogToFile("切换到非鼠标模式,启用快捷键"); } } catch (Exception ex) @@ -505,6 +503,90 @@ namespace Ink_Canvas.Helpers } } + /// + /// 确保配置文件存在,如果不存在则创建 + /// + private void EnsureConfigFileExists() + { + try + { + // 如果配置文件不存在,创建默认配置文件 + if (!File.Exists(HotkeyConfigFile)) + { + LogHelper.WriteLogToFile($"快捷键配置文件不存在,创建默认配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event); + 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 = "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); + + LogHelper.WriteLogToFile($"已创建默认快捷键配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建默认快捷键配置文件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + /// /// 从配置文件加载快捷键设置 /// diff --git a/Ink Canvas/Helpers/InkSmoothingConfig.cs b/Ink Canvas/Helpers/InkSmoothingConfig.cs index dea1df70..6cb996bb 100644 --- a/Ink Canvas/Helpers/InkSmoothingConfig.cs +++ b/Ink Canvas/Helpers/InkSmoothingConfig.cs @@ -71,37 +71,43 @@ namespace Ink_Canvas.Helpers } /// - /// 应用质量设置 + /// 应用质量设置 /// public void ApplyQualitySettings() { switch (Quality) { case SmoothingQuality.Performance: - SmoothingStrength = 0.2; - ResampleInterval = 4.0; - InterpolationSteps = 6; + SmoothingStrength = 0.15; + ResampleInterval = 5.0; + InterpolationSteps = 4; UseAdaptiveInterpolation = false; - CurveTension = 0.2; + CurveTension = 0.15; MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2); + UseHardwareAcceleration = true; + UseAsyncProcessing = true; break; case SmoothingQuality.Balanced: - SmoothingStrength = 0.4; - ResampleInterval = 2.5; - InterpolationSteps = 12; + SmoothingStrength = 0.3; + ResampleInterval = 3.0; + InterpolationSteps = 8; UseAdaptiveInterpolation = true; - CurveTension = 0.3; + CurveTension = 0.25; MaxConcurrentTasks = Environment.ProcessorCount; + UseHardwareAcceleration = true; + UseAsyncProcessing = true; break; case SmoothingQuality.Quality: - SmoothingStrength = 0.6; - ResampleInterval = 1.5; - InterpolationSteps = 20; + SmoothingStrength = 0.5; + ResampleInterval = 2.0; + InterpolationSteps = 15; UseAdaptiveInterpolation = true; - CurveTension = 0.4; + CurveTension = 0.35; MaxConcurrentTasks = Environment.ProcessorCount; + UseHardwareAcceleration = true; + UseAsyncProcessing = true; break; } } diff --git a/Ink Canvas/Helpers/MultiPPTInkManager.cs b/Ink Canvas/Helpers/MultiPPTInkManager.cs new file mode 100644 index 00000000..29228802 --- /dev/null +++ b/Ink Canvas/Helpers/MultiPPTInkManager.cs @@ -0,0 +1,666 @@ +using Microsoft.Office.Interop.PowerPoint; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Windows.Ink; + +namespace Ink_Canvas.Helpers +{ + /// + /// 多PPT墨迹管理器 - 支持多个PPT窗口分别管理墨迹 + /// + public class MultiPPTInkManager : IDisposable + { + #region Properties + public bool IsAutoSaveEnabled { get; set; } = true; + public string AutoSaveLocation { get; set; } = ""; + public PPTManager PPTManager { get; set; } + #endregion + + #region Private Fields + private readonly Dictionary _presentationManagers; + private readonly Dictionary _presentationInfos; + private readonly object _lockObject = new object(); + private bool _disposed; + private string _currentActivePresentationId = ""; + #endregion + + #region Constructor + public MultiPPTInkManager() + { + _presentationManagers = new Dictionary(); + _presentationInfos = new Dictionary(); + } + #endregion + + #region Public Methods + /// + /// 初始化新的演示文稿 + /// + public void InitializePresentation(Presentation presentation) + { + if (presentation == null) return; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + + // 如果已存在该演示文稿的管理器,先清理 + if (_presentationManagers.ContainsKey(presentationId)) + { + _presentationManagers[presentationId].Dispose(); + _presentationManagers.Remove(presentationId); + } + + // 创建新的墨迹管理器 + var inkManager = new PPTInkManager(); + inkManager.IsAutoSaveEnabled = IsAutoSaveEnabled; + inkManager.AutoSaveLocation = AutoSaveLocation; + inkManager.InitializePresentation(presentation); + + // 保存管理器和演示文稿信息 + _presentationManagers[presentationId] = inkManager; + _presentationInfos[presentationId] = new PresentationInfo + { + Id = presentationId, + Name = presentation.Name, + FullName = presentation.FullName, + SlideCount = presentation.Slides.Count, + CreatedTime = DateTime.Now, + LastAccessTime = DateTime.Now + }; + + // 设置为当前活跃的演示文稿 + _currentActivePresentationId = presentationId; + + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化多PPT墨迹管理失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 切换到指定的演示文稿 + /// + public bool SwitchToPresentation(Presentation presentation) + { + if (presentation == null) return false; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + + if (_presentationManagers.ContainsKey(presentationId)) + { + // 如果切换的是不同的演示文稿,先保存当前活跃演示文稿的墨迹 + if (!string.IsNullOrEmpty(_currentActivePresentationId) && + _currentActivePresentationId != presentationId) + { + var currentManager = GetCurrentManager(); + if (currentManager != null) + { + // 获取当前活跃的演示文稿并保存墨迹 + var currentPresentation = GetCurrentActivePresentation(); + if (currentPresentation != null) + { + currentManager.SaveAllStrokesToFile(currentPresentation); + LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace); + } + } + } + + _currentActivePresentationId = presentationId; + + // 更新最后访问时间 + if (_presentationInfos.ContainsKey(presentationId)) + { + _presentationInfos[presentationId].LastAccessTime = DateTime.Now; + } + + if (_currentActivePresentationId != presentationId) + { + LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace); + } + return true; + } + else + { + // 如果不存在,尝试初始化 + InitializePresentation(presentation); + return true; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到演示文稿失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + } + + /// + /// 保存当前页面的墨迹 + /// + public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes) + { + if (slideIndex <= 0 || strokes == null) return; + + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + manager.SaveCurrentSlideStrokes(slideIndex, strokes); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 强制保存指定页面的墨迹(忽略锁定状态) + /// + public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes) + { + if (slideIndex <= 0 || strokes == null) return; + + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + manager.ForceSaveSlideStrokes(slideIndex, strokes); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"强制保存页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 加载指定页面的墨迹 + /// + public StrokeCollection LoadSlideStrokes(int slideIndex) + { + if (slideIndex <= 0) return new StrokeCollection(); + + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + return manager.LoadSlideStrokes(slideIndex); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + + return new StrokeCollection(); + } + + /// + /// 切换到指定页面并加载墨迹 + /// + public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null) + { + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + return manager.SwitchToSlide(slideIndex, currentStrokes); + } + else + { + LogHelper.WriteLogToFile($"无法获取当前墨迹管理器,页面切换失败: {slideIndex}", LogHelper.LogType.Warning); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + + return new StrokeCollection(); + } + + /// + /// 保存所有墨迹到文件 + /// + public void SaveAllStrokesToFile(Presentation presentation) + { + if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + if (_presentationManagers.ContainsKey(presentationId)) + { + _presentationManagers[presentationId].SaveAllStrokesToFile(presentation); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存所有墨迹到文件失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 从文件加载已保存的墨迹 + /// + public void LoadSavedStrokes(Presentation presentation) + { + if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + if (_presentationManagers.ContainsKey(presentationId)) + { + _presentationManagers[presentationId].LoadSavedStrokes(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 清除指定演示文稿的所有墨迹 + /// + public void ClearPresentationStrokes(Presentation presentation) + { + if (presentation == null) return; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + if (_presentationManagers.ContainsKey(presentationId)) + { + _presentationManagers[presentationId].ClearAllStrokes(); + LogHelper.WriteLogToFile($"已清除演示文稿墨迹: {presentation.Name}", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清除演示文稿墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 清除所有演示文稿的墨迹 + /// + public void ClearAllStrokes() + { + lock (_lockObject) + { + try + { + foreach (var manager in _presentationManagers.Values) + { + manager?.ClearAllStrokes(); + } + LogHelper.WriteLogToFile("已清除所有演示文稿墨迹", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清除所有墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 翻页后锁定墨迹写入 + /// + public void LockInkForSlide(int slideIndex) + { + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + manager.LockInkForSlide(slideIndex); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"锁定墨迹写入失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 检查是否可以写入墨迹 + /// + public bool CanWriteInk(int currentSlideIndex) + { + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + return manager.CanWriteInk(currentSlideIndex); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查墨迹写入权限失败: {ex}", LogHelper.LogType.Error); + } + } + + return false; + } + + /// + /// 重置当前演示文稿的墨迹锁定状态 + /// + public void ResetCurrentPresentationLockState() + { + lock (_lockObject) + { + try + { + var manager = GetCurrentManager(); + if (manager != null) + { + manager.ResetLockState(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重置墨迹锁定状态失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 移除演示文稿管理器 + /// + public void RemovePresentation(Presentation presentation) + { + if (presentation == null) return; + + lock (_lockObject) + { + try + { + var presentationId = GeneratePresentationId(presentation); + + if (_presentationManagers.ContainsKey(presentationId)) + { + // 保存墨迹到文件 + _presentationManagers[presentationId].SaveAllStrokesToFile(presentation); + + // 释放资源 + _presentationManagers[presentationId].Dispose(); + _presentationManagers.Remove(presentationId); + } + + if (_presentationInfos.ContainsKey(presentationId)) + { + _presentationInfos.Remove(presentationId); + } + + // 如果移除的是当前活跃的演示文稿,重置活跃ID + if (_currentActivePresentationId == presentationId) + { + _currentActivePresentationId = ""; + } + + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010) + { + } + } + catch (Exception) + { + } + } + } + + /// + /// 获取当前管理的演示文稿数量 + /// + public int GetPresentationCount() + { + lock (_lockObject) + { + return _presentationManagers.Count; + } + } + + /// + /// 获取所有演示文稿信息 + /// + public List GetAllPresentationInfos() + { + lock (_lockObject) + { + return _presentationInfos.Values.ToList(); + } + } + + /// + /// 清理长时间未访问的演示文稿管理器 + /// + public void CleanupInactivePresentations(TimeSpan inactiveThreshold) + { + lock (_lockObject) + { + try + { + var inactiveIds = new List(); + var cutoffTime = DateTime.Now - inactiveThreshold; + + foreach (var info in _presentationInfos.Values) + { + if (info.LastAccessTime < cutoffTime && info.Id != _currentActivePresentationId) + { + inactiveIds.Add(info.Id); + } + } + + foreach (var id in inactiveIds) + { + if (_presentationManagers.ContainsKey(id)) + { + _presentationManagers[id].Dispose(); + _presentationManagers.Remove(id); + } + _presentationInfos.Remove(id); + + LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理非活跃演示文稿失败: {ex}", LogHelper.LogType.Error); + } + } + } + #endregion + + #region Private Methods + private PPTInkManager GetCurrentManager() + { + if (string.IsNullOrEmpty(_currentActivePresentationId) || + !_presentationManagers.ContainsKey(_currentActivePresentationId)) + { + return null; + } + + return _presentationManagers[_currentActivePresentationId]; + } + + private Presentation GetCurrentActivePresentation() + { + try + { + // 通过PPTManager获取当前活跃的演示文稿 + return PPTManager?.GetCurrentActivePresentation(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error); + return null; + } + } + + private string GeneratePresentationId(Presentation presentation) + { + try + { + // 检查COM对象是否仍然有效 + if (presentation == null) + { + return $"invalid_{DateTime.Now.Ticks}"; + } + + var presentationPath = presentation.FullName; + var fileHash = GetFileHash(presentationPath); + var processId = GetProcessId(presentation); + return $"{presentation.Name}_{fileHash}_{processId}"; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010) + { + return $"disconnected_{DateTime.Now.Ticks}"; + } + return $"error_{DateTime.Now.Ticks}"; + } + catch (Exception) + { + return $"unknown_{DateTime.Now.Ticks}"; + } + } + + private string GetFileHash(string filePath) + { + try + { + if (string.IsNullOrEmpty(filePath)) return "unknown"; + + using (var md5 = MD5.Create()) + { + byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath)); + return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8); + } + } + catch (Exception) + { + // 所有异常都静默处理,避免日志噪音 + return "error"; + } + } + + private string GetProcessId(Presentation presentation) + { + try + { + // 尝试获取PowerPoint应用程序的进程ID + if (presentation.Application != null) + { + // 通过COM对象获取进程信息 + var hwnd = presentation.Application.HWND; + if (hwnd != 0) + { + return hwnd.ToString(); + } + } + return "unknown"; + } + catch (COMException comEx) + { + // COM对象已失效,这是正常情况,完全静默处理 + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010) + { + return "disconnected"; + } + return "error"; + } + catch (Exception) + { + return "error"; + } + } + #endregion + + #region Dispose + public void Dispose() + { + if (!_disposed) + { + lock (_lockObject) + { + foreach (var manager in _presentationManagers.Values) + { + manager?.Dispose(); + } + _presentationManagers.Clear(); + _presentationInfos.Clear(); + } + _disposed = true; + } + } + #endregion + } + + /// + /// 演示文稿信息 + /// + public class PresentationInfo + { + public string Id { get; set; } + public string Name { get; set; } + public string FullName { get; set; } + public int SlideCount { get; set; } + public DateTime CreatedTime { get; set; } + public DateTime LastAccessTime { get; set; } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/MultiTouchInput.cs b/Ink Canvas/Helpers/MultiTouchInput.cs index d9554d19..9764d2ca 100644 --- a/Ink Canvas/Helpers/MultiTouchInput.cs +++ b/Ink Canvas/Helpers/MultiTouchInput.cs @@ -25,10 +25,14 @@ namespace Ink_Canvas.Helpers } /// - /// 用于显示笔迹的类 + /// 用于显示笔迹的类 /// public class StrokeVisual : DrawingVisual { + private bool _needsRedraw = true; + private int _lastPointCount = 0; + private const int REDRAW_THRESHOLD = 3; + /// /// 创建显示笔迹的类 /// @@ -49,15 +53,20 @@ namespace Ink_Canvas.Helpers public StrokeVisual(DrawingAttributes drawingAttributes) { _drawingAttributes = drawingAttributes; + + // 启用硬件加速 + RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality); + RenderOptions.SetEdgeMode(this, EdgeMode.Aliased); + RenderOptions.SetCachingHint(this, CachingHint.Cache); } /// - /// 设置或获取显示的笔迹 + /// 设置或获取显示的笔迹 /// public Stroke Stroke { set; get; } /// - /// 在笔迹中添加点 + /// 在笔迹中添加点 /// /// public void Add(StylusPoint point) @@ -66,28 +75,50 @@ namespace Ink_Canvas.Helpers { var collection = new StylusPointCollection { point }; Stroke = new Stroke(collection) { DrawingAttributes = _drawingAttributes }; + _lastPointCount = 1; } else { Stroke.StylusPoints.Add(point); + _lastPointCount++; } + + // 标记需要重绘 + _needsRedraw = true; } /// - /// 重新画出笔迹 + /// 重新画出笔迹 /// public void Redraw() { + if (!_needsRedraw || Stroke == null) return; + + if (_lastPointCount % REDRAW_THRESHOLD != 0 && _lastPointCount > REDRAW_THRESHOLD) + { + return; + } + try { using (var dc = RenderOpen()) { Stroke.Draw(dc); } + _needsRedraw = false; } catch { } } + /// + /// 强制重绘 + /// + public void ForceRedraw() + { + _needsRedraw = true; + Redraw(); + } + private readonly DrawingAttributes _drawingAttributes; public static implicit operator Stroke(StrokeVisual v) diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs index 436f5d71..0776e827 100644 --- a/Ink Canvas/Helpers/PPTInkManager.cs +++ b/Ink Canvas/Helpers/PPTInkManager.cs @@ -1,6 +1,7 @@ using Microsoft.Office.Interop.PowerPoint; using System; using System.IO; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Windows.Ink; @@ -29,6 +30,11 @@ namespace Ink_Canvas.Helpers private DateTime _inkLockUntil = DateTime.MinValue; private int _lockedSlideIndex = -1; private const int InkLockMilliseconds = 500; + + // 添加快速切换保护机制 + private DateTime _lastSwitchTime = DateTime.MinValue; + private int _lastSwitchSlideIndex = -1; + private const int MinSwitchIntervalMs = 100; // 最小切换间隔100毫秒 #endregion #region Constructor @@ -66,7 +72,20 @@ namespace Ink_Canvas.Helpers _currentPresentationId = GeneratePresentationId(presentation); // 重新初始化内存流数组 - var slideCount = presentation.Slides.Count; + int slideCount = 0; + try + { + slideCount = presentation.Slides.Count; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x80048010) + { + return; + } + throw; + } _memoryStreams = new MemoryStream[slideCount + 2]; // 如果启用自动保存,尝试加载已保存的墨迹 @@ -75,7 +94,6 @@ namespace Ink_Canvas.Helpers LoadSavedStrokes(); } - LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace); } catch (Exception ex) { @@ -95,10 +113,13 @@ namespace Ink_Canvas.Helpers { try { - // 检查墨迹锁定 + // 检查墨迹锁定 if (!CanWriteInk(slideIndex)) { - LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning); + if (DateTime.Now < _inkLockUntil) + { + LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning); + } return; } @@ -112,7 +133,9 @@ namespace Ink_Canvas.Helpers _memoryStreams[slideIndex]?.Dispose(); _memoryStreams[slideIndex] = ms; - LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace); + if (ms.Length > 0) + { + } } } catch (Exception ex) @@ -122,6 +145,37 @@ namespace Ink_Canvas.Helpers } } + /// + /// 强制保存指定页面的墨迹(忽略锁定状态) + /// + public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes) + { + if (slideIndex <= 0 || strokes == null) return; + + lock (_lockObject) + { + try + { + if (slideIndex < _memoryStreams.Length) + { + var ms = new MemoryStream(); + strokes.Save(ms); + ms.Position = 0; + + // 释放旧的内存流 + _memoryStreams[slideIndex]?.Dispose(); + _memoryStreams[slideIndex] = ms; + + LogHelper.WriteLogToFile($"已强制保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"强制保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + /// /// 加载指定页面的墨迹 /// @@ -137,7 +191,6 @@ namespace Ink_Canvas.Helpers { _memoryStreams[slideIndex].Position = 0; var strokes = new StrokeCollection(_memoryStreams[slideIndex]); - LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace); return strokes; } } @@ -159,26 +212,29 @@ namespace Ink_Canvas.Helpers { try { - // 如果有当前墨迹,先保存到正确的页面 - if (currentStrokes != null && currentStrokes.Count > 0) + // 检查快速切换保护 + var now = DateTime.Now; + if (now - _lastSwitchTime < TimeSpan.FromMilliseconds(MinSwitchIntervalMs) && + _lastSwitchSlideIndex == slideIndex) { - // 确定要保存的页面索引 - int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex; - - // 确保页面索引有效 - if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length) - { - SaveCurrentSlideStrokes(saveToSlideIndex, currentStrokes); - LogHelper.WriteLogToFile($"已保存第{saveToSlideIndex}页墨迹,墨迹数量: {currentStrokes.Count}", LogHelper.LogType.Trace); - } + LogHelper.WriteLogToFile($"快速切换保护:忽略重复的页面切换请求 {slideIndex}", LogHelper.LogType.Warning); + return LoadSlideStrokes(slideIndex); } + // 设置墨迹锁定 LockInkForSlide(slideIndex); // 加载新页面的墨迹 var newStrokes = LoadSlideStrokes(slideIndex); - LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace); + + // 更新切换记录 + _lastSwitchTime = now; + _lastSwitchSlideIndex = slideIndex; + + if (newStrokes.Count > 0) + { + } return newStrokes; } @@ -219,7 +275,23 @@ namespace Ink_Canvas.Helpers // 保存所有页面的墨迹 int savedCount = 0; - for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++) + int slideCount = 0; + + try + { + slideCount = presentation.Slides.Count; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x80048010) + { + return; + } + throw; + } + + for (int i = 1; i <= slideCount && i < _memoryStreams.Length; i++) { if (_memoryStreams[i] != null) { @@ -235,7 +307,6 @@ namespace Ink_Canvas.Helpers File.WriteAllBytes(filePath, srcBuf); savedCount++; - LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace); } else { @@ -254,7 +325,6 @@ namespace Ink_Canvas.Helpers } } - LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event); } catch (Exception ex) { @@ -301,7 +371,6 @@ namespace Ink_Canvas.Helpers } } - LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event); } catch (Exception ex) { @@ -349,9 +418,34 @@ namespace Ink_Canvas.Helpers /// public bool CanWriteInk(int currentSlideIndex) { - if (DateTime.Now < _inkLockUntil) return false; - if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false; - return true; + // 如果锁定时间已过,允许写入 + if (DateTime.Now >= _inkLockUntil) + { + return true; + } + + // 如果当前页面与锁定页面相同,允许写入(用户在当前页面绘制) + if (currentSlideIndex == _lockedSlideIndex) + { + return true; + } + + // 只有在快速切换且页面不同时才锁定 + return false; + } + + /// + /// 重置墨迹锁定状态 + /// + public void ResetLockState() + { + lock (_lockObject) + { + _inkLockUntil = DateTime.MinValue; + _lockedSlideIndex = -1; + _lastSwitchTime = DateTime.MinValue; + _lastSwitchSlideIndex = -1; + } } #endregion @@ -362,7 +456,7 @@ namespace Ink_Canvas.Helpers { var presentationPath = presentation.FullName; var fileHash = GetFileHash(presentationPath); - return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}"; + return $"{presentation.Name}_{fileHash}"; } catch (Exception ex) { diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 48fc4c70..ecca9519 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -70,17 +70,17 @@ namespace Ink_Canvas.Helpers try { if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false; - + // 检查是否有放映窗口 var slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows == null || slideShowWindows.Count == 0) return false; - + // 验证放映窗口是否真正有效 try { var slideShowWindow = slideShowWindows[1]; if (slideShowWindow == null) return false; - + // 尝试访问放映窗口的属性来验证其有效性 var _ = slideShowWindow.View; return true; @@ -249,7 +249,6 @@ namespace Ink_Canvas.Helpers if (!currentSlideShowState) { - LogHelper.WriteLogToFile("检测到PPT放映已结束", LogHelper.LogType.Trace); } } } @@ -277,16 +276,6 @@ namespace Ink_Canvas.Helpers catch (COMException ex) { var hr = (uint)ex.HResult; - // 忽略常见的PowerPoint连接错误: - // 0x800401E3: 操作无法使用 - // 0x80004005: 未指定错误 - // 0x800706B5: RPC服务器不可用 - // 0x8001010E: 应用程序调用一个已为另一线程整理的接口 - // 0x800401F3: 无效的类字符串(PowerPoint未安装或COM组件未注册) - if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3) - { - LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning); - } return null; } catch (InvalidCastException) @@ -294,9 +283,8 @@ namespace Ink_Canvas.Helpers // COM对象类型转换失败 return null; } - catch (Exception ex) + catch (Exception) { - LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning); return null; } } @@ -422,12 +410,7 @@ namespace Ink_Canvas.Helpers } catch (COMException comEx) { - // COM对象已经被释放或在错误的线程中,忽略这些错误 var hr = (uint)comEx.HResult; - if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x800706B5) - { - LogHelper.WriteLogToFile($"取消PPT事件注册COM异常: {comEx}", LogHelper.LogType.Warning); - } } catch (InvalidCastException) { @@ -437,10 +420,53 @@ namespace Ink_Canvas.Helpers }, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1)); } } - catch (Exception ex) + catch (Exception) + { + } + + // 释放COM对象 + try + { + if (Marshal.IsComObject(CurrentSlide)) + { + Marshal.ReleaseComObject(CurrentSlide); + } + } + catch (Exception) + { + } + + try + { + if (Marshal.IsComObject(CurrentSlides)) + { + Marshal.ReleaseComObject(CurrentSlides); + } + } + catch (Exception) + { + } + + try + { + if (Marshal.IsComObject(CurrentPresentation)) + { + Marshal.ReleaseComObject(CurrentPresentation); + } + } + catch (Exception) + { + } + + try + { + if (Marshal.IsComObject(PPTApplication)) + { + Marshal.ReleaseComObject(PPTApplication); + } + } + catch (Exception) { - // 记录但不抛出异常,确保清理过程能够继续 - LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning); } // 清理引用 @@ -479,7 +505,7 @@ namespace Ink_Canvas.Helpers { CurrentPresentation = activePresentation; CurrentSlides = CurrentPresentation.Slides; - + // 验证页数读取是否成功 try { @@ -487,7 +513,6 @@ namespace Ink_Canvas.Helpers if (slideCount > 0) { SlidesCount = slideCount; - LogHelper.WriteLogToFile($"成功读取PPT页数: {slideCount}", LogHelper.LogType.Trace); } else { @@ -603,7 +628,6 @@ namespace Ink_Canvas.Helpers try { PresentationClose?.Invoke(pres); - LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event); // 重新启动连接检查 _connectionCheckTimer?.Start(); @@ -620,7 +644,6 @@ namespace Ink_Canvas.Helpers { UpdateCurrentPresentationInfo(); SlideShowBegin?.Invoke(wn); - LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event); } catch (Exception ex) { @@ -634,7 +657,6 @@ namespace Ink_Canvas.Helpers { UpdateCurrentPresentationInfo(); SlideShowNextSlide?.Invoke(wn); - LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace); } catch (Exception ex) { @@ -653,7 +675,6 @@ namespace Ink_Canvas.Helpers } SlideShowEnd?.Invoke(pres); - LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event); } catch (Exception ex) { @@ -825,14 +846,86 @@ namespace Ink_Canvas.Helpers } } + /// + /// 获取当前活跃的演示文稿(用于多窗口墨迹分离) + /// + public Presentation GetCurrentActivePresentation() + { + try + { + if (!IsConnected || PPTApplication == null) return null; + if (!Marshal.IsComObject(PPTApplication)) return null; + + // 如果在放映模式,获取放映窗口的演示文稿 + if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0) + { + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + return (Presentation)slideShowWindow.View.Slide.Parent; + } + } + + // 如果不在放映模式,获取活动窗口的演示文稿 + if (PPTApplication.ActiveWindow?.Presentation != null) + { + return PPTApplication.ActiveWindow.Presentation; + } + + // 如果没有活动窗口,返回当前演示文稿 + return CurrentPresentation; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning); + return CurrentPresentation; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error); + return CurrentPresentation; + } + } + + /// + /// 获取当前幻灯片编号 + /// public int GetCurrentSlideNumber() { try { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0; + if (!IsConnected || PPTApplication == null) return 0; if (!Marshal.IsComObject(PPTApplication)) return 0; - return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0; + // 如果在放映模式,获取放映窗口的当前幻灯片编号 + if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0) + { + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + return slideShowWindow.View.CurrentShowPosition; + } + } + + // 如果不在放映模式,获取活动窗口的当前幻灯片编号 + if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0) + { + return PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber; + } + + // 如果CurrentSlide存在,尝试获取其编号 + if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide)) + { + return CurrentSlide.SlideNumber; + } + + return 0; } catch (COMException comEx) { @@ -1042,7 +1135,7 @@ namespace Ink_Canvas.Helpers _wpsProcessCheckTimer.Dispose(); } - // 优化:增加检查间隔到2秒,减少性能开销 + // 增加检查间隔到2秒,减少性能开销 _wpsProcessCheckTimer = new Timer(2000); _wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed; _wpsProcessCheckTimer.Start(); @@ -1076,7 +1169,7 @@ namespace Ink_Canvas.Helpers } - // 检查前台WPS窗口是否存在(优化版) + // 检查前台WPS窗口是否存在 bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized(); if (isForegroundWpsWindowActive) @@ -1088,7 +1181,7 @@ namespace Ink_Canvas.Helpers return; } - // 优化:多重验证确保准确性 + // 多重验证确保准确性 if (!PerformMultipleWpsWindowChecks()) { LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace); @@ -1109,7 +1202,7 @@ namespace Ink_Canvas.Helpers } /// - /// 优化版的前台WPS窗口检测,减少性能开销 + /// 前台WPS窗口检测 /// private bool IsForegroundWpsWindowStillActiveOptimized() { @@ -1135,7 +1228,7 @@ namespace Ink_Canvas.Helpers } catch (Exception ex) { - LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"WPS窗口检测失败: {ex}", LogHelper.LogType.Error); return false; } } diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs index 1e29affc..fef41177 100644 --- a/Ink Canvas/Helpers/PPTUIManager.cs +++ b/Ink Canvas/Helpers/PPTUIManager.cs @@ -86,7 +86,6 @@ namespace Ink_Canvas.Helpers { _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace); } else { @@ -127,7 +126,6 @@ namespace Ink_Canvas.Helpers { _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace); } else { @@ -157,7 +155,6 @@ namespace Ink_Canvas.Helpers { // 如果不在放映模式,隐藏所有导航面板 HideAllNavigationPanels(); - LogHelper.WriteLogToFile("PPT放映状态变化:隐藏导航面板", LogHelper.LogType.Trace); } } catch (Exception ex) @@ -181,7 +178,7 @@ namespace Ink_Canvas.Helpers bool isInSlideShow = _mainWindow.PPTManager?.IsInSlideShow == true; int slidesCount = _mainWindow.PPTManager?.SlidesCount ?? 0; bool hasValidPageCount = slidesCount > 0; - + bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible && isInSlideShow && @@ -190,12 +187,9 @@ namespace Ink_Canvas.Helpers if (!shouldShowButtons) { HideAllNavigationPanels(); - LogHelper.WriteLogToFile($"隐藏PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}, 按钮设置: {ShowPPTButton}", LogHelper.LogType.Trace); return; } - LogHelper.WriteLogToFile($"显示PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}", LogHelper.LogType.Trace); - // 设置侧边按钮位置 _mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2); _mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2); diff --git a/Ink Canvas/Helpers/Plugins/EnhancedPluginBaseV2.cs b/Ink Canvas/Helpers/Plugins/EnhancedPluginBaseV2.cs index d4f2f96f..218068b6 100644 --- a/Ink Canvas/Helpers/Plugins/EnhancedPluginBaseV2.cs +++ b/Ink Canvas/Helpers/Plugins/EnhancedPluginBaseV2.cs @@ -238,4 +238,4 @@ namespace Ink_Canvas.Helpers.Plugins #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IActionService.cs b/Ink Canvas/Helpers/Plugins/IActionService.cs index 0101a605..99c61473 100644 --- a/Ink Canvas/Helpers/Plugins/IActionService.cs +++ b/Ink Canvas/Helpers/Plugins/IActionService.cs @@ -293,4 +293,4 @@ namespace Ink_Canvas.Helpers.Plugins #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IGetService.cs b/Ink Canvas/Helpers/Plugins/IGetService.cs index 70ff947e..ab48889a 100644 --- a/Ink Canvas/Helpers/Plugins/IGetService.cs +++ b/Ink Canvas/Helpers/Plugins/IGetService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; @@ -212,4 +211,4 @@ namespace Ink_Canvas.Helpers.Plugins #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IPluginService.cs b/Ink Canvas/Helpers/Plugins/IPluginService.cs index b1923121..9559a992 100644 --- a/Ink Canvas/Helpers/Plugins/IPluginService.cs +++ b/Ink Canvas/Helpers/Plugins/IPluginService.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - namespace Ink_Canvas.Helpers.Plugins { /// diff --git a/Ink Canvas/Helpers/Plugins/IWindowService.cs b/Ink Canvas/Helpers/Plugins/IWindowService.cs index 2685046c..eb6fb863 100644 --- a/Ink Canvas/Helpers/Plugins/IWindowService.cs +++ b/Ink Canvas/Helpers/Plugins/IWindowService.cs @@ -1,5 +1,3 @@ -using System; - namespace Ink_Canvas.Helpers.Plugins { /// @@ -151,4 +149,4 @@ namespace Ink_Canvas.Helpers.Plugins #endregion } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginManager.cs b/Ink Canvas/Helpers/Plugins/PluginManager.cs index 27b7742e..9e018183 100644 --- a/Ink Canvas/Helpers/Plugins/PluginManager.cs +++ b/Ink Canvas/Helpers/Plugins/PluginManager.cs @@ -20,8 +20,8 @@ namespace Ink_Canvas.Helpers.Plugins public class PluginManager { private static readonly string PluginsDirectory = Path.Combine(App.RootPath, "Plugins"); - private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "PluginConfig.json"); - private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "PluginConfig.json.bak"); + private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json"); + private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json.bak"); private static PluginManager _instance; private static SemaphoreSlim _configLock = new SemaphoreSlim(1, 1); @@ -79,8 +79,6 @@ namespace Ink_Canvas.Helpers.Plugins Directory.CreateDirectory(PluginsDirectory); } - // 加载插件配置 - LoadConfig(); // 初始化自动保存计时器(3秒) _autoSaveTimer = new Timer(3000); diff --git a/Ink Canvas/Helpers/WindowZOrderManager.cs b/Ink Canvas/Helpers/WindowZOrderManager.cs index 2ce292c8..3cf561c3 100644 --- a/Ink Canvas/Helpers/WindowZOrderManager.cs +++ b/Ink Canvas/Helpers/WindowZOrderManager.cs @@ -156,7 +156,7 @@ namespace Ink_Canvas.Helpers // 使用更直接的方法:先激活窗口,再置顶 window.Activate(); window.Focus(); - + // 设置窗口为置顶 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 2bf113a3..8d67e1d4 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -24,8 +24,12 @@ false False true - Release;x86 Debug;Debug - AnyCPU;x86 + Debug;Release;x86 Debug + + + embedded + bin\$(Configuration)\ + True embedded @@ -154,6 +158,8 @@ + + @@ -248,6 +254,7 @@ + diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index d918730f..187411e7 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -291,7 +291,7 @@ - + - + private void CheckEnableTwoFingerGestureBtnVisibility(bool isVisible) { - // 在PPT模式下根据设置决定是否显示手势按钮 - if (currentMode == 0 || BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + // 在PPT放映模式下根据设置决定是否显示手势按钮 + if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { - // 如果启用了PPT放映模式显示手势按钮,则显示手势按钮(在PPT模式下不依赖手势功能是否启用) - if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow && isVisible) + // 如果启用了PPT放映模式显示手势按钮,且当前处于批注模式,则显示手势按钮 + if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow && isVisible && inkCanvas.EditingMode == InkCanvasEditingMode.Ink) { EnableTwoFingerGestureBorder.Visibility = Visibility.Visible; } @@ -145,6 +150,13 @@ namespace Ink_Canvas return; } + // 在屏幕模式(非放映模式)下,不显示手势按钮 + if (currentMode == 0) + { + EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed; + return; + } + if (StackPanelCanvasControls.Visibility != Visibility.Visible || BorderFloatingBarMainControls.Visibility != Visibility.Visible) { @@ -358,11 +370,15 @@ namespace Ink_Canvas { BorderSettings.Visibility = Visibility.Collapsed; isOpeningOrHidingSettingsPane = false; - // 在设置面板完全关闭后,根据当前设置恢复无焦点模式 - if (Settings.Advanced.IsNoFocusMode) + // 在设置面板完全关闭后,根据情况恢复无焦点模式状态 + if (!userChangedNoFocusModeInSettings && wasNoFocusModeBeforeSettings) { + // 如果用户没有在设置中修改无焦点模式,则恢复之前的状态 + Settings.Advanced.IsNoFocusMode = true; + ToggleSwitchNoFocusMode.IsOn = true; // 同步更新设置面板中的开关状态 ApplyNoFocusMode(); } + // 如果用户在设置中修改了无焦点模式,则保持用户的修改 }; BorderSettings.Visibility = Visibility.Visible; @@ -668,7 +684,7 @@ namespace Ink_Canvas HideSubPanelsImmediately(); // 只有在PPT放映模式下且页数有效时才显示翻页按钮 - if (StackPanelPPTControls.Visibility == Visibility.Visible && + if (StackPanelPPTControls.Visibility == Visibility.Visible && BtnPPTSlideShowEnd.Visibility == Visibility.Visible && PPTManager?.IsInSlideShow == true && PPTManager?.SlidesCount > 0) @@ -688,14 +704,12 @@ namespace Ink_Canvas RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - LogHelper.WriteLogToFile($"隐藏PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace); } - + // 使用PPT UI管理器来正确更新翻页按钮显示状态,确保遵循用户设置 if (_pptUIManager != null) { _pptUIManager.UpdateNavigationPanelsVisibility(); - LogHelper.WriteLogToFile($"使用PPT UI管理器更新翻页按钮显示状态", LogHelper.LogType.Trace); } if (Settings.Automation.IsAutoSaveStrokesAtClear && @@ -827,10 +841,10 @@ namespace Ink_Canvas if (sender == SymbolIconSelect && lastBorderMouseDownObject != SymbolIconSelect) return; BtnSelect_Click(null, null); - + // 更新模式缓存 UpdateCurrentToolMode("select"); - + HideSubPanels("select"); } @@ -967,7 +981,7 @@ namespace Ink_Canvas var randWindow = new RandWindow(Settings); randWindow.Show(); - + // 使用延迟确保窗口完全显示后再强制置顶 randWindow.Dispatcher.BeginInvoke(new Action(() => { @@ -976,10 +990,10 @@ namespace Ink_Canvas // 强制激活窗口 randWindow.Activate(); randWindow.Focus(); - + // 设置置顶 randWindow.Topmost = true; - + // 使用Win32 API强制置顶 var hwnd = new WindowInteropHelper(randWindow).Handle; if (hwnd != IntPtr.Zero) @@ -1080,20 +1094,37 @@ namespace Ink_Canvas AnimationsHelper.HideWithSlideAndFade(BoardBorderTools); AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel); - // 检查是否启用了直接调用ClassIsland点名功能 + // 检查是否启用了外部点名功能 if (Settings.RandSettings.DirectCallCiRand) { try { + string protocol = ""; + switch (Settings.RandSettings.ExternalCallerType) + { + case 0: // ClassIsland点名 + protocol = "classisland://plugins/IslandCaller/Simple/1"; + break; + case 1: // SecRandom点名 + protocol = "secrandom://direct_extraction"; + break; + case 2: // NamePicker点名 + protocol = "namepicker://"; + break; + default: + protocol = "classisland://plugins/IslandCaller/Simple/1"; + break; + } + Process.Start(new ProcessStartInfo { - FileName = "classisland://plugins/IslandCaller/Simple/1", + FileName = protocol, UseShellExecute = true }); } catch (Exception ex) { - MessageBox.Show("无法调用ClassIsland点名:" + ex.Message); + MessageBox.Show("无法调用外部点名:" + ex.Message); // 调用失败时回退到默认的随机点名窗口 new RandWindow(Settings, true).ShowDialog(); @@ -1404,19 +1435,19 @@ namespace Ink_Canvas // 计算浮动栏位置,考虑快捷调色盘的显示状态 // 使用更可靠的方法获取浮动栏宽度 double baseWidth = ViewboxFloatingBar.ActualWidth; - + // 如果ActualWidth为0,尝试使用DesiredSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.DesiredSize.Width; } - + // 如果仍然为0,使用RenderSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.RenderSize.Width; } - + // 如果所有方法都失败,使用一个基于内容的估算值 if (baseWidth <= 0) { @@ -1424,9 +1455,9 @@ namespace Ink_Canvas baseWidth = 200; // 最小宽度 LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}"); } - + double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX; - + // 如果快捷调色盘显示,确保有足够空间 if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) || @@ -1549,19 +1580,19 @@ namespace Ink_Canvas // 计算浮动栏位置,考虑快捷调色盘的显示状态 // 使用更可靠的方法获取浮动栏宽度 double baseWidth = ViewboxFloatingBar.ActualWidth; - + // 如果ActualWidth为0,尝试使用DesiredSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.DesiredSize.Width; } - + // 如果仍然为0,使用RenderSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.RenderSize.Width; } - + // 如果所有方法都失败,使用一个基于内容的估算值 if (baseWidth <= 0) { @@ -1569,9 +1600,9 @@ namespace Ink_Canvas baseWidth = 200; // 最小宽度 LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}"); } - + double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX; - + // 如果快捷调色盘显示,确保有足够空间 if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) || @@ -1655,19 +1686,19 @@ namespace Ink_Canvas // 计算浮动栏位置,考虑快捷调色盘的显示状态 // 使用更可靠的方法获取浮动栏宽度 double baseWidth = ViewboxFloatingBar.ActualWidth; - + // 如果ActualWidth为0,尝试使用DesiredSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.DesiredSize.Width; } - + // 如果仍然为0,使用RenderSize if (baseWidth <= 0) { baseWidth = ViewboxFloatingBar.RenderSize.Width; } - + // 如果所有方法都失败,使用一个基于内容的估算值 if (baseWidth <= 0) { @@ -1675,9 +1706,9 @@ namespace Ink_Canvas baseWidth = 200; // 最小宽度 LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}"); } - + double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX; - + // 如果快捷调色盘显示,确保有足够空间 if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) || @@ -1735,7 +1766,7 @@ namespace Ink_Canvas // 使用集中化的工具模式切换方法,确保快捷键状态正确更新 // 鼠标模式下应该禁用快捷键以放行键盘操作 SetCurrentToolMode(InkCanvasEditingMode.None); - + // 更新模式缓存,确保后续的模式检测正确 UpdateCurrentToolMode("cursor"); @@ -1844,6 +1875,13 @@ namespace Ink_Canvas ((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent); if (sender == Pen_Icon && lastBorderMouseDownObject != Pen_Icon) return; + // 如果当前有选中的图片元素,先取消选中 + if (currentSelectedElement != null) + { + UnselectElement(currentSelectedElement); + currentSelectedElement = null; + } + // 禁用高级橡皮擦系统 DisableAdvancedEraserSystem(); @@ -1867,7 +1905,7 @@ namespace Ink_Canvas { // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.Ink); - + // 更新模式缓存 UpdateCurrentToolMode("pen"); @@ -1904,7 +1942,7 @@ namespace Ink_Canvas CheckEnableTwoFingerGestureBtnVisibility(true); // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.Ink); - + // 更新模式缓存 UpdateCurrentToolMode("pen"); @@ -2029,7 +2067,7 @@ namespace Ink_Canvas } // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.Ink); - + // 更新模式缓存 UpdateCurrentToolMode("pen"); @@ -2093,10 +2131,10 @@ namespace Ink_Canvas // 使用新的高级橡皮擦系统 // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.EraseByPoint); - + // 更新模式缓存 UpdateCurrentToolMode("eraser"); - + ApplyAdvancedEraserShape(); // 使用新的橡皮擦形状应用方法 SetCursorBasedOnEditingMode(inkCanvas); HideSubPanels("eraser"); // 高亮橡皮按钮 @@ -2137,10 +2175,10 @@ namespace Ink_Canvas // 使用新的高级橡皮擦系统 // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.EraseByPoint); - + // 更新模式缓存 UpdateCurrentToolMode("eraser"); - + ApplyAdvancedEraserShape(); // 使用新的橡皮擦形状应用方法 SetCursorBasedOnEditingMode(inkCanvas); HideSubPanels("eraser"); // 高亮橡皮按钮 @@ -2179,10 +2217,10 @@ namespace Ink_Canvas inkCanvas.EraserShape = new EllipseStylusShape(5, 5); // 使用集中化的工具模式切换方法 SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke); - + // 更新模式缓存 UpdateCurrentToolMode("eraserByStrokes"); - + drawingShapeMode = 0; // 修复:切换到线擦时,保存当前的笔类型状态,而不是强制重置 @@ -2599,6 +2637,7 @@ namespace Ink_Canvas { // 临时禁用无焦点模式以避免下拉选项被遮挡 wasNoFocusModeBeforeSettings = Settings.Advanced.IsNoFocusMode; + userChangedNoFocusModeInSettings = false; // 重置用户修改标志 if (wasNoFocusModeBeforeSettings) { Settings.Advanced.IsNoFocusMode = false; @@ -3092,6 +3131,11 @@ namespace Ink_Canvas } timeMachine.CommitElementInsertHistory(image); + + // 插入图片后切换到选择模式并刷新浮动栏高光显示 + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); } } } @@ -3158,6 +3202,11 @@ namespace Ink_Canvas } timeMachine.CommitElementInsertHistory(image); + + // 插入图片后切换到选择模式并刷新浮动栏高光显示 + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); } } } @@ -3224,6 +3273,11 @@ namespace Ink_Canvas } timeMachine.CommitElementInsertHistory(image); + + // 插入图片后切换到选择模式并刷新浮动栏高光显示 + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); } } } @@ -3510,7 +3564,6 @@ namespace Ink_Canvas private void UpdateCurrentToolMode(string mode) { _currentToolMode = mode; - LogHelper.WriteLogToFile($"更新工具模式缓存: {mode}", LogHelper.LogType.Trace); } #endregion diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingWindowInterceptor.cs b/Ink Canvas/MainWindow_cs/MW_FloatingWindowInterceptor.cs new file mode 100644 index 00000000..5d3ae88f --- /dev/null +++ b/Ink Canvas/MainWindow_cs/MW_FloatingWindowInterceptor.cs @@ -0,0 +1,328 @@ +using Ink_Canvas.Helpers; +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; + +namespace Ink_Canvas +{ + public partial class MainWindow : Window + { + #region 悬浮窗拦截功能 + + /// + /// 初始化悬浮窗拦截管理器 + /// + private void InitializeFloatingWindowInterceptor() + { + try + { + _floatingWindowInterceptorManager = new FloatingWindowInterceptorManager(); + + // 订阅事件 + _floatingWindowInterceptorManager.WindowIntercepted += OnFloatingWindowIntercepted; + _floatingWindowInterceptorManager.WindowRestored += OnFloatingWindowRestored; + + // 初始化拦截器 + _floatingWindowInterceptorManager.Initialize(Settings.Automation.FloatingWindowInterceptor); + + // 加载UI状态 + LoadFloatingWindowInterceptorUI(); + + LogHelper.WriteLogToFile("悬浮窗拦截管理器初始化完成", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化悬浮窗拦截管理器失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 加载悬浮窗拦截UI状态 + /// + private void LoadFloatingWindowInterceptorUI() + { + try + { + if (!isLoaded) return; + + // 设置主开关状态 + ToggleSwitchFloatingWindowInterceptorEnabled.IsOn = Settings.Automation.FloatingWindowInterceptor.IsEnabled; + + // 设置各个拦截规则的状态 + foreach (var kvp in Settings.Automation.FloatingWindowInterceptor.InterceptRules) + { + var toggleName = $"ToggleSwitch{kvp.Key}"; + var toggle = FindName(toggleName) as ToggleSwitch; + if (toggle != null) + { + toggle.IsOn = kvp.Value; + } + } + + // 更新UI可见性 + UpdateFloatingWindowInterceptorUI(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载悬浮窗拦截UI状态失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 更新悬浮窗拦截UI + /// + private void UpdateFloatingWindowInterceptorUI() + { + try + { + var isEnabled = Settings.Automation.FloatingWindowInterceptor.IsEnabled; + FloatingWindowInterceptorGrid.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed; + + // 计算启用的规则数量 + var enabledRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Where(kvp => kvp.Value).Count(); + var totalRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Count; + + // 更新状态文本 + if (_floatingWindowInterceptorManager != null) + { + var stats = _floatingWindowInterceptorManager.GetStatistics(); + TextBlockFloatingWindowInterceptorStatus.Text = stats.IsRunning + ? $"拦截器运行中 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则" + : $"拦截器未启动 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则"; + } + else + { + TextBlockFloatingWindowInterceptorStatus.Text = $"拦截器未初始化 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则"; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新悬浮窗拦截UI失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口被拦截事件处理 + /// + private void OnFloatingWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e) + { + try + { + // 在UI线程中更新状态 + Dispatcher.BeginInvoke(new Action(() => + { + UpdateFloatingWindowInterceptorUI(); + })); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口被恢复事件处理 + /// + private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e) + { + try + { + // 在UI线程中更新状态 + Dispatcher.BeginInvoke(new Action(() => + { + UpdateFloatingWindowInterceptorUI(); + })); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion + + #region 悬浮窗拦截事件处理 + + /// + /// 主开关切换事件 + /// + private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + + try + { + Settings.Automation.FloatingWindowInterceptor.IsEnabled = ToggleSwitchFloatingWindowInterceptorEnabled.IsOn; + + if (_floatingWindowInterceptorManager != null) + { + if (Settings.Automation.FloatingWindowInterceptor.IsEnabled) + { + _floatingWindowInterceptorManager.Start(); + } + else + { + _floatingWindowInterceptorManager.Stop(); + } + } + + UpdateFloatingWindowInterceptorUI(); + SaveSettingsToFile(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换悬浮窗拦截主开关失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 希沃白板3拦截开关 + /// + private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, ToggleSwitchSeewoWhiteboard3Floating.IsOn); + } + + /// + /// 希沃白板5拦截开关 + /// + private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, ToggleSwitchSeewoWhiteboard5Floating.IsOn); + } + + /// + /// 希沃白板5C拦截开关 + /// + private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, ToggleSwitchSeewoWhiteboard5CFloating.IsOn); + } + + /// + /// 希沃品课侧栏拦截开关 + /// + private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, ToggleSwitchSeewoPincoSideBarFloating.IsOn); + } + + /// + /// 希沃品课画笔拦截开关 + /// + private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, ToggleSwitchSeewoPincoDrawingFloating.IsOn); + } + + /// + /// 希沃PPT小工具拦截开关 + /// + private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, ToggleSwitchSeewoPPTFloating.IsOn); + } + + /// + /// AiClass拦截开关 + /// + private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, ToggleSwitchAiClassFloating.IsOn); + } + + /// + /// 鸿合屏幕书写拦截开关 + /// + private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, ToggleSwitchHiteAnnotationFloating.IsOn); + } + + /// + /// 畅言智慧课堂拦截开关 + /// + private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, ToggleSwitchChangYanFloating.IsOn); + } + + /// + /// 畅言PPT拦截开关 + /// + private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, ToggleSwitchChangYanPptFloating.IsOn); + } + + /// + /// 天喻教育云拦截开关 + /// + private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, ToggleSwitchIntelligentClassFloating.IsOn); + } + + /// + /// 希沃桌面画笔拦截开关 + /// + private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, ToggleSwitchSeewoDesktopAnnotationFloating.IsOn); + } + + /// + /// 希沃桌面侧栏拦截开关 + /// + private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, ToggleSwitchSeewoDesktopSideBarFloating.IsOn); + } + + /// + /// 设置拦截规则 + /// + private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled) + { + try + { + if (_floatingWindowInterceptorManager != null) + { + _floatingWindowInterceptorManager.SetInterceptRule(type, enabled); + } + + // 更新设置 + var ruleName = type.ToString(); + if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(ruleName)) + { + Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled; + } + + // 更新UI显示 + UpdateFloatingWindowInterceptorUI(); + + SaveSettingsToFile(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs index 5a880df1..0d585128 100644 --- a/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs +++ b/Ink Canvas/MainWindow_cs/MW_Hotkeys.cs @@ -8,8 +8,8 @@ namespace Ink_Canvas private void Window_MouseWheel(object sender, MouseWheelEventArgs e) { // 只有在PPT放映模式下才响应鼠标滚轮翻页 - if (StackPanelPPTControls.Visibility != Visibility.Visible || - currentMode != 0 || + if (StackPanelPPTControls.Visibility != Visibility.Visible || + currentMode != 0 || BtnPPTSlideShowEnd.Visibility != Visibility.Visible || PPTManager?.IsInSlideShow != true) return; @@ -29,8 +29,8 @@ namespace Ink_Canvas private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e) { // 只有在PPT放映模式下才响应键盘翻页快捷键 - if (StackPanelPPTControls.Visibility != Visibility.Visible || - currentMode != 0 || + if (StackPanelPPTControls.Visibility != Visibility.Visible || + currentMode != 0 || BtnPPTSlideShowEnd.Visibility != Visibility.Visible || PPTManager?.IsInSlideShow != true) return; diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs index 9e7c89d5..9814950c 100644 --- a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs +++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs @@ -27,11 +27,15 @@ namespace Ink_Canvas { public Rectangle Area; public List Path; + public Bitmap CameraImage; + public BitmapSource CameraBitmapSource; - public ScreenshotResult(Rectangle area, List path = null) + public ScreenshotResult(Rectangle area, List path = null, Bitmap cameraImage = null, BitmapSource cameraBitmapSource = null) { Area = area; Path = path; + CameraImage = cameraImage; + CameraBitmapSource = cameraBitmapSource; } } @@ -55,34 +59,48 @@ namespace Ink_Canvas // 恢复窗口显示 Visibility = originalVisibility; - if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0) + if (screenshotResult.HasValue) { - // 截取选定区域 - using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area)) + // 检查是否是摄像头截图 + if (screenshotResult.Value.CameraBitmapSource != null) { - if (originalBitmap != null) + // 摄像头截图(使用BitmapSource) + await InsertBitmapSourceToCanvas(screenshotResult.Value.CameraBitmapSource); + } + else if (screenshotResult.Value.CameraImage != null) + { + // 摄像头截图(使用Bitmap) + await InsertScreenshotToCanvas(screenshotResult.Value.CameraImage); + } + else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0) + { + // 屏幕截图 + using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area)) { - Bitmap finalBitmap = originalBitmap; - bool needDisposeFinalBitmap = false; - - try + if (originalBitmap != null) { - // 如果有路径信息,应用形状遮罩 - if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) + Bitmap finalBitmap = originalBitmap; + bool needDisposeFinalBitmap = false; + + try { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); - needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + // 如果有路径信息,应用形状遮罩 + if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) + { + finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); + needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + } + + // 将截图转换为WPF Image并插入到画布 + await InsertScreenshotToCanvas(finalBitmap); } - - // 将截图转换为WPF Image并插入到画布 - await InsertScreenshotToCanvas(finalBitmap); - } - finally - { - // 如果创建了新的位图,需要释放它 - if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + finally { - finalBitmap.Dispose(); + // 如果创建了新的位图,需要释放它 + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } } } } @@ -100,6 +118,46 @@ namespace Ink_Canvas } } + // 直接全屏截图并插入到画布 + private async Task CaptureFullScreenAndInsert() + { + try + { + // 隐藏主窗口以避免截图包含窗口本身 + var originalVisibility = Visibility; + Visibility = Visibility.Hidden; + + // 等待窗口隐藏 + await Task.Delay(200); + + // 获取虚拟屏幕边界 + var virtualScreen = SystemInformation.VirtualScreen; + var fullScreenArea = new Rectangle(virtualScreen.X, virtualScreen.Y, virtualScreen.Width, virtualScreen.Height); + + // 截取全屏 + using (var fullScreenBitmap = CaptureScreenArea(fullScreenArea)) + { + if (fullScreenBitmap != null) + { + // 将截图转换为WPF Image并插入到画布 + await InsertScreenshotToCanvas(fullScreenBitmap); + } + else + { + ShowNotification("全屏截图失败"); + } + } + + // 恢复窗口显示 + Visibility = originalVisibility; + } + catch (Exception ex) + { + ShowNotification($"全屏截图失败: {ex.Message}"); + Visibility = Visibility.Visible; + } + } + // 显示截图区域选择器 private async Task ShowScreenshotSelector() { @@ -112,10 +170,31 @@ namespace Ink_Canvas var selectorWindow = new ScreenshotSelectorWindow(); if (selectorWindow.ShowDialog() == true) { - result = new ScreenshotResult( - selectorWindow.SelectedArea.Value, - selectorWindow.SelectedPath - ); + // 检查是否是摄像头截图 + if (selectorWindow.CameraBitmapSource != null) + { + result = new ScreenshotResult( + Rectangle.Empty, // 摄像头截图不需要区域 + null, // 摄像头截图不需要路径 + null, // 不再使用Bitmap + selectorWindow.CameraBitmapSource // 摄像头BitmapSource + ); + } + else if (selectorWindow.CameraImage != null) + { + result = new ScreenshotResult( + Rectangle.Empty, // 摄像头截图不需要区域 + null, // 摄像头截图不需要路径 + selectorWindow.CameraImage // 摄像头图像 + ); + } + else + { + result = new ScreenshotResult( + selectorWindow.SelectedArea.Value, + selectorWindow.SelectedPath + ); + } } }); } @@ -173,8 +252,21 @@ namespace Ink_Canvas { try { + // 验证位图有效性 + if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0) + { + ShowNotification("无效的截图"); + return; + } + // 将Bitmap转换为WPF BitmapSource var bitmapSource = ConvertBitmapToBitmapSource(bitmap); + + if (bitmapSource == null) + { + ShowNotification("转换截图失败"); + return; + } // 创建WPF Image控件 var image = new Image @@ -216,6 +308,11 @@ namespace Ink_Canvas // 提交历史记录 timeMachine.CommitElementInsertHistory(image); + // 插入图片后切换到选择模式并刷新浮动栏高光显示 + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); + ShowNotification("截图已插入到画布"); } catch (Exception ex) @@ -223,6 +320,69 @@ namespace Ink_Canvas ShowNotification($"插入截图失败: {ex.Message}"); LogHelper.WriteLogToFile($"插入截图失败: {ex.Message}", LogHelper.LogType.Error); } + finally + { + bitmap?.Dispose(); + } + } + + // 将BitmapSource插入到画布(用于摄像头截图) + private async Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource) + { + try + { + // 创建WPF Image控件 + var image = new Image + { + Source = bitmapSource, + Stretch = Stretch.Uniform + }; + RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); + + // 生成唯一名称 + string timestamp = "camera_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); + image.Name = timestamp; + + // 初始化TransformGroup + InitializeScreenshotTransform(image); + + // 设置截图属性,避免被InkCanvas选择系统处理 + image.IsHitTestVisible = true; + image.Focusable = false; + + // 初始化InkCanvas选择设置 + InitializeInkCanvasSelectionSettings(); + + // 等待图片加载完成后再进行居中处理 + image.Loaded += (sender, e) => + { + // 确保在UI线程中执行 + Dispatcher.BeginInvoke(new Action(() => + { + CenterAndScaleScreenshot(image); + // 绑定事件处理器 + BindScreenshotEvents(image); + }), DispatcherPriority.Loaded); + }; + + // 添加到画布 + inkCanvas.Children.Add(image); + + // 提交历史记录 + timeMachine.CommitElementInsertHistory(image); + + // 插入图片后切换到选择模式并刷新浮动栏高光显示 + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); + + ShowNotification("摄像头截图已插入到画布"); + } + catch (Exception ex) + { + ShowNotification($"插入摄像头截图失败: {ex.Message}"); + LogHelper.WriteLogToFile($"插入摄像头截图失败: {ex.Message}", LogHelper.LogType.Error); + } } // 初始化截图的TransformGroup @@ -433,24 +593,169 @@ namespace Ink_Canvas { try { - using (var memory = new MemoryStream()) + // 验证位图有效性 + 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 { - bitmap.Save(memory, ImageFormat.Png); - memory.Position = 0; + // 根据像素格式选择合适的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 bitmapImage = new BitmapImage(); - bitmapImage.BeginInit(); - bitmapImage.CacheOption = BitmapCacheOption.OnLoad; - bitmapImage.StreamSource = memory; - bitmapImage.EndInit(); - bitmapImage.Freeze(); + var bitmapSource = BitmapSource.Create( + bitmapData.Width, + bitmapData.Height, + bitmap.HorizontalResolution, + bitmap.VerticalResolution, + wpfPixelFormat, + null, + bitmapData.Scan0, + bitmapData.Stride * bitmapData.Height, + bitmapData.Stride); - return bitmapImage; + 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; + } + } + } + } + + // 备用的位图转换方法(使用内存流) + private BitmapSource ConvertBitmapToBitmapSourceFallback(Bitmap bitmap) + { + try + { + // 验证位图有效性 + if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0) + return null; + + // 创建一个新的位图,确保格式正确 + using (var convertedBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb)) + { + using (var graphics = Graphics.FromImage(convertedBitmap)) + { + 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; + } + } + + // 最简单的位图转换方法 + 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; } } diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs index 7a9c77c0..16ea1892 100644 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs @@ -3,7 +3,6 @@ using iNKORE.UI.WPF.Modern; using Microsoft.Office.Core; using Microsoft.Office.Interop.PowerPoint; using System; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -11,11 +10,12 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; using Application = System.Windows.Application; using File = System.IO.File; -using MessageBox = System.Windows.MessageBox; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs; using MouseEventArgs = System.Windows.Input.MouseEventArgs; @@ -88,12 +88,22 @@ namespace Ink_Canvas // PowerPoint应用程序守护相关字段 private DispatcherTimer _powerPointProcessMonitorTimer; - private const int ProcessMonitorInterval = 5000; // 应用程序监控间隔(毫秒) + private const int ProcessMonitorInterval = 1000; // 应用程序监控间隔(毫秒) + + // 上次播放位置相关字段 + private int _lastPlaybackPage = 0; + private bool _shouldNavigateToLastPage = false; + + // 页面切换防抖机制 + private DateTime _lastSlideSwitchTime = DateTime.MinValue; + private int _pendingSlideIndex = -1; + private System.Timers.Timer _slideSwitchDebounceTimer; + private const int SlideSwitchDebounceMs = 150; // 防抖延迟150毫秒 #endregion #region PPT Managers private PPTManager _pptManager; - private PPTInkManager _pptInkManager; + private MultiPPTInkManager _multiPPTInkManager; private PPTUIManager _pptUIManager; /// @@ -123,10 +133,11 @@ namespace Ink_Canvas _pptManager.PresentationClose += OnPPTPresentationClose; _pptManager.SlideShowStateChanged += OnPPTSlideShowStateChanged; - // 初始化墨迹管理器 - _pptInkManager = new PPTInkManager(); - _pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint; - _pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation; + // 初始化多PPT墨迹管理器 + _multiPPTInkManager = new MultiPPTInkManager(); + _multiPPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint; + _multiPPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation; + _multiPPTInkManager.PPTManager = _pptManager; // 初始化UI管理器 _pptUIManager = new PPTUIManager(this); @@ -230,12 +241,12 @@ namespace Ink_Canvas // 创建新的PowerPoint应用程序实例 pptApplication = new Microsoft.Office.Interop.PowerPoint.Application(); - + // 设置为不可见,作为后台进程 - pptApplication.Visible = Microsoft.Office.Core.MsoTriState.msoFalse; - + pptApplication.Visible = MsoTriState.msoFalse; + // 设置应用程序属性 - pptApplication.WindowState = Microsoft.Office.Interop.PowerPoint.PpWindowState.ppWindowMinimized; + pptApplication.WindowState = PpWindowState.ppWindowMinimized; // 直接设置PPTManager的PPTApplication属性,绕过COM注册问题 Task.Delay(1000).ContinueWith(_ => @@ -278,9 +289,9 @@ namespace Ink_Canvas // 使用反射调用PPTManager的ConnectToPPT方法 var pptManagerType = _pptManager.GetType(); - var connectMethod = pptManagerType.GetMethod("ConnectToPPT", + var connectMethod = pptManagerType.GetMethod("ConnectToPPT", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - + if (connectMethod != null) { connectMethod.Invoke(_pptManager, new object[] { app }); @@ -289,9 +300,9 @@ namespace Ink_Canvas else { // 如果无法通过反射调用,尝试直接设置属性 - var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication", + var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); - + if (pptApplicationProperty != null && pptApplicationProperty.CanWrite) { pptApplicationProperty.SetValue(_pptManager, app); @@ -318,7 +329,7 @@ namespace Ink_Canvas { if (pptApplication == null) return false; if (!Marshal.IsComObject(pptApplication)) return false; - + // 尝试访问一个简单的属性来验证连接是否有效 var _ = pptApplication.Name; return true; @@ -363,12 +374,12 @@ namespace Ink_Canvas // 退出PowerPoint应用程序 pptApplication.Quit(); - + // 释放COM对象 Marshal.ReleaseComObject(pptApplication); pptApplication = null; } - + LogHelper.WriteLogToFile("PowerPoint应用程序已关闭", LogHelper.LogType.Event); } catch (Exception ex) @@ -409,11 +420,11 @@ namespace Ink_Canvas try { _pptManager?.Dispose(); - _pptInkManager?.Dispose(); + _multiPPTInkManager?.Dispose(); _longPressTimer?.Stop(); _longPressTimer = null; _pptManager = null; - _pptInkManager = null; + _multiPPTInkManager = null; _pptUIManager = null; // 清理PowerPoint进程守护 @@ -448,6 +459,8 @@ namespace Ink_Canvas if (!Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) return; _isLongPressNext = isNext; + // 重置定时器间隔为初始延迟时间,确保每次长按检测都从正确的延迟开始 + _longPressTimer.Interval = TimeSpan.FromMilliseconds(LongPressDelay); _longPressTimer?.Start(); } @@ -497,7 +510,7 @@ namespace Ink_Canvas { LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event); // 清理墨迹管理器 - _pptInkManager?.ClearAllStrokes(); + _multiPPTInkManager?.ClearAllStrokes(); } }); } @@ -522,8 +535,8 @@ namespace Ink_Canvas TimeMachineHistories[0] = null; } - // 初始化墨迹管理器 - _pptInkManager?.InitializePresentation(pres); + // 初始化多PPT墨迹管理器 + _multiPPTInkManager?.InitializePresentation(pres); // 处理跳转到首页或上次播放页的逻辑 HandlePresentationOpenNavigation(pres); @@ -558,17 +571,24 @@ namespace Ink_Canvas Application.Current.Dispatcher.InvokeAsync(() => { // 保存所有墨迹 - _pptInkManager?.SaveAllStrokesToFile(pres); + _multiPPTInkManager?.SaveAllStrokesToFile(pres); - // 清理墨迹管理器 - _pptInkManager?.ClearAllStrokes(); + // 移除演示文稿管理器 + _multiPPTInkManager?.RemovePresentation(pres); _pptUIManager?.UpdateConnectionStatus(false); }); } - catch (Exception ex) + catch (COMException comEx) + { + // COM对象已失效,这是正常情况,完全静默处理 + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010) + { + } + } + catch (Exception) { - LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error); } } @@ -583,7 +603,6 @@ namespace Ink_Canvas if (!isInSlideShow) { - LogHelper.WriteLogToFile("PPT放映状态变化:退出放映模式", LogHelper.LogType.Trace); } // 检查主窗口可见性(用于仅PPT模式) @@ -612,11 +631,23 @@ namespace Ink_Canvas await Application.Current.Dispatcher.InvokeAsync(() => { - // 处理跳转到首页 + // 获取当前活跃的演示文稿并切换到对应的墨迹管理器 + var activePresentation = _pptManager?.GetCurrentActivePresentation(); + if (activePresentation != null) + { + _multiPPTInkManager?.SwitchToPresentation(activePresentation); + } + + // 处理跳转到首页或上次播放位置 if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) { _pptManager?.TryNavigateToSlide(1); } + else if (_shouldNavigateToLastPage && _lastPlaybackPage > 0) + { + _pptManager?.TryNavigateToSlide(_lastPlaybackPage); + _shouldNavigateToLastPage = false; // 重置标志位 + } // 更新UI状态 var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; @@ -649,7 +680,7 @@ namespace Ink_Canvas // 在PPT模式下根据设置决定是否隐藏手势面板和手势按钮 AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder); AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder); - + // 根据设置决定是否在PPT放映模式下显示手势按钮 if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow) { @@ -667,7 +698,24 @@ namespace Ink_Canvas if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow && !Settings.Automation.IsAutoFoldInPPTSlideShow) + { BtnColorRed_Click(null, null); + Dispatcher.BeginInvoke(new Action(() => + { + try + { + if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink) + { + UpdateCurrentToolMode("pen"); + SetFloatingBarHighlightPosition("pen"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PPT进入批注模式后同步浮动栏高光状态失败: {ex.Message}", LogHelper.LogType.Error); + } + }), DispatcherPriority.Loaded); + } isEnteredSlideShowEndEvent = false; @@ -699,16 +747,19 @@ namespace Ink_Canvas { Application.Current.Dispatcher.InvokeAsync(() => { + // 获取当前活跃的演示文稿并确保切换到正确的墨迹管理器 + var activePresentation = _pptManager?.GetCurrentActivePresentation(); + if (activePresentation != null) + { + _multiPPTInkManager?.SwitchToPresentation(activePresentation); + } + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; var totalSlides = _pptManager?.SlidesCount ?? 0; - // 保存上一页墨迹并加载当前页墨迹 - SwitchSlideInk(currentSlide); + // 使用防抖机制处理页面切换 + HandleSlideSwitchWithDebounce(currentSlide, totalSlides); - // 更新UI - _pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides); - - LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace); }); } catch (Exception ex) @@ -735,7 +786,7 @@ namespace Ink_Canvas isEnteredSlideShowEndEvent = true; // 保存所有墨迹 - _pptInkManager?.SaveAllStrokesToFile(pres); + _multiPPTInkManager?.SaveAllStrokesToFile(pres); await Application.Current.Dispatcher.InvokeAsync(() => { @@ -767,6 +818,9 @@ namespace Ink_Canvas // 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上) TimeMachineHistories[0] = null; + // 重置墨迹管理器的锁定状态,防止下次放映时墨迹显示错误 + ResetInkManagerLockState(); + // 退出PPT模式时恢复手势面板和手势按钮的显示状态 if (Settings.Gesture.IsEnableTwoFingerGesture && ToggleSwitchEnableMultiTouchMode.IsOn) { @@ -791,6 +845,7 @@ namespace Ink_Canvas if (GridTransparencyFakeBackground.Background != Brushes.Transparent) BtnHideInkCanvas_Click(BtnHideInkCanvas, null); + SetCurrentToolMode(InkCanvasEditingMode.None); } catch (Exception ex) { @@ -798,7 +853,7 @@ namespace Ink_Canvas } }); - await Task.Delay(150); + await Task.Delay(100); await Application.Current.Dispatcher.InvokeAsync(() => { // 强制重新计算浮动栏位置,确保在退出PPT模式后正确复位 @@ -850,9 +905,10 @@ namespace Ink_Canvas if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0) { + _lastPlaybackPage = page; new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => { - _pptManager?.TryNavigateToSlide(page); + _shouldNavigateToLastPage = true; }).ShowDialog(); } } @@ -971,7 +1027,7 @@ namespace Ink_Canvas { try { - var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex); + var strokes = _multiPPTInkManager?.LoadSlideStrokes(slideIndex); if (strokes != null) { inkCanvas.Strokes.Clear(); @@ -984,11 +1040,95 @@ namespace Ink_Canvas } } + /// + /// 重置墨迹管理器的锁定状态,防止墨迹显示错误 + /// + private void ResetInkManagerLockState() + { + try + { + // 获取当前活跃的演示文稿 + var activePresentation = _pptManager?.GetCurrentActivePresentation(); + if (activePresentation != null) + { + // 切换到对应的墨迹管理器 + _multiPPTInkManager?.SwitchToPresentation(activePresentation); + + // 重置锁定状态 + _multiPPTInkManager?.ResetCurrentPresentationLockState(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重置墨迹管理器锁定状态失败: {ex}", LogHelper.LogType.Error); + } + } + + /// + /// 使用防抖机制处理页面切换 + /// + private void HandleSlideSwitchWithDebounce(int currentSlide, int totalSlides) + { + try + { + var now = DateTime.Now; + + // 如果距离上次切换时间太短,使用防抖机制 + if (now - _lastSlideSwitchTime < TimeSpan.FromMilliseconds(SlideSwitchDebounceMs)) + { + _pendingSlideIndex = currentSlide; + + // 停止之前的定时器 + _slideSwitchDebounceTimer?.Stop(); + + // 创建新的定时器 + _slideSwitchDebounceTimer = new System.Timers.Timer(SlideSwitchDebounceMs); + _slideSwitchDebounceTimer.Elapsed += (sender, e) => + { + Application.Current.Dispatcher.Invoke(() => + { + if (_pendingSlideIndex > 0) + { + SwitchSlideInk(_pendingSlideIndex); + _pptUIManager?.UpdateCurrentSlideNumber(_pendingSlideIndex, totalSlides); + _pendingSlideIndex = -1; + } + }); + _slideSwitchDebounceTimer?.Stop(); + }; + _slideSwitchDebounceTimer.Start(); + } + else + { + // 直接处理页面切换 + SwitchSlideInk(currentSlide); + _pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides); + } + + _lastSlideSwitchTime = now; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理页面切换防抖失败: {ex}", LogHelper.LogType.Error); + } + } + private void SwitchSlideInk(int newSlideIndex) { try { - var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes); + // 获取当前页面索引 + var currentSlideIndex = _pptManager?.GetCurrentSlideNumber() ?? 0; + + // 如果有当前墨迹且不是第一次切换,先保存到当前页面 + if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex) + { + _multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes); + LogHelper.WriteLogToFile($"切换前保存第{currentSlideIndex}页墨迹,墨迹数量: {inkCanvas.Strokes.Count}", LogHelper.LogType.Trace); + } + + // 切换到新页面并加载墨迹 + var newStrokes = _multiPPTInkManager?.SwitchToSlide(newSlideIndex, null); if (newStrokes != null) { inkCanvas.Strokes.Clear(); @@ -996,7 +1136,7 @@ namespace Ink_Canvas } // 设置墨迹锁定 - _pptInkManager?.LockInkForSlide(newSlideIndex); + _multiPPTInkManager?.LockInkForSlide(newSlideIndex); } catch (Exception ex) { @@ -1065,24 +1205,23 @@ namespace Ink_Canvas private void ToggleSwitchPowerPointEnhancement_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; - + Settings.PowerPointSettings.EnablePowerPointEnhancement = ToggleSwitchPowerPointEnhancement.IsOn; - - // 与WPS支持互斥 + if (Settings.PowerPointSettings.EnablePowerPointEnhancement) { Settings.PowerPointSettings.IsSupportWPS = false; ToggleSwitchSupportWPS.IsOn = false; - + // 更新PPT管理器的WPS支持设置 if (_pptManager != null) { _pptManager.IsSupportWPS = false; } } - + SaveSettingsToFile(); - + // 启动或停止PowerPoint进程守护 if (Settings.PowerPointSettings.EnablePowerPointEnhancement) { @@ -1099,8 +1238,7 @@ namespace Ink_Canvas if (!isLoaded) return; Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn; - - // 与PowerPoint联动增强互斥 + if (Settings.PowerPointSettings.IsSupportWPS) { Settings.PowerPointSettings.EnablePowerPointEnhancement = false; @@ -1122,24 +1260,6 @@ namespace Ink_Canvas public static bool IsShowingRestoreHiddenSlidesWindow; private static bool IsShowingAutoplaySlidesWindow; - - - - - - - - - - - - - - - - - - private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e) { Application.Current.Dispatcher.Invoke(() => @@ -1150,7 +1270,7 @@ namespace Ink_Canvas var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; if (currentSlide > 0) { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); + _multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); } // 保存截图(如果启用) @@ -1165,7 +1285,6 @@ namespace Ink_Canvas if (_pptManager?.TryNavigatePrevious() == true) { // 翻页成功,等待事件处理墨迹切换 - LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace); } else { @@ -1191,7 +1310,7 @@ namespace Ink_Canvas var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; if (currentSlide > 0) { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); + _multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); } // 保存截图(如果启用) @@ -1206,7 +1325,6 @@ namespace Ink_Canvas if (_pptManager?.TryNavigateNext() == true) { // 翻页成功,等待事件处理墨迹切换 - LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace); } else { @@ -1352,7 +1470,7 @@ namespace Ink_Canvas { Application.Current.Dispatcher.Invoke(() => { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); + _multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); timeMachine.ClearStrokeHistory(); }); } @@ -1360,7 +1478,6 @@ namespace Ink_Canvas // 结束放映 if (_pptManager?.TryEndSlideShow() == true) { - LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event); } else { @@ -1376,6 +1493,8 @@ namespace Ink_Canvas } HideSubPanels("cursor"); + SetCurrentToolMode(InkCanvasEditingMode.None); + await Task.Delay(150); ViewboxFloatingBarMarginAnimation(100, true); } diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs index c949fdca..b0aae681 100644 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs @@ -78,7 +78,7 @@ namespace Ink_Canvas for (int i = 1; i <= totalSlides; i++) { - var slideStrokes = _pptInkManager?.LoadSlideStrokes(i); + var slideStrokes = _multiPPTInkManager?.LoadSlideStrokes(i); if (slideStrokes != null && slideStrokes.Count > 0) { allPageStrokes.Add(slideStrokes); @@ -528,7 +528,7 @@ namespace Ink_Canvas timeMachine.ClearStrokeHistory(); // 重置PPT墨迹存储 - _pptInkManager?.ClearAllStrokes(); + _multiPPTInkManager?.ClearAllStrokes(); // 读取所有页面的墨迹文件 var files = Directory.GetFiles(tempDir, "page_*.icstk"); @@ -542,7 +542,7 @@ namespace Ink_Canvas var strokes = new StrokeCollection(fs); if (strokes.Count > 0) { - _pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes); + _multiPPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes); } } } @@ -552,7 +552,7 @@ namespace Ink_Canvas if (_pptManager?.IsInSlideShow == true) { int currentSlide = _pptManager.GetCurrentSlideNumber(); - var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide); + var currentStrokes = _multiPPTInkManager?.LoadSlideStrokes(currentSlide); if (currentStrokes != null && currentStrokes.Count > 0) { inkCanvas.Strokes.Add(currentStrokes); diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs index e3ea3b83..71981cd6 100644 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs @@ -1,3 +1,4 @@ +using Ink_Canvas.Helpers; using iNKORE.UI.WPF.Modern.Controls; using System; using System.Collections.Generic; @@ -7,6 +8,7 @@ using System.Windows.Controls; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Shapes; using Point = System.Windows.Point; namespace Ink_Canvas @@ -254,18 +256,101 @@ namespace Ink_Canvas #endregion private bool isGridInkCanvasSelectionCoverMouseDown; + private bool isStrokeDragging = false; + private Point strokeDragStartPoint; private StrokeCollection StrokesSelectionClone = new StrokeCollection(); + // 选择框和选择点相关变量 + private bool isResizing = false; + private string currentResizeHandle = ""; + private Point resizeStartPoint; + private Rect originalSelectionBounds; + private void GridInkCanvasSelectionCover_MouseDown(object sender, MouseButtonEventArgs e) { isGridInkCanvasSelectionCoverMouseDown = true; + + // 检查是否有选中的墨迹 + if (inkCanvas.GetSelectedStrokes().Count > 0) + { + // 获取鼠标点击位置 + var clickPoint = e.GetPosition(inkCanvas); + var selectionBounds = inkCanvas.GetSelectionBounds(); + + // 检查点击位置是否在选择框边界内 + if (clickPoint.X >= selectionBounds.Left && + clickPoint.X <= selectionBounds.Right && + clickPoint.Y >= selectionBounds.Top && + clickPoint.Y <= selectionBounds.Bottom) + { + // 只有在选择框边界内才允许拖动 + isStrokeDragging = true; + strokeDragStartPoint = clickPoint; + GridInkCanvasSelectionCover.CaptureMouse(); + GridInkCanvasSelectionCover.Cursor = Cursors.SizeAll; + } + else + { + // 点击在选择框外,取消选择 + inkCanvas.Select(new StrokeCollection()); + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + } + } + } + + private void GridInkCanvasSelectionCover_MouseMove(object sender, MouseEventArgs e) + { + if (!isGridInkCanvasSelectionCoverMouseDown) return; + + // 如果正在拖动墨迹,执行拖动操作 + if (isStrokeDragging && GridInkCanvasSelectionCover.IsMouseCaptured) + { + var currentPoint = e.GetPosition(inkCanvas); + var delta = currentPoint - strokeDragStartPoint; + + // 创建变换矩阵 + var matrix = new Matrix(); + matrix.Translate(delta.X, delta.Y); + + // 对选中的墨迹应用变换 + var selectedStrokes = inkCanvas.GetSelectedStrokes(); + foreach (var stroke in selectedStrokes) + { + stroke.Transform(matrix, false); + } + + // 更新选中栏位置 + updateBorderStrokeSelectionControlLocation(); + + // 更新起始点 + strokeDragStartPoint = currentPoint; + } + else if (inkCanvas.GetSelectedStrokes().Count > 0) + { + // 当鼠标在选中区域移动时,更新墨迹选中栏位置 + updateBorderStrokeSelectionControlLocation(); + } } private void GridInkCanvasSelectionCover_MouseUp(object sender, MouseButtonEventArgs e) { if (!isGridInkCanvasSelectionCoverMouseDown) return; + + // 结束墨迹拖动 + if (isStrokeDragging) + { + isStrokeDragging = false; + GridInkCanvasSelectionCover.ReleaseMouseCapture(); + GridInkCanvasSelectionCover.Cursor = Cursors.Arrow; + } + isGridInkCanvasSelectionCoverMouseDown = false; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + + // 只有在没有选中墨迹时才隐藏选中栏 + if (inkCanvas.GetSelectedStrokes().Count == 0) + { + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + } } private void BtnSelect_Click(object sender, RoutedEventArgs e) @@ -306,7 +391,30 @@ namespace Ink_Canvas { if (isProgramChangeStrokeSelection) return; - // 检查是否有图片元素被选中 + // 优先检查墨迹选择状态 + if (inkCanvas.GetSelectedStrokes().Count > 0) + { + // 有墨迹被选中,清除图片选择状态 + if (currentSelectedElement != null) + { + currentSelectedElement = null; + // 隐藏图片选择工具栏 + if (BorderImageSelectionControl != null) + { + BorderImageSelectionControl.Visibility = Visibility.Collapsed; + } + } + + // 显示墨迹选择栏和选择框 + GridInkCanvasSelectionCover.Visibility = Visibility.Visible; + BorderStrokeSelectionClone.Background = Brushes.Transparent; + isStrokeSelectionCloneOn = false; + updateBorderStrokeSelectionControlLocation(); + UpdateSelectionDisplay(); + return; + } + + // 检查是否有图片元素被选中(通过InkCanvas的选中元素) var selectedElements = inkCanvas.GetSelectedElements(); bool hasImageElement = selectedElements.Any(element => element is Image); @@ -314,20 +422,21 @@ namespace Ink_Canvas if (hasImageElement) { GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + HideSelectionDisplay(); return; } - if (inkCanvas.GetSelectedStrokes().Count == 0) + // 检查是否有图片元素被选中(通过currentSelectedElement) + if (currentSelectedElement != null && currentSelectedElement is Image) { GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + HideSelectionDisplay(); + return; } - else - { - GridInkCanvasSelectionCover.Visibility = Visibility.Visible; - BorderStrokeSelectionClone.Background = Brushes.Transparent; - isStrokeSelectionCloneOn = false; - updateBorderStrokeSelectionControlLocation(); - } + + // 没有选中任何内容,隐藏选择框 + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + HideSelectionDisplay(); } @@ -336,7 +445,7 @@ namespace Ink_Canvas { var borderLeft = (inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Right - BorderStrokeSelectionControlWidth) / 2; - var borderTop = inkCanvas.GetSelectionBounds().Bottom + 1; + var borderTop = inkCanvas.GetSelectionBounds().Bottom + 10; // 在墨迹下方10像素处显示 if (borderLeft < 0) borderLeft = 0; if (borderTop < 0) borderTop = 0; if (Width - borderLeft < BorderStrokeSelectionControlWidth || double.IsNaN(borderLeft)) @@ -344,7 +453,14 @@ namespace Ink_Canvas if (Height - borderTop < BorderStrokeSelectionControlHeight || double.IsNaN(borderTop)) borderTop = Height - BorderStrokeSelectionControlHeight; - if (borderTop > 60) borderTop -= 60; + // 确保墨迹选中栏始终显示在墨迹下方 + // 如果选中栏会超出屏幕底部,则显示在墨迹上方 + if (borderTop + BorderStrokeSelectionControlHeight > Height) + { + borderTop = inkCanvas.GetSelectionBounds().Top - BorderStrokeSelectionControlHeight - 10; + if (borderTop < 0) borderTop = 10; // 如果上方也没有空间,则显示在顶部 + } + BorderStrokeSelectionControl.Margin = new Thickness(borderLeft, borderTop, 0, 0); } @@ -408,25 +524,19 @@ namespace Ink_Canvas strokes = StrokesSelectionClone; else if (Settings.Gesture.IsEnableTwoFingerRotationOnSelection) m.RotateAt(rotate, center.X, center.Y); // 旋转 + + // 应用变换到选中的墨迹 foreach (var stroke in strokes) { stroke.Transform(m, false); - - try - { - stroke.DrawingAttributes.Width *= md.Scale.X; - stroke.DrawingAttributes.Height *= md.Scale.Y; - } - catch - { - } } updateBorderStrokeSelectionControlLocation(); } } - catch + catch (Exception ex) { + LogHelper.WriteLogToFile($"墨迹ManipulationDelta错误: {ex.Message}", LogHelper.LogType.Error); } } @@ -438,6 +548,78 @@ namespace Ink_Canvas { } + private void GridInkCanvasSelectionCover_TouchMove(object sender, TouchEventArgs e) + { + // 处理触摸移动事件 - 用于拖动选中的墨迹 + if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1) + { + var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position; + + // 检查是否有有效的起始触摸点 + if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0)) + { + var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover; + + // 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动) + if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1) + { + // 创建变换矩阵 + var matrix = new Matrix(); + matrix.Translate(delta.X, delta.Y); + + // 对选中的墨迹应用变换 + var selectedStrokes = inkCanvas.GetSelectedStrokes(); + foreach (var stroke in selectedStrokes) + { + stroke.Transform(matrix, false); + } + + // 更新选中栏位置 + updateBorderStrokeSelectionControlLocation(); + + // 更新最后触摸点 + lastTouchPointOnGridInkCanvasCover = currentTouchPoint; + } + } + } + } + + private void GridInkCanvasSelectionCover_PreviewTouchMove(object sender, TouchEventArgs e) + { + // 预览触摸移动事件 - 用于更精确的触摸处理 + if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1) + { + var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position; + + // 检查是否有有效的起始触摸点 + if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0)) + { + var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover; + + // 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动) + if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1) + { + // 创建变换矩阵 + var matrix = new Matrix(); + matrix.Translate(delta.X, delta.Y); + + // 对选中的墨迹应用变换 + var selectedStrokes = inkCanvas.GetSelectedStrokes(); + foreach (var stroke in selectedStrokes) + { + stroke.Transform(matrix, false); + } + + // 更新选中栏位置 + updateBorderStrokeSelectionControlLocation(); + + // 更新最后触摸点 + lastTouchPointOnGridInkCanvasCover = currentTouchPoint; + } + } + } + } + private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0); private void GridInkCanvasSelectionCover_PreviewTouchDown(object sender, TouchEventArgs e) @@ -448,7 +630,32 @@ namespace Ink_Canvas { var touchPoint = e.GetTouchPoint(null); centerPoint = touchPoint.Position; - lastTouchPointOnGridInkCanvasCover = touchPoint.Position; + lastTouchPointOnGridInkCanvasCover = e.GetTouchPoint(inkCanvas).Position; + + // 检查是否有选中的墨迹 + if (inkCanvas.GetSelectedStrokes().Count > 0) + { + // 获取触摸点位置 + var touchPosition = e.GetTouchPoint(inkCanvas).Position; + var selectionBounds = inkCanvas.GetSelectionBounds(); + + // 检查触摸位置是否在选择框边界内 + if (touchPosition.X >= selectionBounds.Left && + touchPosition.X <= selectionBounds.Right && + touchPosition.Y >= selectionBounds.Top && + touchPosition.Y <= selectionBounds.Bottom) + { + // 只有在选择框边界内才允许拖动 + // 触摸拖动状态已通过TouchMove事件处理 + } + else + { + // 触摸在选择框外,取消选择 + inkCanvas.Select(new StrokeCollection()); + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + return; + } + } if (isStrokeSelectionCloneOn) { @@ -474,13 +681,32 @@ namespace Ink_Canvas { dec.Remove(e.TouchDevice.Id); if (dec.Count >= 1) return; + + // 重置触摸状态 + lastTouchPointOnGridInkCanvasCover = new Point(0, 0); isProgramChangeStrokeSelection = false; - if (lastTouchPointOnGridInkCanvasCover == e.GetTouchPoint(null).Position) + + // 检查是否有点击(没有移动) + var currentTouchPoint = e.GetTouchPoint(null).Position; + if (Math.Abs(currentTouchPoint.X - centerPoint.X) < 5 && Math.Abs(currentTouchPoint.Y - centerPoint.Y) < 5) { - if (!(lastTouchPointOnGridInkCanvasCover.X < inkCanvas.GetSelectionBounds().Left) && - !(lastTouchPointOnGridInkCanvasCover.Y < inkCanvas.GetSelectionBounds().Top) && - !(lastTouchPointOnGridInkCanvasCover.X > inkCanvas.GetSelectionBounds().Right) && - !(lastTouchPointOnGridInkCanvasCover.Y > inkCanvas.GetSelectionBounds().Bottom)) return; + // 点击在选择框内,保持选择状态 + if (inkCanvas.GetSelectedStrokes().Count > 0) + { + var selectionBounds = inkCanvas.GetSelectionBounds(); + if (currentTouchPoint.X >= selectionBounds.Left && + currentTouchPoint.X <= selectionBounds.Right && + currentTouchPoint.Y >= selectionBounds.Top && + currentTouchPoint.Y <= selectionBounds.Bottom) + { + // 点击在选择框内,保持选择 + GridInkCanvasSelectionCover.Visibility = Visibility.Visible; + StrokesSelectionClone = new StrokeCollection(); + return; + } + } + + // 点击在选择框外,取消选择 inkCanvas.Select(new StrokeCollection()); StrokesSelectionClone = new StrokeCollection(); } @@ -494,6 +720,7 @@ namespace Ink_Canvas GridInkCanvasSelectionCover.Visibility = Visibility.Visible; StrokesSelectionClone = new StrokeCollection(); } + } private void LassoSelect_Click(object sender, RoutedEventArgs e) @@ -558,7 +785,177 @@ namespace Ink_Canvas return new Rect(0, 0, 0, 0); } + + #endregion + + #region Selection Display and Resize Handles + + private void UpdateSelectionDisplay() + { + if (inkCanvas.GetSelectedStrokes().Count == 0) + { + HideSelectionDisplay(); + return; + } + + var selectionBounds = inkCanvas.GetSelectionBounds(); + + // 更新选择框 + SelectionRectangle.Visibility = Visibility.Visible; + SelectionRectangle.Margin = new Thickness(selectionBounds.Left, selectionBounds.Top, 0, 0); + SelectionRectangle.Width = selectionBounds.Width; + SelectionRectangle.Height = selectionBounds.Height; + + // 更新选择点位置 + UpdateSelectionHandles(selectionBounds); + SelectionHandlesCanvas.Visibility = Visibility.Visible; + } + + private void HideSelectionDisplay() + { + SelectionRectangle.Visibility = Visibility.Collapsed; + SelectionHandlesCanvas.Visibility = Visibility.Collapsed; + } + + private void UpdateSelectionHandles(Rect bounds) + { + // 四个角选择点 + TopLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top - 4, 0, 0); + TopRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top - 4, 0, 0); + BottomLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Bottom - 4, 0, 0); + BottomRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Bottom - 4, 0, 0); + + // 四个边选择点 + TopHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Top - 4, 0, 0); + BottomHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Bottom - 4, 0, 0); + LeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0); + RightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0); + } + + private void SelectionHandle_MouseDown(object sender, MouseButtonEventArgs e) + { + if (sender is Rectangle handle) + { + isResizing = true; + currentResizeHandle = handle.Name; + resizeStartPoint = e.GetPosition(inkCanvas); + originalSelectionBounds = inkCanvas.GetSelectionBounds(); + handle.CaptureMouse(); + e.Handled = true; + } + } + + private void SelectionHandle_MouseMove(object sender, MouseEventArgs e) + { + if (!isResizing || !(sender is Rectangle handle)) return; + + var currentPoint = e.GetPosition(inkCanvas); + var delta = new Point(currentPoint.X - resizeStartPoint.X, currentPoint.Y - resizeStartPoint.Y); + + var newBounds = CalculateNewBounds(originalSelectionBounds, delta, currentResizeHandle); + + // 应用新的边界到选中的墨迹 + ApplyBoundsToStrokes(newBounds); + + // 更新选择框显示 + UpdateSelectionDisplay(); + } + + private void SelectionHandle_MouseUp(object sender, MouseButtonEventArgs e) + { + if (sender is Rectangle handle) + { + isResizing = false; + currentResizeHandle = ""; + handle.ReleaseMouseCapture(); + e.Handled = true; + } + } + + private Rect CalculateNewBounds(Rect originalBounds, Point delta, string handleName) + { + var newBounds = originalBounds; + double newWidth = originalBounds.Width; + double newHeight = originalBounds.Height; + double newX = originalBounds.X; + double newY = originalBounds.Y; + + switch (handleName) + { + case "TopLeftHandle": + newX = originalBounds.X + delta.X; + newY = originalBounds.Y + delta.Y; + newWidth = originalBounds.Width - delta.X; + newHeight = originalBounds.Height - delta.Y; + break; + case "TopRightHandle": + newY = originalBounds.Y + delta.Y; + newWidth = originalBounds.Width + delta.X; + newHeight = originalBounds.Height - delta.Y; + break; + case "BottomLeftHandle": + newX = originalBounds.X + delta.X; + newWidth = originalBounds.Width - delta.X; + newHeight = originalBounds.Height + delta.Y; + break; + case "BottomRightHandle": + newWidth = originalBounds.Width + delta.X; + newHeight = originalBounds.Height + delta.Y; + break; + case "TopHandle": + newY = originalBounds.Y + delta.Y; + newHeight = originalBounds.Height - delta.Y; + break; + case "BottomHandle": + newHeight = originalBounds.Height + delta.Y; + break; + case "LeftHandle": + newX = originalBounds.X + delta.X; + newWidth = originalBounds.Width - delta.X; + break; + case "RightHandle": + newWidth = originalBounds.Width + delta.X; + break; + } + + // 确保最小尺寸和正值 + if (newWidth < 10) newWidth = 10; + if (newHeight < 10) newHeight = 10; + + // 创建新的Rect,确保所有值都是有效的 + newBounds = new Rect(newX, newY, newWidth, newHeight); + + return newBounds; + } + + private void ApplyBoundsToStrokes(Rect newBounds) + { + var selectedStrokes = inkCanvas.GetSelectedStrokes(); + if (selectedStrokes.Count == 0) return; + + var originalBounds = inkCanvas.GetSelectionBounds(); + + // 计算缩放比例 + var scaleX = newBounds.Width / originalBounds.Width; + var scaleY = newBounds.Height / originalBounds.Height; + + // 计算平移量 + var translateX = newBounds.X - originalBounds.X; + var translateY = newBounds.Y - originalBounds.Y; + + // 创建变换矩阵 + var matrix = new Matrix(); + matrix.Translate(translateX, translateY); + matrix.ScaleAt(scaleX, scaleY, originalBounds.X + originalBounds.Width / 2, originalBounds.Y + originalBounds.Height / 2); + + // 应用变换到选中的墨迹 + foreach (var stroke in selectedStrokes) + { + stroke.Transform(matrix, false); + } + } + + #endregion } } -#endregion diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 381745de..fee71d5f 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -18,7 +18,7 @@ using Application = System.Windows.Application; using CheckBox = System.Windows.Controls.CheckBox; using ComboBox = System.Windows.Controls.ComboBox; using File = System.IO.File; -using MessageBox = System.Windows.MessageBox; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; using OpenFileDialog = Microsoft.Win32.OpenFileDialog; using OperatingSystem = OSVersionExtension.OperatingSystem; using RadioButton = System.Windows.Controls.RadioButton; @@ -197,23 +197,23 @@ namespace Ink_Canvas val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1; ViewboxFloatingBarScaleTransform.ScaleY = val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1; - + // 等待UI更新后再重新计算浮动栏位置,确保居中计算准确 Dispatcher.BeginInvoke(new Action(async () => { // 强制更新布局以确保ActualWidth正确 ViewboxFloatingBar.UpdateLayout(); - + // 等待一小段时间让布局完全更新 await Task.Delay(100); - + // 再次强制更新布局 ViewboxFloatingBar.UpdateLayout(); - + // 强制重新测量和排列 ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize)); - + // auto align - 新增:只在屏幕模式下重新计算浮动栏位置 if (currentMode == 0) { @@ -1814,7 +1814,11 @@ namespace Ink_Canvas // 先设为None再设回原来的模式,避免可能的事件冲突 inkCanvas.EditingMode = InkCanvasEditingMode.None; + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = true; // 恢复到之前的编辑状态 @@ -2412,12 +2416,23 @@ namespace Ink_Canvas SaveSettingsToFile(); } - private void ToggleSwitchDirectCallCiRand_Toggled(object sender, RoutedEventArgs e) + private void ToggleSwitchExternalCaller_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; // 获取开关状态并保存到设置中 - Settings.RandSettings.DirectCallCiRand = ToggleSwitchDirectCallCiRand.IsOn; + Settings.RandSettings.DirectCallCiRand = ToggleSwitchExternalCaller.IsOn; + + // 保存设置到文件 + SaveSettingsToFile(); + } + + private void ComboBoxExternalCallerType_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!isLoaded) return; + + // 获取下拉框选择并保存到设置中 + Settings.RandSettings.ExternalCallerType = ComboBoxExternalCallerType.SelectedIndex; // 保存设置到文件 SaveSettingsToFile(); @@ -2747,6 +2762,12 @@ namespace Ink_Canvas var text = JsonConvert.SerializeObject(Settings, Formatting.Indented); try { + string configsDir = Path.Combine(App.RootPath, "Configs"); + if (!Directory.Exists(configsDir)) + { + Directory.CreateDirectory(configsDir); + } + File.WriteAllText(App.RootPath + settingsFileName, text); } catch { } @@ -2961,6 +2982,13 @@ namespace Ink_Canvas SaveSettingsToFile(); } + private void ToggleSwitchKeepFoldAfterSoftwareExit_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Automation.KeepFoldAfterSoftwareExit = ToggleSwitchKeepFoldAfterSoftwareExit.IsOn; + SaveSettingsToFile(); + } + private void ToggleSwitchAlwaysGoToFirstPageOnReenter_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 2ce687d5..2518ab54 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -256,7 +256,7 @@ namespace Ink_Canvas _taskbar.Visibility = Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed; ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue; - + // 初始化浮动栏透明度滑块值 ViewboxFloatingBarOpacityValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityValue; ViewboxFloatingBarOpacityInPPTValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue; @@ -763,7 +763,8 @@ namespace Ink_Canvas RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency; RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents; ToggleSwitchShowRandomAndSingleDraw.IsOn = Settings.RandSettings.ShowRandomAndSingleDraw; - ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand; + ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand; + ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType; RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed; SingleDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed; @@ -783,13 +784,22 @@ namespace Ink_Canvas ToggleSwitchDisplayRandWindowNamesInputBtn.IsOn = Settings.RandSettings.DisplayRandWindowNamesInputBtn; RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency; RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents; - ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand; + ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand; + ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType; } // ModeSettings if (Settings.ModeSettings != null) { ToggleSwitchMode.IsOn = Settings.ModeSettings.IsPPTOnlyMode; + + // 根据加载的配置状态执行相应的窗口显示/隐藏逻辑 + if (isStartup && Settings.ModeSettings.IsPPTOnlyMode) + { + // 启动时如果是仅PPT模式,隐藏主窗口 + Hide(); + LogHelper.WriteLogToFile("启动时检测到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); + } } else { @@ -848,6 +858,8 @@ namespace Ink_Canvas ToggleSwitchAutoFoldAfterPPTSlideShow.IsOn = Settings.Automation.IsAutoFoldAfterPPTSlideShow; + ToggleSwitchKeepFoldAfterSoftwareExit.IsOn = Settings.Automation.KeepFoldAfterSoftwareExit; + if (Settings.Automation.IsAutoKillEasiNote || Settings.Automation.IsAutoKillPptService || Settings.Automation.IsAutoKillHiteAnnotation || Settings.Automation.IsAutoKillInkCanvas || Settings.Automation.IsAutoKillICA || Settings.Automation.IsAutoKillIDT || diff --git a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs index 9cc3731b..70e4b8cc 100644 --- a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs +++ b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs @@ -10,7 +10,7 @@ using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; -using MessageBox = System.Windows.MessageBox; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; using Point = System.Windows.Point; namespace Ink_Canvas @@ -107,10 +107,10 @@ namespace Ink_Canvas else if (sender == ImageDrawArrow || sender == BoardImageDrawArrow) drawingShapeMode = 2; else if (sender == ImageDrawParallelLine || sender == BoardImageDrawParallelLine) drawingShapeMode = 15; - + // 更新模式缓存 UpdateCurrentToolMode("shape"); - + isLongPressSelected = true; if (isSingleFingerDragMode) BtnFingerDragMode_Click(BtnFingerDragMode, null); } @@ -118,6 +118,13 @@ namespace Ink_Canvas private void BtnPen_Click(object sender, RoutedEventArgs e) { + // 如果当前有选中的图片元素,先取消选中 + if (currentSelectedElement != null) + { + UnselectElement(currentSelectedElement); + currentSelectedElement = null; + } + // 禁用高级橡皮擦系统 DisableAdvancedEraserSystem(); diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs index e9a32ba2..9ddcfd79 100644 --- a/Ink Canvas/MainWindow_cs/MW_Timer.cs +++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs @@ -65,8 +65,11 @@ namespace Ink_Canvas private Timer timerDisplayTime = new Timer(); private Timer timerDisplayDate = new Timer(); + private Timer timerNtpSync = new Timer(); private TimeViewModel nowTimeVM = new TimeViewModel(); + private DateTime cachedNetworkTime = DateTime.Now; + private DateTime lastNtpSyncTime = DateTime.MinValue; private async Task GetNetworkTimeAsync() { @@ -117,35 +120,61 @@ namespace Ink_Canvas timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed; timerDisplayDate.Interval = 1000 * 60 * 60 * 1; timerDisplayDate.Start(); + timerNtpSync.Elapsed += async (s, e) => await TimerNtpSync_ElapsedAsync(); + timerNtpSync.Interval = 1000 * 60 * 60 * 2; // 每2小时同步一次 + timerNtpSync.Start(); timerKillProcess.Start(); nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd"); nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'"); } - // 修改TimerDisplayTime_ElapsedAsync方法中的时间格式,实现校验制 + // NTP同步定时器事件处理 + private async Task TimerNtpSync_ElapsedAsync() + { + try + { + DateTime networkTime = await GetNetworkTimeAsync(); + cachedNetworkTime = networkTime; + lastNtpSyncTime = DateTime.Now; + } + catch + { + // NTP同步失败时,保持使用本地时间 + cachedNetworkTime = DateTime.Now; + } + } + + // 修改TimerDisplayTime_ElapsedAsync方法,使用缓存的网络时间 private async Task TimerDisplayTime_ElapsedAsync() { DateTime localTime = DateTime.Now; DateTime displayTime = localTime; // 默认使用本地时间 - - try + + // 如果还没有进行过NTP同步,或者距离上次同步超过2小时,则进行一次同步 + if (lastNtpSyncTime == DateTime.MinValue || + (DateTime.Now - lastNtpSyncTime).TotalHours >= 2) { - DateTime networkTime = await GetNetworkTimeAsync(); - - // 计算时间差 - TimeSpan timeDifference = networkTime - localTime; - double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes); - - // 如果网络时间与本地时间相差不超过1分钟,则使用本地时间 - // 否则使用网络时间 - displayTime = timeDifferenceMinutes <= 1.0 ? localTime : networkTime; + try + { + DateTime networkTime = await GetNetworkTimeAsync(); + cachedNetworkTime = networkTime; + lastNtpSyncTime = DateTime.Now; + } + catch + { + // 网络时间获取失败时,使用本地时间 + cachedNetworkTime = localTime; + } } - catch - { - // 网络时间获取失败时,使用本地时间 - displayTime = localTime; - } - + + // 使用缓存的网络时间进行显示 + TimeSpan timeDifference = cachedNetworkTime - localTime; + double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes); + + // 如果网络时间与本地时间相差不超过3分钟,则使用本地时间 + // 否则使用网络时间 + displayTime = timeDifferenceMinutes <= 3.0 ? localTime : cachedNetworkTime; + // 只更新时间,日期由原有逻辑定时更新即可 Dispatcher.Invoke(() => { @@ -240,8 +269,18 @@ namespace Ink_Canvas ShowNotification("“鸿合屏幕书写”已自动关闭"); if (Settings.Automation.IsAutoKillHiteAnnotation && Settings.Automation.IsAutoEnterAnnotationAfterKillHite) { - // 自动进入批注状态 - PenIcon_Click(null, null); + // 检查是否处于收纳状态,如果是则先展开浮动栏 + if (isFloatingBarFolded) + { + // 先展开浮动栏,然后进入批注状态 + // UnFoldFloatingBar 方法内部会根据设置自动进入批注模式 + UnFoldFloatingBar(null); + } + else + { + // 如果已经展开,直接进入批注状态 + PenIcon_Click(null, null); + } } }); } @@ -302,6 +341,44 @@ namespace Ink_Canvas { var windowTitle = ForegroundWindowInfo.WindowTitle(); var windowRect = ForegroundWindowInfo.WindowRect(); + var windowProcessName = ForegroundWindowInfo.ProcessName(); + + // 检测希沃白板五的批注面板 + // 希沃白板五的批注面板通常具有以下特征: + // 1. 窗口标题为空或包含特定关键词 + // 2. 窗口高度较小(批注工具栏) + // 3. 窗口宽度适中(工具栏宽度) + if (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher") + { + // 检测希沃白板五的批注工具栏 + // 批注工具栏通常高度在50-200像素之间,宽度在200-800像素之间 + if (windowRect.Height >= 50 && windowRect.Height <= 200 && + windowRect.Width >= 200 && windowRect.Width <= 800) + { + return true; + } + + // 检测希沃白板五的二级菜单面板 + // 二级菜单面板通常高度在100-400像素之间,宽度在150-400像素之间 + if (windowRect.Height >= 100 && windowRect.Height <= 400 && + windowRect.Width >= 150 && windowRect.Width <= 400) + { + return true; + } + } + + // 检测鸿合软件的批注面板 + if (windowProcessName == "HiteCamera" || windowProcessName == "HiteTouchPro" || windowProcessName == "HiteLightBoard") + { + // 鸿合软件的批注面板特征 + if (windowRect.Height >= 50 && windowRect.Height <= 300 && + windowRect.Width >= 200 && windowRect.Width <= 600) + { + return true; + } + } + + // 原有的检测逻辑(保持向后兼容) return windowTitle.Length == 0 && windowRect.Height < 500; } @@ -369,7 +446,17 @@ namespace Ink_Canvas } else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher && (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher")) { - if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + // 检测到希沃白板五的批注窗口时保持收纳状态 + if (IsAnnotationWindow()) + { + // 批注窗口打开时,如果当前是展开状态则收纳 + if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + } + else + { + // 非批注窗口时正常处理 + if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + } // HiteCamera } else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" && @@ -491,8 +578,18 @@ namespace Ink_Canvas } else { - if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null); - unfoldFloatingBarByUser = false; + // 检查是否启用了软件退出后保持收纳模式 + if (Settings.Automation.KeepFoldAfterSoftwareExit) + { + // 如果启用了保持收纳模式,则不自动展开浮动栏 + unfoldFloatingBarByUser = false; + } + else + { + // 原有的逻辑:软件退出后自动展开浮动栏 + if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null); + unfoldFloatingBarByUser = false; + } } } catch { } diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index a5f6af9c..0f9a5dd6 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -8,6 +8,7 @@ using System.Windows.Controls; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Media.Imaging; using System.Windows.Threading; using Point = System.Windows.Point; @@ -30,7 +31,7 @@ namespace Ink_Canvas { var preservedElements = new List(); - // 遍历inkCanvas的所有子元素 + // 遍历inkCanvas的所有子元素,创建副本而不是直接引用 for (int i = inkCanvas.Children.Count - 1; i >= 0; i--) { var child = inkCanvas.Children[i]; @@ -39,13 +40,118 @@ namespace Ink_Canvas if (child is Image || child is MediaElement || (child is Border border && border.Name != "AdvancedEraserOverlay")) { - preservedElements.Add(child); + // 创建元素的深拷贝,避免直接引用导致的问题 + var clonedElement = CloneUIElement(child); + if (clonedElement != null) + { + preservedElements.Add(clonedElement); + } } } return preservedElements; } + /// + /// 克隆UI元素,创建深拷贝 + /// + private UIElement CloneUIElement(UIElement originalElement) + { + try + { + if (originalElement is Image originalImage) + { + var clonedImage = new Image(); + + // 复制图片源 + if (originalImage.Source is BitmapSource bitmapSource) + { + clonedImage.Source = bitmapSource; + } + + // 复制属性 + clonedImage.Width = originalImage.Width; + clonedImage.Height = originalImage.Height; + clonedImage.Stretch = originalImage.Stretch; + clonedImage.StretchDirection = originalImage.StretchDirection; + clonedImage.Name = originalImage.Name; + clonedImage.IsHitTestVisible = originalImage.IsHitTestVisible; + clonedImage.Focusable = originalImage.Focusable; + clonedImage.Cursor = originalImage.Cursor; + clonedImage.IsManipulationEnabled = originalImage.IsManipulationEnabled; + + // 复制位置 + InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(originalImage)); + InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(originalImage)); + + // 复制变换 + if (originalImage.RenderTransform != null) + { + clonedImage.RenderTransform = originalImage.RenderTransform.Clone(); + } + + return clonedImage; + } + else if (originalElement is MediaElement originalMedia) + { + var clonedMedia = new MediaElement(); + + // 复制媒体属性 + clonedMedia.Source = originalMedia.Source; + clonedMedia.Width = originalMedia.Width; + clonedMedia.Height = originalMedia.Height; + clonedMedia.Name = originalMedia.Name; + clonedMedia.IsHitTestVisible = originalMedia.IsHitTestVisible; + clonedMedia.Focusable = originalMedia.Focusable; + + // 复制位置 + InkCanvas.SetLeft(clonedMedia, InkCanvas.GetLeft(originalMedia)); + InkCanvas.SetTop(clonedMedia, InkCanvas.GetTop(originalMedia)); + + // 复制变换 + if (originalMedia.RenderTransform != null) + { + clonedMedia.RenderTransform = originalMedia.RenderTransform.Clone(); + } + + return clonedMedia; + } + else if (originalElement is Border originalBorder) + { + var clonedBorder = new Border(); + + // 复制边框属性 + clonedBorder.Width = originalBorder.Width; + clonedBorder.Height = originalBorder.Height; + clonedBorder.Name = originalBorder.Name; + clonedBorder.IsHitTestVisible = originalBorder.IsHitTestVisible; + clonedBorder.Focusable = originalBorder.Focusable; + clonedBorder.Background = originalBorder.Background; + clonedBorder.BorderBrush = originalBorder.BorderBrush; + clonedBorder.BorderThickness = originalBorder.BorderThickness; + clonedBorder.CornerRadius = originalBorder.CornerRadius; + + // 复制位置 + InkCanvas.SetLeft(clonedBorder, InkCanvas.GetLeft(originalBorder)); + InkCanvas.SetTop(clonedBorder, InkCanvas.GetTop(originalBorder)); + + // 复制变换 + if (originalBorder.RenderTransform != null) + { + clonedBorder.RenderTransform = originalBorder.RenderTransform.Clone(); + } + + return clonedBorder; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"克隆UI元素失败: {ex.Message}", LogHelper.LogType.Error); + } + + return null; + } + /// /// 恢复之前保存的非笔画元素到画布 /// @@ -55,11 +161,15 @@ namespace Ink_Canvas foreach (var element in preservedElements) { - // 确保元素没有父容器再添加到inkCanvas - if (element is FrameworkElement fe && fe.Parent == null) + try { + // 由于现在使用的是克隆的元素,不需要检查Parent属性 inkCanvas.Children.Add(element); } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"恢复非笔画元素失败: {ex.Message}", LogHelper.LogType.Error); + } } } @@ -163,13 +273,11 @@ namespace Ink_Canvas return; } - LogHelper.WriteLogToFile($"MainWindow_StylusDown 被调用,笔尾状态: {e.StylusDevice.Inverted}, 当前 drawingShapeMode: {drawingShapeMode}, 当前 EditingMode: {inkCanvas.EditingMode}"); // 新增:根据是否为笔尾自动切换橡皮擦/画笔模式 if (e.StylusDevice.Inverted) { inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; - LogHelper.WriteLogToFile("检测到笔尾,设置 EditingMode 为 EraseByPoint"); } else { @@ -178,14 +286,12 @@ namespace Ink_Canvas { // 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集 inkCanvas.EditingMode = InkCanvasEditingMode.None; - LogHelper.WriteLogToFile("几何绘制模式,设置 EditingMode 为 None"); return; } // 修复:保持当前的线擦模式,不要强制切换到Ink模式 if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke) { inkCanvas.EditingMode = InkCanvasEditingMode.Ink; - LogHelper.WriteLogToFile("设置 EditingMode 为 Ink"); } else { @@ -231,17 +337,11 @@ namespace Ink_Canvas { try { - LogHelper.WriteLogToFile($"MainWindow_StylusUp 被调用,EditingMode: {inkCanvas.EditingMode}, EnableInkFade: {Settings.Canvas.EnableInkFade}"); - var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke; - LogHelper.WriteLogToFile($"获取到墨迹,StylusPoints数量: {stroke.StylusPoints.Count}"); // 正常模式:添加到画布并参与墨迹纠正 - // 墨迹渐隐功能现在在 StrokeCollected 事件中统一处理所有输入方式 - LogHelper.WriteLogToFile("StylusUp: 添加墨迹到画布"); - inkCanvas.Strokes.Add(stroke); - await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁 + await Task.Delay(5); inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id)); inkCanvas_StrokeCollected(inkCanvas, @@ -369,7 +469,8 @@ namespace Ink_Canvas } if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) { - // 套索选状态下只return,保证套索选可用 + // 套索选状态下不直接return,允许触摸事件继续处理 + dec.Add(e.TouchDevice.Id); return; } // 修复:几何绘制模式下完全禁止触摸轨迹收集 @@ -502,7 +603,7 @@ namespace Ink_Canvas // 计算触摸面积(使用设备提供的Size) double touchArea = size.Width * size.Height; - + // 计算宽高比(使用Bounds确保准确性) double aspectRatio = Math.Min(bounds.Width, bounds.Height) / Math.Max(bounds.Width, bounds.Height); @@ -510,7 +611,7 @@ namespace Ink_Canvas bool isLargeTouch = touchArea >= palmAreaThreshold; bool isPalmLikeShape = aspectRatio >= aspectRatioThreshold; bool hasMultipleTouchPoints = dec.Count >= minTouchPoints; - + // 新增:额外的判定条件提高准确性 bool isReasonableSize = size.Width >= 20 && size.Height >= 20 && size.Width <= 200 && size.Height <= 200; // 合理的触摸尺寸范围 bool isNotTooElongated = aspectRatio >= 0.2; // 避免过于细长的触摸(可能是手指) @@ -613,7 +714,7 @@ namespace Ink_Canvas if (isPalmEraserActive && palmEraserTouchIds.Count == 0) { LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {palmEraserTouchIds.Count}, dec.Count: {dec.Count}"); - + // 恢复高光状态 drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter; @@ -777,10 +878,10 @@ namespace Ink_Canvas if (isPalmEraserActive) { LogHelper.WriteLogToFile("Palm eraser force recovery - all touch points cleared"); - + // 恢复高光状态 drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter; - + // 恢复编辑模式 try { @@ -806,7 +907,7 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"Palm eraser force recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error); inkCanvas.EditingMode = InkCanvasEditingMode.Ink; } - + // 如果手掌擦还在激活状态但触摸点已清空,强制重置状态 isPalmEraserActive = false; palmEraserTouchDownHandled = false; @@ -816,10 +917,10 @@ namespace Ink_Canvas ViewboxFloatingBar.IsHitTestVisible = true; BlackboardUIGridForInkReplay.IsHitTestVisible = true; - + // 停止恢复定时器 StopPalmEraserRecoveryTimer(); - + LogHelper.WriteLogToFile("Palm eraser force recovery completed"); } } @@ -864,7 +965,34 @@ namespace Ink_Canvas return; // 三指及以上禁止缩放 bool disableScale = dec.Count >= 3; - if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; + + // 修复:允许单指拖动选中的墨迹,即使禁用了多指手势 + if (isInMultiTouchMode) return; + + // 如果是单指拖动选中的墨迹,允许处理 + if (dec.Count == 1 && inkCanvas.GetSelectedStrokes().Count > 0) + { + var md = e.DeltaManipulation; + var trans = md.Translation; // 获得位移矢量 + + if (trans.X != 0 || trans.Y != 0) + { + var m = new Matrix(); + m.Translate(trans.X, trans.Y); // 移动 + + var strokes = inkCanvas.GetSelectedStrokes(); + foreach (var stroke in strokes) + { + stroke.Transform(m, false); + } + + // 更新选择框位置 + updateBorderStrokeSelectionControlLocation(); + } + return; + } + + if (!Settings.Gesture.IsEnableTwoFingerGesture) return; if ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode || StackPanelPPTControls.Visibility != Visibility.Visible || StackPanelPPTButtons.Visibility == Visibility.Collapsed)) || @@ -938,12 +1066,15 @@ namespace Ink_Canvas catch { } } - ; + // 同时变换画布上的图片元素 + TransformCanvasImages(m); } else { foreach (var stroke in inkCanvas.Strokes) stroke.Transform(m, false); - ; + + // 同时变换画布上的图片元素 + TransformCanvasImages(m); } foreach (var circle in circles) @@ -961,6 +1092,86 @@ namespace Ink_Canvas } } + /// + /// 变换画布上的图片元素,使其与墨迹同步移动 + /// + private void TransformCanvasImages(Matrix matrix) + { + try + { + // 遍历inkCanvas的所有子元素,找到图片元素 + for (int i = inkCanvas.Children.Count - 1; i >= 0; i--) + { + var child = inkCanvas.Children[i]; + + if (child is Image image) + { + // 应用矩阵变换到图片 + ApplyMatrixTransformToImage(image, matrix); + } + else if (child is MediaElement mediaElement) + { + // 对媒体元素也应用变换 + ApplyMatrixTransformToMediaElement(mediaElement, matrix); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"变换画布图片失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 对图片应用矩阵变换 + /// + private void ApplyMatrixTransformToImage(Image image, Matrix matrix) + { + try + { + // 获取图片的RenderTransform,如果不存在则创建新的TransformGroup + TransformGroup transformGroup = image.RenderTransform as TransformGroup; + if (transformGroup == null) + { + transformGroup = new TransformGroup(); + image.RenderTransform = transformGroup; + } + + // 创建新的MatrixTransform并添加到变换组 + var matrixTransform = new MatrixTransform(matrix); + transformGroup.Children.Add(matrixTransform); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用图片变换失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 对媒体元素应用矩阵变换 + /// + private void ApplyMatrixTransformToMediaElement(MediaElement mediaElement, Matrix matrix) + { + try + { + // 获取媒体元素的RenderTransform,如果不存在则创建新的TransformGroup + TransformGroup transformGroup = mediaElement.RenderTransform as TransformGroup; + if (transformGroup == null) + { + transformGroup = new TransformGroup(); + mediaElement.RenderTransform = transformGroup; + } + + // 创建新的MatrixTransform并添加到变换组 + var matrixTransform = new MatrixTransform(matrix); + transformGroup.Children.Add(matrixTransform); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用媒体元素变换失败: {ex.Message}", LogHelper.LogType.Error); + } + } + // 退出多指书写模式,恢复InkCanvas的TouchDown事件绑定 private void ExitMultiTouchModeIfNeeded() { diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs index 21a9de2b..678f8232 100644 --- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs +++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs @@ -201,5 +201,60 @@ namespace Ink_Canvas } } + private void DisableAllHotkeysMenuItem_Clicked(object sender, RoutedEventArgs e) + { + var mainWin = (MainWindow)Current.MainWindow; + if (mainWin.IsLoaded) + { + try + { + // 获取全局快捷键管理器 + var hotkeyManagerField = typeof(MainWindow).GetField("_globalHotkeyManager", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var hotkeyManager = hotkeyManagerField?.GetValue(mainWin) as GlobalHotkeyManager; + + if (hotkeyManager != null) + { + // 禁用所有快捷键 + hotkeyManager.DisableHotkeyRegistration(); + + // 更新菜单项文本和状态 + var menuItem = sender as MenuItem; + if (menuItem != null) + { + var headerPanel = menuItem.Header as SimpleStackPanel; + if (headerPanel != null) + { + var textBlock = headerPanel.Children[0] as TextBlock; + if (textBlock != null) + { + if (textBlock.Text == "禁用所有快捷键") + { + textBlock.Text = "启用所有快捷键"; + LogHelper.WriteLogToFile("已禁用所有快捷键", LogHelper.LogType.Event); + } + else + { + textBlock.Text = "禁用所有快捷键"; + // 重新启用快捷键 + hotkeyManager.EnableHotkeyRegistration(); + LogHelper.WriteLogToFile("已启用所有快捷键", LogHelper.LogType.Event); + } + } + } + } + } + else + { + LogHelper.WriteLogToFile("无法获取全局快捷键管理器", LogHelper.LogType.Error); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"禁用/启用快捷键时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + } } diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs index dff64f25..094e2735 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/Ink Canvas/Properties/AssemblyInfo.cs @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.10.0")] -[assembly: AssemblyFileVersion("1.7.10.0")] +[assembly: AssemblyVersion("1.7.11.0")] +[assembly: AssemblyFileVersion("1.7.11.0")] diff --git a/Ink Canvas/Resources/DeveloperAvatars/PrefacedCorg.jpg b/Ink Canvas/Resources/DeveloperAvatars/PrefacedCorg.jpg new file mode 100644 index 00000000..7df28e3d Binary files /dev/null and b/Ink Canvas/Resources/DeveloperAvatars/PrefacedCorg.jpg differ diff --git a/Ink Canvas/Resources/ICCConfiguration.cs b/Ink Canvas/Resources/ICCConfiguration.cs index 5a866e0d..7ff5dc71 100644 --- a/Ink Canvas/Resources/ICCConfiguration.cs +++ b/Ink Canvas/Resources/ICCConfiguration.cs @@ -1,27 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; using System.Windows.Media; -namespace Ink_Canvas.Resources.ICCConfiguration { - public enum InitialPositionTypes { +namespace Ink_Canvas.Resources.ICCConfiguration +{ + public enum InitialPositionTypes + { TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, BottomCenter, Custom } - public enum ElementCornerRadiusTypes { + public enum ElementCornerRadiusTypes + { SuperEllipse, Circle, Custom, None } - public class NearSnapAreaSize { - public double[] TopLeft { get; set; } = {24,24}; - public double[] TopRight { get; set; } = {24,24}; - public double[] BottomLeft { get; set; } = {24,24}; - public double[] BottomRight { get; set; } = {24,24}; + public class NearSnapAreaSize + { + public double[] TopLeft { get; set; } = { 24, 24 }; + public double[] TopRight { get; set; } = { 24, 24 }; + public double[] BottomLeft { get; set; } = { 24, 24 }; + public double[] BottomRight { get; set; } = { 24, 24 }; public double TopCenter { get; set; } = 24; public double BottomCenter { get; set; } = 24; } - public class ICCFloatingBarConfiguration { + public class ICCFloatingBarConfiguration + { public bool SemiTransparent { get; set; } = false; public bool NearSnap { get; set; } = true; public InitialPositionTypes InitialPosition { get; set; } = InitialPositionTypes.BottomCenter; @@ -37,7 +37,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration { public double MovingLimitationNoSnap { get; set; } = 12; public double MovingLimitationSnapped { get; set; } = 24; - public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize() { + public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize() + { TopLeft = new double[] { 24, 24 }, TopRight = new double[] { 24, 24 }, BottomLeft = new double[] { 24, 24 }, @@ -55,7 +56,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration { }; } - public class ICCConfiguration { + public class ICCConfiguration + { public ICCFloatingBarConfiguration FloatingBar { get; set; } = new ICCFloatingBarConfiguration(); } } diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 9b6c8b5c..d1997e4e 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -440,7 +440,7 @@ namespace Ink_Canvas public int MinimumAutomationStrokeNumber { get; set; } [JsonProperty("autoSavedStrokesLocation")] - public string AutoSavedStrokesLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves"); + public string AutoSavedStrokesLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Saves"); [JsonProperty("autoDelSavedFiles")] public bool AutoDelSavedFiles; @@ -448,11 +448,50 @@ namespace Ink_Canvas [JsonProperty("autoDelSavedFilesDaysThreshold")] public int AutoDelSavedFilesDaysThreshold = 15; + [JsonProperty("keepFoldAfterSoftwareExit")] + public bool KeepFoldAfterSoftwareExit { get; set; } = false; + [JsonProperty("isSaveFullPageStrokes")] public bool IsSaveFullPageStrokes; [JsonProperty("isAutoEnterAnnotationAfterKillHite")] public bool IsAutoEnterAnnotationAfterKillHite { get; set; } + + [JsonProperty("floatingWindowInterceptor")] + public FloatingWindowInterceptorSettings FloatingWindowInterceptor { get; set; } = new FloatingWindowInterceptorSettings(); + } + + public class FloatingWindowInterceptorSettings + { + [JsonProperty("isEnabled")] + public bool IsEnabled { get; set; } = false; + + [JsonProperty("scanIntervalMs")] + public int ScanIntervalMs { get; set; } = 1000; + + [JsonProperty("interceptRules")] + public Dictionary InterceptRules { get; set; } = new Dictionary + { + { "SeewoWhiteboard3Floating", true }, + { "SeewoWhiteboard5Floating", true }, + { "SeewoWhiteboard5CFloating", true }, + { "SeewoPincoSideBarFloating", true }, + { "SeewoPincoDrawingFloating", true }, + { "SeewoPPTFloating", true }, + { "AiClassFloating", true }, + { "HiteAnnotationFloating", true }, + { "ChangYanFloating", true }, + { "ChangYanPptFloating", true }, + { "IntelligentClassFloating", true }, + { "SeewoDesktopAnnotationFloating", true }, + { "SeewoDesktopSideBarFloating", true } + }; + + [JsonProperty("autoStart")] + public bool AutoStart { get; set; } = false; + + [JsonProperty("showNotifications")] + public bool ShowNotifications { get; set; } = true; } public class Advanced @@ -545,6 +584,8 @@ namespace Ink_Canvas public bool ShowRandomAndSingleDraw { get; set; } = true; [JsonProperty("directCallCiRand")] public bool DirectCallCiRand { get; set; } + [JsonProperty("externalCallerType")] + public int ExternalCallerType { get; set; } = 0; [JsonProperty("selectedBackgroundIndex")] public int SelectedBackgroundIndex { get; set; } [JsonProperty("customPickNameBackgrounds")] diff --git a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs index 31e190ce..a90e6b1d 100644 --- a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs +++ b/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs @@ -222,7 +222,6 @@ namespace Ink_Canvas BtnStartCover.Visibility = Visibility.Collapsed; BorderStopTime.Visibility = Visibility.Collapsed; TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F")); - return; } else if (isTimerRunning && isPaused) { @@ -359,7 +358,8 @@ namespace Ink_Canvas // Set to center double dpiScaleX = 1, dpiScaleY = 1; PresentationSource source = PresentationSource.FromVisual(this); - if (source != null) { + if (source != null) + { dpiScaleX = source.CompositionTarget.TransformToDevice.M11; dpiScaleY = source.CompositionTarget.TransformToDevice.M22; } diff --git a/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs b/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs index bb7f41df..8172acbf 100644 --- a/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs +++ b/Ink Canvas/Windows/HotkeySettingsWindow.xaml.cs @@ -42,7 +42,7 @@ namespace Ink_Canvas.Windows // 加载当前快捷键(包括配置文件中的) LoadCurrentHotkeys(); SetupEventHandlers(); - + // 初始化鼠标模式快捷键设置 InitializeMouseModeSettings(); } @@ -66,11 +66,11 @@ namespace Ink_Canvas.Windows try { // 设置窗口启动位置为屏幕中心 - this.WindowStartupLocation = WindowStartupLocation.CenterScreen; - + WindowStartupLocation = WindowStartupLocation.CenterScreen; + // 确保窗口在显示时获得焦点 - this.ShowInTaskbar = true; - + ShowInTaskbar = true; + LogHelper.WriteLogToFile("快捷键设置窗口属性已设置"); } catch (Exception ex) @@ -274,10 +274,10 @@ namespace Ink_Canvas.Windows { // 设置开关的初始状态 ToggleSwitchEnableHotkeysInMouseMode.IsOn = MainWindow.Settings.Appearance.EnableHotkeysInMouseMode; - + // 绑定开关变化事件 ToggleSwitchEnableHotkeysInMouseMode.Toggled += OnMouseModeHotkeyToggleChanged; - + LogHelper.WriteLogToFile($"鼠标模式快捷键设置已初始化: {MainWindow.Settings.Appearance.EnableHotkeysInMouseMode}"); } catch (Exception ex) @@ -295,16 +295,16 @@ namespace Ink_Canvas.Windows { // 更新设置 MainWindow.Settings.Appearance.EnableHotkeysInMouseMode = ToggleSwitchEnableHotkeysInMouseMode.IsOn; - + // 立即保存设置 MainWindow.SaveSettingsToFile(); - + // 如果快捷键管理器存在,立即更新快捷键状态 if (_hotkeyManager != null) { // 检查当前是否处于鼠标模式 bool isCurrentlyMouseMode = _mainWindow.inkCanvas.EditingMode == InkCanvasEditingMode.None; - + // 如果当前处于鼠标模式且关闭了开关,立即禁用快捷键 if (isCurrentlyMouseMode && !ToggleSwitchEnableHotkeysInMouseMode.IsOn) { @@ -317,7 +317,7 @@ namespace Ink_Canvas.Windows _hotkeyManager.UpdateHotkeyStateForToolMode(isCurrentlyMouseMode); } } - + LogHelper.WriteLogToFile($"鼠标模式快捷键设置已更新: {MainWindow.Settings.Appearance.EnableHotkeysInMouseMode}", LogHelper.LogType.Event); } catch (Exception ex) @@ -540,7 +540,7 @@ namespace Ink_Canvas.Windows /// /// 标题栏拖拽事件 /// - private void TitleBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { diff --git a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs index 3d48d677..61c50f51 100644 --- a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs +++ b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs @@ -25,17 +25,22 @@ namespace Ink_Canvas if (e.LeftButton == MouseButtonState.Pressed) DragMove(); } - private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e) { - if (WindowState == WindowState.Normal) { + private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e) + { + if (WindowState == WindowState.Normal) + { WindowState = WindowState.Maximized; SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow; - } else { + } + else + { WindowState = WindowState.Normal; SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen; } } - private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e) { + private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e) + { e.Handled = true; } } diff --git a/Ink Canvas/Windows/PluginSettingsWindow.xaml b/Ink Canvas/Windows/PluginSettingsWindow.xaml index 776a2b1a..46ed420f 100644 --- a/Ink Canvas/Windows/PluginSettingsWindow.xaml +++ b/Ink Canvas/Windows/PluginSettingsWindow.xaml @@ -6,10 +6,10 @@ xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:local="clr-namespace:Ink_Canvas.Windows" mc:Ignorable="d" + WindowStyle="None" Title="插件管理" Height="550" Width="800" WindowStartupLocation="CenterScreen" ResizeMode="CanResize" - WindowStyle="None" AllowsTransparency="True" Background="#F9F9F9"> diff --git a/Ink Canvas/Windows/PluginSettingsWindow.xaml.cs b/Ink Canvas/Windows/PluginSettingsWindow.xaml.cs index ef206124..5f54aaa3 100644 --- a/Ink Canvas/Windows/PluginSettingsWindow.xaml.cs +++ b/Ink Canvas/Windows/PluginSettingsWindow.xaml.cs @@ -12,7 +12,7 @@ using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; -using MessageBox = System.Windows.MessageBox; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; namespace Ink_Canvas.Windows { diff --git a/Ink Canvas/Windows/RandWindow.xaml b/Ink Canvas/Windows/RandWindow.xaml index 135d1dc2..f1bada9c 100644 --- a/Ink Canvas/Windows/RandWindow.xaml +++ b/Ink Canvas/Windows/RandWindow.xaml @@ -98,7 +98,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -127,7 +127,7 @@ - + @@ -137,7 +137,7 @@ - + diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/Ink Canvas/Windows/RandWindow.xaml.cs index 42c55d6f..f8f568fe 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/Ink Canvas/Windows/RandWindow.xaml.cs @@ -1,4 +1,5 @@ using Ink_Canvas.Helpers; +using iNKORE.UI.WPF.Modern.Controls; using Microsoft.VisualBasic; using System; using System.Collections.Generic; @@ -26,7 +27,7 @@ namespace Ink_Canvas { InitializeComponent(); AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25); - BorderBtnHelp.Visibility = !settings.RandSettings.DisplayRandWindowNamesInputBtn ? Visibility.Collapsed : Visibility.Visible; + BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible; RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents; RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000; @@ -35,10 +36,10 @@ namespace Ink_Canvas // 设置窗口为置顶 Topmost = true; - + // 添加窗口关闭事件处理 Closed += RandWindow_Closed; - + // 添加窗口显示事件处理,确保置顶 Loaded += RandWindow_Loaded; } @@ -80,7 +81,7 @@ namespace Ink_Canvas isAutoClose = IsAutoClose; PeopleControlPane.Opacity = 0.4; PeopleControlPane.IsHitTestVisible = false; - BorderBtnHelp.Visibility = !settings.RandSettings.DisplayRandWindowNamesInputBtn ? Visibility.Collapsed : Visibility.Visible; + BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible; RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents; RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000; @@ -89,10 +90,10 @@ namespace Ink_Canvas // 设置窗口为置顶 Topmost = true; - + // 添加窗口关闭事件处理 Closed += RandWindow_Closed; - + // 添加窗口显示事件处理,确保置顶 Loaded += RandWindow_Loaded; @@ -120,7 +121,7 @@ namespace Ink_Canvas if (RandMaxPeopleOneTime != -1 && TotalCount >= RandMaxPeopleOneTime) return; TotalCount++; LabelNumberCount.Text = TotalCount.ToString(); - FontIconStart.Glyph = ""; + SymbolIconStart.Symbol = Symbol.People; BorderBtnAdd.Opacity = 1; BorderBtnMinus.Opacity = 1; } @@ -132,7 +133,7 @@ namespace Ink_Canvas LabelNumberCount.Text = TotalCount.ToString(); if (TotalCount == 1) { - FontIconStart.Glyph = ""; + SymbolIconStart.Symbol = Symbol.Contact; } } @@ -371,10 +372,10 @@ namespace Ink_Canvas // 强制激活窗口 Activate(); Focus(); - + // 设置置顶 Topmost = true; - + // 使用Win32 API强制置顶 var hwnd = new WindowInteropHelper(this).Handle; if (hwnd != IntPtr.Zero) diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml index ef299119..b4832ff9 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml @@ -19,8 +19,6 @@ - - +