diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt index 823a14ad..f2f7432d 100644 --- a/AutomaticUpdateVersionControl.txt +++ b/AutomaticUpdateVersionControl.txt @@ -1 +1 @@ -1.7.5.0 +1.7.6.0 diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 8439db7a..1237f47d 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -423,7 +423,7 @@ namespace Ink_Canvas try { string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath, "-m"); + Process.Start(exePath); } catch { } Environment.Exit(1); @@ -438,16 +438,7 @@ namespace Ink_Canvas /*if (!StoreHelper.IsStoreApp) */RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version)); - - // 在应用启动时自动释放IACore相关DLL - try - { - Helpers.IACoreDllExtractor.ExtractIACoreDlls(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } + // 记录应用启动(设备标识符) DeviceIdentifier.RecordAppLaunch(); @@ -456,7 +447,7 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}"); bool ret; - mutex = new Mutex(true, "InkCanvasForClass", out ret); + mutex = new Mutex(true, "InkCanvasForClass CE", out ret); if (!ret && !e.Args.Contains("-m")) //-m multiple { @@ -654,7 +645,7 @@ namespace Ink_Canvas try { string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath, "-m"); + Process.Start(exePath); } catch { } Environment.Exit(1); @@ -714,7 +705,7 @@ namespace Ink_Canvas Environment.Exit(1); } string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath, "-m"); + Process.Start(exePath); } // CrashActionType.NoAction 时不重启,直接退出 } diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 89b7106e..2fcd67b5 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Windows; [assembly: AssemblyTitle("InkCanvasForClass")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Dubi906w")] +[assembly: AssemblyCompany("CJK_mkp")] [assembly: AssemblyProduct("InkCanvasForClass")] [assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")] [assembly: AssemblyTrademark("")] @@ -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.5.0")] -[assembly: AssemblyFileVersion("1.7.5.0")] +[assembly: AssemblyVersion("1.76.0")] +[assembly: AssemblyFileVersion("1.7.6.0")] diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs index 8dae35a3..c7701754 100644 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -28,7 +28,7 @@ namespace Ink_Canvas.Helpers public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度 public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数 - public int InterpolationSteps { get; set; } = 4; // 极大减少插值步数 + public int InterpolationSteps { get; set; } = 8; // 从4增加到8,提高插值步数 public bool UseHardwareAcceleration { get; set; } = true; public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; @@ -354,7 +354,7 @@ namespace Ink_Canvas.Helpers { public double SmoothingStrength { get; set; } = 0.3; public double ResampleInterval { get; set; } = 3.0; - public int InterpolationSteps { get; set; } = 4; + public int InterpolationSteps { get; set; } = 8; public Stroke SmoothStroke(Stroke stroke) { @@ -457,7 +457,7 @@ namespace Ink_Canvas.Helpers return result; } - private List SlidingBezierFit(List points, int window = 4, int steps = 24) + private List SlidingBezierFit(List points, int window = 4, int steps = 48) // 从24增加到48 { var result = new List(); if (points.Count < window) return points; diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs index 42f9f7f8..9ac6f811 100644 --- a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs +++ b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs @@ -74,13 +74,13 @@ namespace Ink_Canvas.Helpers pathFigure.StartPoint = new Point(points[0].X, points[0].Y); - // 使用贝塞尔曲线段创建平滑路径 - for (int i = 0; i < points.Count - 1; i += 3) + // 使用贝塞尔曲线段创建平滑路径,增加插点密度 + for (int i = 0; i < points.Count - 1; i += 2) // 从i+=3改为i+=2,增加插点密度 { var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint; var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1; var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2; - + var bezierSegment = new BezierSegment(p1, p2, p3, true); pathFigure.Segments.Add(bezierSegment); } @@ -145,7 +145,7 @@ namespace Ink_Canvas.Helpers /// /// 使用GPU加速的并行贝塞尔计算 /// - public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 16) + public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 32) { if (controlPoints.Length < 4) return controlPoints; @@ -212,13 +212,13 @@ namespace Ink_Canvas.Helpers /// public class InkSmoothingConfig { - public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.Balanced; + public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.HighQuality; public bool UseHardwareAcceleration { get; set; } = true; public bool UseAsyncProcessing { get; set; } = true; public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; - public double SmoothingStrength { get; set; } = 0.6; - public double ResampleInterval { get; set; } = 1.2; - public int InterpolationSteps { get; set; } = 16; + public double SmoothingStrength { get; set; } = 0.8; // 高质量模式的平滑强度 + public double ResampleInterval { get; set; } = 0.8; // 高质量模式的重采样间隔 + public int InterpolationSteps { get; set; } = 64; // 高质量模式的插值步数 public static InkSmoothingConfig FromSettings() { @@ -239,17 +239,17 @@ namespace Ink_Canvas.Helpers case InkSmoothingQuality.HighPerformance: SmoothingStrength = 0.4; ResampleInterval = 2.0; - InterpolationSteps = 8; + InterpolationSteps = 16; break; case InkSmoothingQuality.Balanced: SmoothingStrength = 0.6; ResampleInterval = 1.2; - InterpolationSteps = 16; + InterpolationSteps = 32; break; case InkSmoothingQuality.HighQuality: SmoothingStrength = 0.8; ResampleInterval = 0.8; - InterpolationSteps = 32; + InterpolationSteps = 64; break; } } diff --git a/Ink Canvas/Helpers/IACoreDllExtractor.cs b/Ink Canvas/Helpers/IACoreDllExtractor.cs deleted file mode 100644 index de717826..00000000 --- a/Ink Canvas/Helpers/IACoreDllExtractor.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Windows; - -namespace Ink_Canvas.Helpers -{ - /// - /// IACore DLL自动释放器 - /// 在应用启动时自动释放IACore相关的DLL文件到应用程序目录 - /// - public static class IACoreDllExtractor - { - private static readonly string[] RequiredDlls = { - "IACore.dll", - "IALoader.dll", - "IAWinFX.dll" - }; - - /// - /// 在应用启动时释放IACore相关DLL - /// - public static void ExtractIACoreDlls() - { - try - { - string appDirectory = AppDomain.CurrentDomain.BaseDirectory; - LogHelper.WriteLogToFile("开始检查并释放IACore相关DLL文件"); - - foreach (string dllName in RequiredDlls) - { - string targetPath = Path.Combine(appDirectory, dllName); - - // 检查文件是否已存在且有效 - if (File.Exists(targetPath) && IsValidDll(targetPath)) - { - LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放"); - continue; - } - - // 从嵌入资源中释放DLL - if (ExtractDllFromResource(dllName, targetPath)) - { - LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}"); - } - else - { - LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响形状识别功能", LogHelper.LogType.Warning); - } - } - - LogHelper.WriteLogToFile("IACore DLL释放检查完成"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 从嵌入资源中提取DLL文件 - /// - private static bool ExtractDllFromResource(string dllName, string targetPath) - { - try - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string resourceName = $"Ink_Canvas.Resources.IACore.{dllName}"; - - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) - { - if (resourceStream == null) - { - LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning); - return false; - } - - // 确保目标目录存在 - string targetDirectory = Path.GetDirectoryName(targetPath); - if (!Directory.Exists(targetDirectory)) - { - Directory.CreateDirectory(targetDirectory); - } - - // 写入文件 - using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) - { - resourceStream.CopyTo(fileStream); - } - - return true; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 检查DLL文件是否有效 - /// - private static bool IsValidDll(string filePath) - { - try - { - if (!File.Exists(filePath)) - return false; - - FileInfo fileInfo = new FileInfo(filePath); - - // 检查文件大小(空文件或过小的文件可能无效) - if (fileInfo.Length < 1024) // 小于1KB可能无效 - return false; - - // 简单检查PE头(DLL文件应该以MZ开头) - using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - { - byte[] buffer = new byte[2]; - if (fs.Read(buffer, 0, 2) == 2) - { - return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ" - } - } - - return false; - } - catch - { - return false; - } - } - - /// - /// 清理释放的DLL文件(可选,在应用退出时调用) - /// - public static void CleanupExtractedDlls() - { - try - { - string appDirectory = AppDomain.CurrentDomain.BaseDirectory; - - foreach (string dllName in RequiredDlls) - { - string filePath = Path.Combine(appDirectory, dllName); - - if (File.Exists(filePath)) - { - try - { - File.Delete(filePath); - LogHelper.WriteLogToFile($"已清理 {dllName}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } -} diff --git a/Ink Canvas/Helpers/InkSmoothingManager.cs b/Ink Canvas/Helpers/InkSmoothingManager.cs index f28331c2..c6490e41 100644 --- a/Ink Canvas/Helpers/InkSmoothingManager.cs +++ b/Ink Canvas/Helpers/InkSmoothingManager.cs @@ -194,15 +194,17 @@ namespace Ink_Canvas.Helpers var processorCount = Environment.ProcessorCount; var isHardwareAccelerated = IsHardwareAccelerationSupported(); - if (processorCount >= 8 && isHardwareAccelerated) + if (processorCount >= 4 && isHardwareAccelerated) { + // 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量 config.Quality = InkSmoothingQuality.HighQuality; config.UseHardwareAcceleration = true; config.UseAsyncProcessing = true; config.MaxConcurrentTasks = Math.Min(processorCount, 8); } - else if (processorCount >= 4) + else if (processorCount >= 2) { + // 2核以上使用平衡模式 config.Quality = InkSmoothingQuality.Balanced; config.UseHardwareAcceleration = isHardwareAccelerated; config.UseAsyncProcessing = true; @@ -210,6 +212,7 @@ namespace Ink_Canvas.Helpers } else { + // 单核或性能较低的设备使用高性能模式 config.Quality = InkSmoothingQuality.HighPerformance; config.UseHardwareAcceleration = false; config.UseAsyncProcessing = false; diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs new file mode 100644 index 00000000..549ab5ab --- /dev/null +++ b/Ink Canvas/Helpers/PPTInkManager.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Windows.Ink; +using System.Windows.Threading; +using Microsoft.Office.Interop.PowerPoint; + +namespace Ink_Canvas.Helpers +{ + /// + /// PPT墨迹管理器 - 负责PPT中墨迹的保存、加载和同步 + /// + public class PPTInkManager : IDisposable + { + #region Properties + public bool IsAutoSaveEnabled { get; set; } = true; + public string AutoSaveLocation { get; set; } = ""; + public StrokeCollection CurrentStrokes { get; private set; } = new StrokeCollection(); + #endregion + + #region Private Fields + private MemoryStream[] _memoryStreams; + private int _maxSlides = 100; + private string _currentPresentationId = ""; + private readonly object _lockObject = new object(); + private bool _disposed = false; + + // 墨迹锁定机制,防止翻页时的墨迹冲突 + private DateTime _inkLockUntil = DateTime.MinValue; + private int _lockedSlideIndex = -1; + private const int InkLockMilliseconds = 500; + #endregion + + #region Constructor + public PPTInkManager() + { + InitializeMemoryStreams(); + } + + private void InitializeMemoryStreams() + { + _memoryStreams = new MemoryStream[_maxSlides + 2]; + } + #endregion + + #region Public Methods + /// + /// 初始化新的演示文稿 + /// + public void InitializePresentation(Presentation presentation) + { + if (presentation == null) return; + + lock (_lockObject) + { + try + { + // 生成演示文稿唯一标识符 + _currentPresentationId = GeneratePresentationId(presentation); + + // 重新初始化内存流数组 + var slideCount = presentation.Slides.Count; + _memoryStreams = new MemoryStream[slideCount + 2]; + + // 如果启用自动保存,尝试加载已保存的墨迹 + if (IsAutoSaveEnabled && !string.IsNullOrEmpty(AutoSaveLocation)) + { + LoadSavedStrokes(); + } + + LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化演示文稿墨迹管理失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 保存当前页面的墨迹 + /// + public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes) + { + if (slideIndex <= 0 || strokes == null) return; + + lock (_lockObject) + { + try + { + // 检查墨迹锁定 + if (!CanWriteInk(slideIndex)) + { + LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning); + return; + } + + 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); + } + } + } + + /// + /// 加载指定页面的墨迹 + /// + public StrokeCollection LoadSlideStrokes(int slideIndex) + { + if (slideIndex <= 0) return new StrokeCollection(); + + lock (_lockObject) + { + try + { + if (slideIndex < _memoryStreams.Length && _memoryStreams[slideIndex] != null && _memoryStreams[slideIndex].Length > 0) + { + _memoryStreams[slideIndex].Position = 0; + var strokes = new StrokeCollection(_memoryStreams[slideIndex]); + LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace); + return strokes; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + + return new StrokeCollection(); + } + + /// + /// 切换到指定页面并加载墨迹 + /// + public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null) + { + lock (_lockObject) + { + try + { + // 如果有当前墨迹,先保存 + if (currentStrokes != null && currentStrokes.Count > 0) + { + SaveCurrentSlideStrokes(_lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex, currentStrokes); + } + + // 设置墨迹锁定 + LockInkForSlide(slideIndex); + + // 加载新页面的墨迹 + return LoadSlideStrokes(slideIndex); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到第{slideIndex}页失败: {ex}", LogHelper.LogType.Error); + return new StrokeCollection(); + } + } + } + + /// + /// 保存所有墨迹到文件 + /// + public void SaveAllStrokesToFile(Presentation presentation) + { + if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return; + + lock (_lockObject) + { + try + { + var folderPath = GetPresentationFolderPath(); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // 保存位置信息 + try + { + File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString()); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存位置信息失败: {ex}", LogHelper.LogType.Error); + } + + // 保存所有页面的墨迹 + int savedCount = 0; + for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++) + { + if (_memoryStreams[i] != null) + { + try + { + if (_memoryStreams[i].Length > 8) + { + var srcBuf = new byte[_memoryStreams[i].Length]; + _memoryStreams[i].Position = 0; + var byteLength = _memoryStreams[i].Read(srcBuf, 0, srcBuf.Length); + + var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk"); + File.WriteAllBytes(filePath, srcBuf); + savedCount++; + + LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace); + } + else + { + // 删除空的墨迹文件 + var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk"); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存第{i}页墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存墨迹到文件失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 从文件加载已保存的墨迹 + /// + public void LoadSavedStrokes() + { + if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation)) return; + + lock (_lockObject) + { + try + { + var folderPath = GetPresentationFolderPath(); + if (!Directory.Exists(folderPath)) return; + + var files = new DirectoryInfo(folderPath).GetFiles("*.icstk"); + int loadedCount = 0; + + foreach (var file in files) + { + try + { + if (int.TryParse(Path.GetFileNameWithoutExtension(file.Name), out int slideIndex)) + { + if (slideIndex > 0 && slideIndex < _memoryStreams.Length) + { + var fileBytes = File.ReadAllBytes(file.FullName); + _memoryStreams[slideIndex] = new MemoryStream(fileBytes); + _memoryStreams[slideIndex].Position = 0; + loadedCount++; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载墨迹文件{file.Name}失败: {ex}", LogHelper.LogType.Error); + } + } + + LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 清除所有墨迹 + /// + public void ClearAllStrokes() + { + lock (_lockObject) + { + try + { + for (int i = 0; i < _memoryStreams.Length; i++) + { + _memoryStreams[i]?.Dispose(); + _memoryStreams[i] = null; + } + + CurrentStrokes.Clear(); + LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清除墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + } + + /// + /// 翻页后锁定墨迹写入 + /// + public void LockInkForSlide(int slideIndex) + { + _inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds); + _lockedSlideIndex = slideIndex; + } + + /// + /// 检查是否可以写入墨迹 + /// + public bool CanWriteInk(int currentSlideIndex) + { + if (DateTime.Now < _inkLockUntil) return false; + if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false; + return true; + } + #endregion + + #region Private Methods + private string GeneratePresentationId(Presentation presentation) + { + try + { + var presentationPath = presentation.FullName; + var fileHash = GetFileHash(presentationPath); + return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}"; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"生成演示文稿ID失败: {ex}", LogHelper.LogType.Error); + 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 ex) + { + LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error); + return "error"; + } + } + + private string GetPresentationFolderPath() + { + return Path.Combine(AutoSaveLocation, "Auto Saved - Presentations", _currentPresentationId); + } + #endregion + + #region Dispose + public void Dispose() + { + if (!_disposed) + { + lock (_lockObject) + { + ClearAllStrokes(); + } + _disposed = true; + } + } + #endregion + } +} diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs new file mode 100644 index 00000000..59a2f553 --- /dev/null +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -0,0 +1,1642 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using System.Windows; +using System.Windows.Threading; +using Microsoft.Office.Core; +using Microsoft.Office.Interop.PowerPoint; +using Application = System.Windows.Application; +using Timer = System.Timers.Timer; + +namespace Ink_Canvas.Helpers +{ + /// + /// PPT联动管理器 - 统一管理PPT和WPS的连接、事件处理和进程管理 + /// + public class PPTManager : IDisposable + { + #region Events + public event Action SlideShowBegin; + public event Action SlideShowNextSlide; + public event Action SlideShowEnd; + public event Action PresentationOpen; + public event Action PresentationClose; + public event Action PPTConnectionChanged; + #endregion + + #region Properties + public Microsoft.Office.Interop.PowerPoint.Application PPTApplication { get; private set; } + public Presentation CurrentPresentation { get; private set; } + public Slides CurrentSlides { get; private set; } + public Slide CurrentSlide { get; private set; } + public int SlidesCount { get; private set; } + public bool IsConnected + { + get + { + try + { + if (PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + // 尝试访问一个简单的属性来验证连接是否有效 + var _ = PPTApplication.Name; + return true; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + // 如果COM对象已失效,返回false + if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5) + { + return false; + } + return false; + } + catch + { + return false; + } + } + } + public bool IsInSlideShow + { + get + { + try + { + if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false; + return PPTApplication.SlideShowWindows?.Count > 0; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + return false; + } + catch + { + return false; + } + } + } + public bool IsSupportWPS { get; set; } = false; + #endregion + + #region Private Fields + private Timer _connectionCheckTimer; + private Timer _wpsProcessCheckTimer; + private Process _wpsProcess; + private bool _hasWpsProcessId; + private DateTime _wpsProcessRecordTime = DateTime.MinValue; + private int _wpsProcessCheckCount; + private WpsWindowInfo _lastForegroundWpsWindow; + private DateTime _lastWindowCheckTime = DateTime.MinValue; + private readonly object _lockObject = new object(); + private bool _disposed = false; + #endregion + + #region Constructor & Initialization + public PPTManager() + { + InitializeConnectionTimer(); + } + + private void InitializeConnectionTimer() + { + _connectionCheckTimer = new Timer(500); + _connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed; + _connectionCheckTimer.AutoReset = true; + } + + public void StartMonitoring() + { + if (!_disposed) + { + _connectionCheckTimer?.Start(); + LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace); + } + } + + public void StopMonitoring() + { + _connectionCheckTimer?.Stop(); + DisconnectFromPPT(); + LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace); + } + #endregion + + #region Connection Management + private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e) + { + try + { + CheckAndConnectToPPT(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PPT连接检查失败: {ex}", LogHelper.LogType.Error); + } + } + + private void CheckAndConnectToPPT() + { + lock (_lockObject) + { + try + { + // 尝试连接到PowerPoint + var pptApp = TryConnectToPowerPoint(); + if (pptApp == null && IsSupportWPS) + { + // 如果PowerPoint连接失败且支持WPS,尝试连接WPS + pptApp = TryConnectToWPS(); + } + + if (pptApp != null && !IsConnected) + { + // 有可用的PPT/WPS应用程序且当前未连接,建立连接 + ConnectToPPT(pptApp); + } + else if (pptApp == null && IsConnected) + { + // 没有可用的PPT/WPS应用程序但当前显示已连接,断开连接 + DisconnectFromPPT(); + } + else if (pptApp == null && PPTApplication != null) + { + // PPT/WPS应用程序不可用但PPTApplication对象仍存在,清理无效连接 + DisconnectFromPPT(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PPT连接检查异常: {ex}", LogHelper.LogType.Error); + if (PPTApplication != null) + { + DisconnectFromPPT(); + } + } + } + } + + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint() + { + try + { + var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); + + // 验证COM对象是否有效 + if (pptApp != null && Marshal.IsComObject(pptApp)) + { + // 尝试访问一个简单的属性来验证连接 + var _ = pptApp.Name; + return pptApp; + } + return null; + } + 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) + { + // COM对象类型转换失败 + return null; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning); + return null; + } + } + + private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPS() + { + try + { + var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); + + // 验证COM对象是否有效 + if (wpsApp != null && Marshal.IsComObject(wpsApp)) + { + // 尝试访问一个简单的属性来验证连接 + var _ = wpsApp.Name; + return wpsApp; + } + return null; + } + catch (COMException ex) + { + var hr = (uint)ex.HResult; + // 忽略常见的WPS连接错误: + // 0x800401E3: 操作无法使用 + // 0x80004005: 未指定错误 + // 0x800706B5: RPC服务器不可用 + // 0x8001010E: 应用程序调用一个已为另一线程整理的接口 + // 0x800401F3: 无效的类字符串(WPS未安装或COM组件未注册) + if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3) + { + LogHelper.WriteLogToFile($"连接WPS失败: {ex}", LogHelper.LogType.Warning); + } + return null; + } + catch (InvalidCastException) + { + // COM对象类型转换失败 + return null; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连接WPS时发生意外错误: {ex}", LogHelper.LogType.Warning); + return null; + } + } + + private void ConnectToPPT(Microsoft.Office.Interop.PowerPoint.Application pptApp) + { + try + { + PPTApplication = pptApp; + + // 在主线程中注册事件,确保COM对象在正确的线程中 + Application.Current?.Dispatcher?.Invoke(() => + { + try + { + PPTApplication.PresentationOpen += OnPresentationOpen; + PPTApplication.PresentationClose += OnPresentationClose; + PPTApplication.SlideShowBegin += OnSlideShowBegin; + PPTApplication.SlideShowNextSlide += OnSlideShowNextSlide; + PPTApplication.SlideShowEnd += OnSlideShowEnd; + + LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"PPT事件注册失败: {ex}", LogHelper.LogType.Error); + throw; // 重新抛出异常,让外层处理 + } + }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(2)); + + // 获取当前演示文稿信息 + UpdateCurrentPresentationInfo(); + + // 停止连接检查定时器 + _connectionCheckTimer?.Stop(); + + // 触发连接成功事件 + PPTConnectionChanged?.Invoke(true); + + LogHelper.WriteLogToFile("成功连接到PPT应用程序", LogHelper.LogType.Event); + + // 如果已经在放映状态,立即触发放映开始事件 + if (IsInSlideShow) + { + OnSlideShowBegin(PPTApplication.SlideShowWindows[1]); + } + else if (CurrentPresentation != null) + { + OnPresentationOpen(CurrentPresentation); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连接PPT应用程序失败: {ex}", LogHelper.LogType.Error); + PPTApplication = null; + } + } + + private void DisconnectFromPPT() + { + try + { + if (PPTApplication != null) + { + // 取消事件注册 - 使用更安全的方式 + try + { + // 检查COM对象是否仍然有效 + if (Marshal.IsComObject(PPTApplication)) + { + // 尝试在主线程中取消事件注册 + Application.Current?.Dispatcher?.Invoke(() => + { + try + { + PPTApplication.PresentationOpen -= OnPresentationOpen; + PPTApplication.PresentationClose -= OnPresentationClose; + PPTApplication.SlideShowBegin -= OnSlideShowBegin; + PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide; + PPTApplication.SlideShowEnd -= OnSlideShowEnd; + } + 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) + { + // COM对象类型转换失败,通常是因为对象已经被释放 + LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace); + } + }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(1)); + } + } + catch (Exception ex) + { + // 记录但不抛出异常,确保清理过程能够继续 + LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning); + } + + // 清理引用 + PPTApplication = null; + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + + // 重新启动连接检查定时器 + _connectionCheckTimer?.Start(); + + // 触发连接断开事件 + PPTConnectionChanged?.Invoke(false); + + LogHelper.WriteLogToFile("已断开PPT连接", LogHelper.LogType.Event); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error); + } + } + + private void UpdateCurrentPresentationInfo() + { + try + { + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + // 检查是否有活动的演示文稿 + try + { + var activePresentation = PPTApplication.ActivePresentation; + if (activePresentation != null) + { + CurrentPresentation = activePresentation; + CurrentSlides = CurrentPresentation.Slides; + SlidesCount = CurrentSlides.Count; + + // 获取当前幻灯片 + try + { + if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0) + { + CurrentSlide = PPTApplication.SlideShowWindows[1].View.Slide; + } + else if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0) + { + CurrentSlide = CurrentSlides[PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber]; + } + else if (SlidesCount > 0) + { + // 如果获取失败,使用第一张幻灯片 + CurrentSlide = CurrentSlides[1]; + } + } + catch (COMException comEx) + { + // COM异常,尝试使用第一张幻灯片 + var hr = (uint)comEx.HResult; + if (hr != 0x8001010E && hr != 0x80004005) + { + LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning); + } + + if (SlidesCount > 0) + { + CurrentSlide = CurrentSlides[1]; + } + } + } + else + { + // 没有活动演示文稿,清理状态 + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // 常见的COM错误,可能是没有活动演示文稿 + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + else + { + LogHelper.WriteLogToFile($"访问活动演示文稿失败: {comEx}", LogHelper.LogType.Warning); + } + } + } + else + { + // PPT应用程序无效,清理状态 + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error); + // 发生异常时清理状态 + CurrentPresentation = null; + CurrentSlides = null; + CurrentSlide = null; + SlidesCount = 0; + } + } + #endregion + + #region Event Handlers + private void OnPresentationOpen(Presentation pres) + { + try + { + UpdateCurrentPresentationInfo(); + PresentationOpen?.Invoke(pres); + LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnPresentationClose(Presentation pres) + { + try + { + PresentationClose?.Invoke(pres); + LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event); + + // 重新启动连接检查 + _connectionCheckTimer?.Start(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowBegin(SlideShowWindow wn) + { + try + { + UpdateCurrentPresentationInfo(); + SlideShowBegin?.Invoke(wn); + LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowNextSlide(SlideShowWindow wn) + { + try + { + UpdateCurrentPresentationInfo(); + SlideShowNextSlide?.Invoke(wn); + LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private void OnSlideShowEnd(Presentation pres) + { + try + { + // 记录WPS进程用于后续管理 + if (IsSupportWPS && PPTApplication != null) + { + RecordWpsProcessForManagement(); + } + + SlideShowEnd?.Invoke(pres); + LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error); + } + } + #endregion + + #region Public Methods + public bool TryNavigateToSlide(int slideNumber) + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + slideShowWindow.View.GotoSlide(slideNumber); + return true; + } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public bool TryNavigateNext() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + slideShowWindow.Activate(); + slideShowWindow.View.Next(); + return true; + } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到下一页失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public bool TryNavigatePrevious() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + slideShowWindow.Activate(); + slideShowWindow.View.Previous(); + return true; + } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换到上一页失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public bool TryEndSlideShow() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication)) return false; + + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + slideShowWindow.View.Exit(); + return true; + } + return false; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"结束幻灯片放映失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public bool TryStartSlideShow() + { + try + { + if (!IsConnected || CurrentPresentation == null || PPTApplication == null) return false; + if (!Marshal.IsComObject(PPTApplication) || !Marshal.IsComObject(CurrentPresentation)) return false; + + CurrentPresentation.SlideShowSettings.Run(); + return true; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + public int GetCurrentSlideNumber() + { + try + { + if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0; + if (!Marshal.IsComObject(PPTApplication)) return 0; + + return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning); + return 0; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error); + return 0; + } + } + + public string GetPresentationName() + { + try + { + if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; + + return CurrentPresentation.Name ?? ""; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取演示文稿名称失败: {comEx.Message}", LogHelper.LogType.Warning); + return ""; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取演示文稿名称失败: {ex}", LogHelper.LogType.Error); + return ""; + } + } + + public string GetPresentationPath() + { + try + { + if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; + + return CurrentPresentation.FullName ?? ""; + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"获取演示文稿路径失败: {comEx.Message}", LogHelper.LogType.Warning); + return ""; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取演示文稿路径失败: {ex}", LogHelper.LogType.Error); + return ""; + } + } + + public bool TryShowSlideNavigation() + { + try + { + LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace); + + if (!IsConnected || !IsInSlideShow || PPTApplication == null) + { + LogHelper.WriteLogToFile("PPT未连接或未在放映状态", LogHelper.LogType.Warning); + return false; + } + + if (!Marshal.IsComObject(PPTApplication)) + { + LogHelper.WriteLogToFile("PPT应用程序COM对象无效", LogHelper.LogType.Warning); + return false; + } + + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow == null) + { + LogHelper.WriteLogToFile("幻灯片放映窗口为空", LogHelper.LogType.Warning); + return false; + } + + // 检查是否为WPS,WPS可能不支持SlideNavigation + try + { + if (slideShowWindow.SlideNavigation != null) + { + slideShowWindow.SlideNavigation.Visible = true; + LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event); + return true; + } + else + { + LogHelper.WriteLogToFile("SlideNavigation对象为空,可能是WPS不支持此功能", LogHelper.LogType.Warning); + return false; + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + // 0x80020006: 未知名称 - WPS可能不支持SlideNavigation + if (hr == 0x80020006) + { + LogHelper.WriteLogToFile("WPS不支持SlideNavigation功能", LogHelper.LogType.Warning); + return false; + } + throw; // 重新抛出其他COM异常 + } + } + catch (COMException comEx) + { + var hr = (uint)comEx.HResult; + if (hr == 0x8001010E || hr == 0x80004005) + { + // COM对象已失效,触发断开连接 + DisconnectFromPPT(); + } + LogHelper.WriteLogToFile($"显示幻灯片导航COM异常: {comEx.Message} (HRESULT: 0x{hr:X8})", LogHelper.LogType.Error); + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示幻灯片导航失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + #endregion + + #region WPS Process Management + private void RecordWpsProcessForManagement() + { + if (!IsSupportWPS || PPTApplication == null) return; + + try + { + Process wpsProcess = null; + + // 方法1:通过应用程序路径检测 + if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") || + PPTApplication.Path.Contains("WPS Office\\")) + { + uint processId; + GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId); + wpsProcess = Process.GetProcessById((int)processId); + } + + // 方法2:通过前台窗口检测 + if (wpsProcess == null) + { + var foregroundWpsWindow = GetForegroundWpsWindow(); + if (foregroundWpsWindow != null) + { + wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId); + } + } + + // 方法3:通过进程名检测 + if (wpsProcess == null) + { + var wpsProcesses = GetWpsProcesses(); + if (wpsProcesses.Count > 0) + { + wpsProcess = wpsProcesses.First(); + } + } + + if (wpsProcess != null) + { + _wpsProcess = wpsProcess; + _hasWpsProcessId = true; + _wpsProcessRecordTime = DateTime.Now; + _wpsProcessCheckCount = 0; + LogHelper.WriteLogToFile($"成功记录 WPS 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace); + + StartWpsProcessCheckTimer(); + } + else + { + LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error); + } + } + + private void StartWpsProcessCheckTimer() + { + if (!IsSupportWPS) return; + + if (_wpsProcessCheckTimer != null) + { + _wpsProcessCheckTimer.Stop(); + _wpsProcessCheckTimer.Dispose(); + } + + // 优化:增加检查间隔到2秒,减少性能开销 + _wpsProcessCheckTimer = new Timer(2000); + _wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed; + _wpsProcessCheckTimer.Start(); + LogHelper.WriteLogToFile("启动 WPS 进程检测定时器", LogHelper.LogType.Trace); + } + + private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e) + { + if (!IsSupportWPS) + { + StopWpsProcessCheckTimer(); + return; + } + + try + { + if (_wpsProcess == null || !_hasWpsProcessId) + { + StopWpsProcessCheckTimer(); + return; + } + + _wpsProcess.Refresh(); + _wpsProcessCheckCount++; + + if (_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS 进程已正常关闭", LogHelper.LogType.Trace); + StopWpsProcessCheckTimer(); + return; + } + + + // 检查前台WPS窗口是否存在(优化版) + bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized(); + + if (isForegroundWpsWindowActive) + { + if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志 + { + LogHelper.WriteLogToFile($"WPS窗口仍然活跃,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace); + } + return; + } + + // 优化:多重验证确保准确性 + if (!PerformMultipleWpsWindowChecks()) + { + LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace); + return; + } + + // 前台窗口已消失,准备结束WPS进程 + LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event); + + // 安全结束WPS进程 + SafeTerminateWpsProcess(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error); + StopWpsProcessCheckTimer(); + } + } + + /// + /// 优化版的前台WPS窗口检测,减少性能开销 + /// + private bool IsForegroundWpsWindowStillActiveOptimized() + { + try + { + // 快速检查:直接检查前台窗口 + var foregroundWindow = GetForegroundWindow(); + if (foregroundWindow == IntPtr.Zero) return false; + + // 获取前台窗口的进程ID + uint processId; + GetWindowThreadProcessId(foregroundWindow, out processId); + + // 如果前台窗口就是我们监控的WPS进程,则认为仍然活跃 + if (processId == _wpsProcess?.Id) + { + return true; + } + + // 检查是否为WPS相关窗口 + var windowInfo = GetWindowInfo(foregroundWindow); + return IsWpsWindow(windowInfo); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 多重验证WPS窗口状态,确保查杀准确性 + /// + private bool PerformMultipleWpsWindowChecks() + { + try + { + // 第一重验证:等待1秒后再次检查 + Thread.Sleep(1000); + if (IsForegroundWpsWindowStillActiveOptimized()) + { + LogHelper.WriteLogToFile("第一重验证:WPS窗口仍然存在", LogHelper.LogType.Trace); + return false; + } + + // 第二重验证:检查所有WPS进程的窗口 + var wpsProcesses = GetWpsProcesses(); + foreach (var process in wpsProcesses) + { + if (process.Id == _wpsProcess?.Id) continue; // 跳过当前监控的进程 + + var windows = GetWpsWindowsByProcess(process.Id); + if (windows.Any(w => w.IsVisible && !w.IsMinimized)) + { + LogHelper.WriteLogToFile($"第二重验证:发现其他WPS进程{process.Id}有活跃窗口", LogHelper.LogType.Trace); + return false; + } + } + + // 第三重验证:检查任务栏中的WPS窗口 + if (HasWpsWindowInTaskbar()) + { + LogHelper.WriteLogToFile("第三重验证:任务栏中仍有WPS窗口", LogHelper.LogType.Trace); + return false; + } + + LogHelper.WriteLogToFile("多重验证完成:确认WPS窗口已全部消失", LogHelper.LogType.Event); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"多重验证失败: {ex}", LogHelper.LogType.Error); + return false; // 出错时保守处理,不进行查杀 + } + } + + /// + /// 检查任务栏中是否有WPS窗口 + /// + private bool HasWpsWindowInTaskbar() + { + try + { + var allWindows = new List(); + + EnumWindows((hWnd, lParam) => + { + try + { + if (IsWindow(hWnd) && IsWindowVisible(hWnd)) + { + var windowInfo = GetWindowInfo(hWnd); + if (IsWpsWindow(windowInfo) && !string.IsNullOrEmpty(windowInfo.Title)) + { + allWindows.Add(windowInfo); + } + } + } + catch { } + return true; + }, IntPtr.Zero); + + return allWindows.Count > 0; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查任务栏WPS窗口失败: {ex}", LogHelper.LogType.Error); + return true; // 出错时保守处理,认为仍有窗口 + } + } + + /// + /// 安全地结束WPS进程 - 通过释放PPTCOM对象 + /// + private void SafeTerminateWpsProcess() + { + try + { + if (_wpsProcess == null || _wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS进程已经结束,无需查杀", LogHelper.LogType.Trace); + StopWpsProcessCheckTimer(); + return; + } + + LogHelper.WriteLogToFile($"开始通过释放PPTCOM对象安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event); + + // 第一步:释放 pptActWindow 对象(SlideShowWindow) + SlideShowWindow pptActWindow = null; + try + { + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + if (PPTApplication.SlideShowWindows?.Count > 0) + { + pptActWindow = PPTApplication.SlideShowWindows[1]; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取SlideShowWindow对象时发生异常: {ex}", LogHelper.LogType.Warning); + } + + if (pptActWindow != null) + { + Marshal.ReleaseComObject(pptActWindow); + pptActWindow = null; + LogHelper.WriteLogToFile("已释放pptActWindow对象", LogHelper.LogType.Trace); + } + + // 第二步:释放 pptActDoc 对象(CurrentPresentation) + Presentation pptActDoc = CurrentPresentation; + if (pptActDoc != null) + { + Marshal.ReleaseComObject(pptActDoc); + pptActDoc = null; + CurrentPresentation = null; + LogHelper.WriteLogToFile("已释放pptActDoc对象", LogHelper.LogType.Trace); + } + + // 第三步:释放 pptApp 对象(PPTApplication) + Microsoft.Office.Interop.PowerPoint.Application pptApp = PPTApplication; + if (pptApp != null) + { + Marshal.ReleaseComObject(pptApp); + pptApp = null; + PPTApplication = null; + LogHelper.WriteLogToFile("已释放pptApp对象", LogHelper.LogType.Trace); + } + + // 第四步:强制垃圾回收及等待终结器执行 + LogHelper.WriteLogToFile("执行强制垃圾回收", LogHelper.LogType.Trace); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // 等待一段时间让COM对象完全释放 + Thread.Sleep(1000); + + // 检查进程是否已经结束 + try + { + _wpsProcess.Refresh(); + if (_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("WPS进程已通过COM对象释放成功结束", LogHelper.LogType.Event); + StopWpsProcessCheckTimer(); + return; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查WPS进程状态失败: {ex}", LogHelper.LogType.Warning); + } + + // 备用方案:如果COM对象释放后进程仍未结束,尝试关闭 + try + { + LogHelper.WriteLogToFile("COM对象释放后进程仍在运行,尝试关闭", LogHelper.LogType.Warning); + _wpsProcess.CloseMainWindow(); + if (_wpsProcess.WaitForExit(3000)) // 等待3秒 + { + LogHelper.WriteLogToFile("WPS进程已关闭", LogHelper.LogType.Event); + StopWpsProcessCheckTimer(); + return; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"关闭WPS进程失败: {ex}", LogHelper.LogType.Warning); + } + + // 最后备用方案:强制结束进程 + try + { + if (!_wpsProcess.HasExited) + { + LogHelper.WriteLogToFile("所有方法都失败,强制结束WPS进程", LogHelper.LogType.Warning); + _wpsProcess.Kill(); + LogHelper.WriteLogToFile("WPS进程已强制结束", LogHelper.LogType.Event); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"强制结束WPS进程失败: {ex}", LogHelper.LogType.Error); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"安全结束WPS进程时发生异常: {ex}", LogHelper.LogType.Error); + } + finally + { + // 确保清理状态 + if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide)) + { + try { Marshal.ReleaseComObject(CurrentSlide); } catch { } + } + if (CurrentSlides != null && Marshal.IsComObject(CurrentSlides)) + { + try { Marshal.ReleaseComObject(CurrentSlides); } catch { } + } + if (CurrentPresentation != null && Marshal.IsComObject(CurrentPresentation)) + { + try { Marshal.ReleaseComObject(CurrentPresentation); } catch { } + } + if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) + { + try { Marshal.ReleaseComObject(PPTApplication); } catch { } + } + + CurrentSlide = null; + CurrentSlides = null; + CurrentPresentation = null; + PPTApplication = null; + SlidesCount = 0; + StopWpsProcessCheckTimer(); + + // 重新启动连接检查定时器,以便能够检测新的WPS实例 + _connectionCheckTimer?.Start(); + + // 触发连接断开事件 + PPTConnectionChanged?.Invoke(false); + + LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event); + } + } + + + + private void StopWpsProcessCheckTimer() + { + if (_wpsProcessCheckTimer != null) + { + _wpsProcessCheckTimer.Stop(); + _wpsProcessCheckTimer.Dispose(); + _wpsProcessCheckTimer = null; + } + + _wpsProcess = null; + _hasWpsProcessId = false; + _wpsProcessRecordTime = DateTime.MinValue; + _wpsProcessCheckCount = 0; + _lastForegroundWpsWindow = null; + _lastWindowCheckTime = DateTime.MinValue; + LogHelper.WriteLogToFile("停止 WPS 进程检测定时器", LogHelper.LogType.Trace); + } + #endregion + + #region WPS Window Detection + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsZoomed(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Width => Right - Left; + public int Height => Bottom - Top; + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + private class WpsWindowInfo + { + public IntPtr Handle { get; set; } + public string Title { get; set; } + public string ClassName { get; set; } + public bool IsVisible { get; set; } + public bool IsMinimized { get; set; } + public bool IsMaximized { get; set; } + public RECT Rect { get; set; } + public uint ProcessId { get; set; } + public string ProcessName { get; set; } + } + + private WpsWindowInfo GetForegroundWpsWindow() + { + try + { + var foregroundHwnd = GetForegroundWindow(); + if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd)) + { + var windowInfo = GetWindowInfo(foregroundHwnd); + if (IsWpsWindow(windowInfo)) + { + return windowInfo; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error); + } + return null; + } + + private WpsWindowInfo GetWindowInfo(IntPtr hWnd) + { + var windowInfo = new WpsWindowInfo + { + Handle = hWnd, + IsVisible = IsWindowVisible(hWnd), + IsMinimized = IsIconic(hWnd), + IsMaximized = IsZoomed(hWnd) + }; + + // 获取窗口标题 + var windowTitle = new StringBuilder(256); + GetWindowText(hWnd, windowTitle, 256); + windowInfo.Title = windowTitle.ToString().Trim(); + + // 获取窗口类名 + var className = new StringBuilder(256); + GetClassName(hWnd, className, 256); + windowInfo.ClassName = className.ToString().Trim(); + + // 获取窗口位置 + GetWindowRect(hWnd, out RECT rect); + windowInfo.Rect = rect; + + // 获取进程ID + uint processId; + GetWindowThreadProcessId(hWnd, out processId); + windowInfo.ProcessId = processId; + + // 获取进程名 + windowInfo.ProcessName = ""; + try + { + var proc = Process.GetProcessById((int)processId); + windowInfo.ProcessName = proc.ProcessName.ToLower(); + } + catch { } + + return windowInfo; + } + + private bool IsWpsWindow(WpsWindowInfo windowInfo) + { + if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName)) + return false; + + var title = windowInfo.Title.ToLower(); + var className = windowInfo.ClassName.ToLower(); + var processName = windowInfo.ProcessName ?? ""; + + // WPS相关关键词 + var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" }; + // 微软Office相关进程名 + var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" }; + + // 只要进程名是微软Office,直接排除 + if (msOfficeProcess.Any(keyword => processName.Contains(keyword))) + return false; + + // 只要进程名是WPS/WPP/Kingsoft,直接通过 + if (wpsKeywords.Any(keyword => processName.Contains(keyword))) + return true; + + // 标题或类名包含WPS相关关键词 + bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword)); + bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword)); + bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp"); + bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0; + + return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize; + } + + private List GetWpsProcesses() + { + var wpsProcesses = new List(); + try + { + var allProcesses = Process.GetProcesses(); + foreach (var process in allProcesses) + { + try + { + var pname = process.ProcessName.ToLower(); + + // 精确的WPS进程名匹配,避免误杀 + var exactWpsNames = new[] { "wps", "wpp", "et", "wpspdf", "wpsoffice" }; + var microsoftOfficeNames = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "winword", "msaccess" }; + + // 排除微软Office进程 + if (microsoftOfficeNames.Any(name => pname.Contains(name))) + { + continue; + } + + // 精确匹配WPS进程名 + bool isWpsProcess = exactWpsNames.Any(name => pname.Equals(name) || pname.StartsWith(name + ".")); + + // 额外验证:检查进程路径 + if (isWpsProcess) + { + try + { + var processPath = process.MainModule?.FileName?.ToLower() ?? ""; + if (processPath.Contains("kingsoft") || processPath.Contains("wps office")) + { + wpsProcesses.Add(process); + LogHelper.WriteLogToFile($"检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); + } + } + catch + { + // 无法访问进程路径时,基于进程名判断 + if (exactWpsNames.Contains(pname)) + { + wpsProcesses.Add(process); + LogHelper.WriteLogToFile($"基于进程名检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error); + } + + LogHelper.WriteLogToFile($"共检测到{wpsProcesses.Count}个WPS进程", LogHelper.LogType.Trace); + return wpsProcesses; + } + + private bool IsForegroundWpsWindowStillActive() + { + try + { + var currentTime = DateTime.Now; + var currentForegroundWindow = GetForegroundWpsWindow(); + + // 检查窗口状态是否发生变化 + if (_lastForegroundWpsWindow != null && currentForegroundWindow != null) + { + if (_lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle || + _lastForegroundWpsWindow.Title != currentForegroundWindow.Title) + { + LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {_lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace); + } + } + else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null) + { + LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace); + } + else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null) + { + LogHelper.WriteLogToFile($"前台WPS窗口已消失: {_lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace); + } + + // 更新记录 + _lastForegroundWpsWindow = currentForegroundWindow; + _lastWindowCheckTime = currentTime; + + if (currentForegroundWindow != null) + { + if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle)) + { + return true; + } + } + + // 检查所有WPS进程的活跃窗口 + var wpsProcesses = GetWpsProcesses(); + foreach (var process in wpsProcesses) + { + var windows = GetWpsWindowsByProcess(process.Id); + if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow())) + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error); + return false; + } + } + + private List GetWpsWindowsByProcess(int processId) + { + var wpsWindows = new List(); + + try + { + EnumWindows((hWnd, lParam) => + { + try + { + if (!IsWindow(hWnd)) return true; + + uint windowProcessId; + GetWindowThreadProcessId(hWnd, out windowProcessId); + + if ((int)windowProcessId == processId) + { + var windowInfo = GetWindowInfo(hWnd); + if (IsWpsWindow(windowInfo)) + { + wpsWindows.Add(windowInfo); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error); + } + return true; + }, IntPtr.Zero); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error); + } + + return wpsWindows; + } + #endregion + + #region Dispose + public void Dispose() + { + if (!_disposed) + { + StopMonitoring(); + StopWpsProcessCheckTimer(); + + _connectionCheckTimer?.Dispose(); + _wpsProcessCheckTimer?.Dispose(); + + _disposed = true; + } + } + #endregion + } +} diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs new file mode 100644 index 00000000..3bd1713d --- /dev/null +++ b/Ink Canvas/Helpers/PPTUIManager.cs @@ -0,0 +1,435 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Threading; + +namespace Ink_Canvas.Helpers +{ + /// + /// PPT UI管理器 - 统一管理PPT相关的UI更新和样式设置 + /// + public class PPTUIManager + { + #region Properties + public bool ShowPPTButton { get; set; } = true; + public int PPTButtonsDisplayOption { get; set; } = 2222; + public int PPTSButtonsOption { get; set; } = 221; + public int PPTBButtonsOption { get; set; } = 121; + public int PPTLSButtonPosition { get; set; } = 0; + public int PPTRSButtonPosition { get; set; } = 0; + public bool EnablePPTButtonPageClickable { get; set; } = true; + #endregion + + #region Private Fields + private readonly MainWindow _mainWindow; + private readonly Dispatcher _dispatcher; + #endregion + + #region Constructor + public PPTUIManager(MainWindow mainWindow) + { + _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); + _dispatcher = _mainWindow.Dispatcher; + } + #endregion + + #region Public Methods + /// + /// 更新PPT连接状态UI + /// + public void UpdateConnectionStatus(bool isConnected) + { + _dispatcher.InvokeAsync(() => + { + try + { + if (isConnected) + { + _mainWindow.StackPanelPPTControls.Visibility = Visibility.Visible; + _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible; + } + else + { + _mainWindow.StackPanelPPTControls.Visibility = Visibility.Collapsed; + _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed; + _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; + HideAllNavigationPanels(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新PPT连接状态UI失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 更新幻灯片放映状态UI + /// + public void UpdateSlideShowStatus(bool isInSlideShow, int currentSlide = 0, int totalSlides = 0) + { + _dispatcher.InvokeAsync(() => + { + try + { + if (isInSlideShow) + { + _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed; + _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible; + + if (currentSlide > 0 && totalSlides > 0) + { + _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); + _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; + } + + UpdateNavigationPanelsVisibility(); + UpdateNavigationButtonStyles(); + } + else + { + _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible; + _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; + HideAllNavigationPanels(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新幻灯片放映状态UI失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 更新当前页码显示 + /// + public void UpdateCurrentSlideNumber(int currentSlide, int totalSlides) + { + _dispatcher.InvokeAsync(() => + { + try + { + _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); + _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新页码显示失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 更新导航面板显示状态 + /// + public void UpdateNavigationPanelsVisibility() + { + _dispatcher.InvokeAsync(() => + { + try + { + // 检查是否应该显示PPT按钮 + bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible; + + if (!shouldShowButtons) + { + HideAllNavigationPanels(); + return; + } + + // 设置侧边按钮位置 + _mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2); + _mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2); + + // 根据显示选项设置面板可见性 + var displayOption = PPTButtonsDisplayOption.ToString(); + if (displayOption.Length >= 4) + { + var options = displayOption.ToCharArray(); + + // 左下角面板 + if (options[0] == '2') + AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftBottomPanelForPPTNavigation); + else + _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; + + // 右下角面板 + if (options[1] == '2') + AnimationsHelper.ShowWithFadeIn(_mainWindow.RightBottomPanelForPPTNavigation); + else + _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; + + // 左侧面板 + if (options[2] == '2') + AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftSidePanelForPPTNavigation); + else + _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + + // 右侧面板 + if (options[3] == '2') + AnimationsHelper.ShowWithFadeIn(_mainWindow.RightSidePanelForPPTNavigation); + else + _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新导航面板显示状态失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 更新导航按钮样式 + /// + public void UpdateNavigationButtonStyles() + { + _dispatcher.InvokeAsync(() => + { + try + { + UpdateSideButtonStyles(); + UpdateBottomButtonStyles(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新导航按钮样式失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 隐藏所有导航面板 + /// + public void HideAllNavigationPanels() + { + _dispatcher.InvokeAsync(() => + { + try + { + _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; + _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; + _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"隐藏导航面板失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 显示/隐藏侧边栏退出按钮 + /// + public void UpdateSidebarExitButtons(bool show) + { + _dispatcher.InvokeAsync(() => + { + try + { + var visibility = show ? Visibility.Visible : Visibility.Collapsed; + + if (_mainWindow.BtnExitPptFromSidebarLeft != null) + _mainWindow.BtnExitPptFromSidebarLeft.Visibility = visibility; + + if (_mainWindow.BtnExitPptFromSidebarRight != null) + _mainWindow.BtnExitPptFromSidebarRight.Visibility = visibility; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新侧边栏退出按钮失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 设置浮动栏透明度 + /// + public void SetFloatingBarOpacity(double opacity) + { + _dispatcher.InvokeAsync(() => + { + try + { + _mainWindow.ViewboxFloatingBar.Opacity = opacity; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置浮动栏透明度失败: {ex}", LogHelper.LogType.Error); + } + }); + } + + /// + /// 设置主面板边距 + /// + public void SetMainPanelMargin(Thickness margin) + { + _dispatcher.InvokeAsync(() => + { + try + { + _mainWindow.ViewBoxStackPanelMain.Margin = margin; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置主面板边距失败: {ex}", LogHelper.LogType.Error); + } + }); + } + #endregion + + #region Private Methods + private void UpdateSideButtonStyles() + { + try + { + var sideOption = PPTSButtonsOption.ToString(); + if (sideOption.Length < 3) return; + + var options = sideOption.ToCharArray(); + + // 页码按钮显示 + var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; + _mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility; + _mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility; + + // 透明度设置 + var opacity = options[1] == '2' ? 0.5 : 1.0; + _mainWindow.PPTBtnLSBorder.Opacity = opacity; + _mainWindow.PPTBtnRSBorder.Opacity = opacity; + + // 颜色主题 + bool isDarkTheme = options[2] == '2'; + ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新侧边按钮样式失败: {ex}", LogHelper.LogType.Error); + } + } + + private void UpdateBottomButtonStyles() + { + try + { + var bottomOption = PPTBButtonsOption.ToString(); + if (bottomOption.Length < 3) return; + + var options = bottomOption.ToCharArray(); + + // 页码按钮显示 + var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; + _mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility; + _mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility; + + // 透明度设置 + var opacity = options[1] == '2' ? 0.5 : 1.0; + _mainWindow.PPTBtnLBBorder.Opacity = opacity; + _mainWindow.PPTBtnRBBorder.Opacity = opacity; + + // 颜色主题 + bool isDarkTheme = options[2] == '2'; + ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error); + } + } + + private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton) + { + try + { + Color backgroundColor, borderColor, foregroundColor, feedbackColor; + + if (isDarkTheme) + { + backgroundColor = Color.FromRgb(39, 39, 42); + borderColor = Color.FromRgb(82, 82, 91); + foregroundColor = Colors.White; + feedbackColor = Colors.White; + } + else + { + backgroundColor = Color.FromRgb(244, 244, 245); + borderColor = Color.FromRgb(161, 161, 170); + foregroundColor = Color.FromRgb(39, 39, 42); + feedbackColor = Color.FromRgb(24, 24, 27); + } + + // 应用背景和边框颜色 + var backgroundBrush = new SolidColorBrush(backgroundColor); + var borderBrush = new SolidColorBrush(borderColor); + + leftBorder.Background = backgroundBrush; + leftBorder.BorderBrush = borderBrush; + rightBorder.Background = backgroundBrush; + rightBorder.BorderBrush = borderBrush; + + // 应用图标和文字颜色 + var foregroundBrush = new SolidColorBrush(foregroundColor); + var feedbackBrush = new SolidColorBrush(feedbackColor); + + if (isSideButton) + { + ApplySideButtonColors(foregroundBrush, feedbackBrush); + } + else + { + ApplyBottomButtonColors(foregroundBrush, feedbackBrush); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error); + } + } + + private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) + { + // 图标颜色 + _mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush; + + // 反馈背景颜色 + _mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush; + + // 文字颜色 + TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush); + TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush); + } + + private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) + { + // 图标颜色 + _mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush; + _mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush; + + // 反馈背景颜色 + _mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush; + _mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush; + + // 文字颜色 + TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush); + TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush); + } + #endregion + } +} diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 4c53cecb..31ad10da 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -113,15 +113,15 @@ .\IACore.dll - False + True .\IALoader.dll - False + True .\IAWinFX.dll - False + True @@ -143,10 +143,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index c96ff4b5..fcd72650 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -336,12 +336,21 @@ namespace Ink_Canvas { // 加载自定义背景颜色 LoadCustomBackgroundColor(); - + // 注册设置面板滚动事件 if (SettingsPanelScrollViewer != null) { SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; } + + // 初始化PPT管理器 + InitializePPTManagers(); + + // 如果启用PPT支持,开始监控 + if (Settings.PowerPointSettings.PowerPointSupport) + { + StartPPTMonitoring(); + } // HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow(); if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed; @@ -500,8 +509,11 @@ namespace Ink_Canvas { private void Window_Closed(object sender, EventArgs e) { SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged; + // 释放PPT管理器资源 + DisposePPTManagers(); + LogHelper.WriteLogToFile("Ink Canvas closed", LogHelper.LogType.Event); - + // 检查是否有待安装的更新 CheckPendingUpdates(); } diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index 3d3a0e98..de356541 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -24,14 +24,74 @@ namespace Ink_Canvas { // 保存每页白板图片信息 private void SaveStrokes(bool isBackupMain = false) { + // 确保画布上的所有UI元素都被保存到时间机器历史记录中 + var currentHistory = timeMachine.ExportTimeMachineHistory(); + var elementsInHistory = new HashSet(); + + // 收集已经在历史记录中的元素 + if (currentHistory != null) { + foreach (var h in currentHistory) { + if (h.CommitType == TimeMachineHistoryType.ElementInsert && + h.InsertedElement != null && + !h.StrokeHasBeenCleared) { + elementsInHistory.Add(h.InsertedElement); + } + } + } + + // 检查画布上的所有UI元素,确保它们都在历史记录中 + var missingElements = 0; + foreach (UIElement child in inkCanvas.Children) { + if (child is Image || child is MediaElement) { + if (!elementsInHistory.Contains(child)) { + timeMachine.CommitElementInsertHistory(child); + missingElements++; + } + } + } + + + // 确保画布上的所有墨迹都被保存 + if (inkCanvas.Strokes.Count > 0) { + // 检查是否有墨迹没有在时间机器历史记录中 + var strokesInHistory = new HashSet(); + if (currentHistory != null) { + foreach (var h in currentHistory) { + if (h.CommitType == TimeMachineHistoryType.UserInput && + h.CurrentStroke != null && + !h.StrokeHasBeenCleared) { + foreach (Stroke stroke in h.CurrentStroke) { + strokesInHistory.Add(stroke); + } + } + } + } + + // 收集没有在历史记录中的墨迹 + var missingStrokes = new StrokeCollection(); + foreach (Stroke stroke in inkCanvas.Strokes) { + if (!strokesInHistory.Contains(stroke)) { + missingStrokes.Add(stroke); + } + } + + if (missingStrokes.Count > 0) { + timeMachine.CommitStrokeUserInputHistory(missingStrokes); + } + } + if (isBackupMain) { var timeMachineHistory = timeMachine.ExportTimeMachineHistory(); TimeMachineHistories[0] = timeMachineHistory; timeMachine.ClearStrokeHistory(); + + } else { var timeMachineHistory = timeMachine.ExportTimeMachineHistory(); TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory; timeMachine.ClearStrokeHistory(); + + } } @@ -54,9 +114,11 @@ namespace Ink_Canvas { try { var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex; - // 先清空当前画布的所有内容(墨迹和图片) - // 这里必须清除图片,因为页面切换时需要完全重置画布状态 + // 先清空当前画布的墨迹 inkCanvas.Strokes.Clear(); + + // 清空当前画布的所有内容(墨迹和图片) + // 这里必须清除图片,因为页面切换时需要完全重置画布状态 inkCanvas.Children.Clear(); // 如果历史记录为空,直接返回(新页面或空页面) @@ -73,6 +135,11 @@ namespace Ink_Canvas { // 通过时间机器历史恢复所有内容(墨迹和图片) foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item); } + + // 确保选中状态被清除,因为我们切换了页面 + if (selectedUIElement != null) { + DeselectUIElement(); + } } catch { // ignored diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs index df09130b..1c06148a 100644 --- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; +using Ink_Canvas.Helpers; using Microsoft.Win32; namespace Ink_Canvas @@ -28,17 +29,10 @@ namespace Ink_Canvas string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); image.Name = timestamp; - // 新缩放逻辑:最大宽高为画布一半,并居中 - double maxWidth = inkCanvas.ActualWidth / 2; - double maxHeight = inkCanvas.ActualHeight / 2; - double scaleX = maxWidth / image.Width; - double scaleY = maxHeight / image.Height; - double scale = Math.Min(1, Math.Min(scaleX, scaleY)); - image.Width = image.Width * scale; - image.Height = image.Height * scale; - InkCanvas.SetLeft(image, (inkCanvas.ActualWidth - image.Width) / 2); - InkCanvas.SetTop(image, (inkCanvas.ActualHeight - image.Height) / 2); + CenterAndScaleElement(image); + InkCanvas.SetLeft(image, 0); + InkCanvas.SetTop(image, 0); inkCanvas.Children.Add(image); // 添加鼠标事件处理,使图片可以被选择 @@ -119,6 +113,8 @@ namespace Ink_Canvas if (mediaElement != null) { + CenterAndScaleElement(mediaElement); + InkCanvas.SetLeft(mediaElement, 0); InkCanvas.SetTop(mediaElement, 0); inkCanvas.Children.Add(mediaElement); @@ -256,5 +252,27 @@ namespace Ink_Canvas } #endregion + + private void CenterAndScaleElement(FrameworkElement element) + { + double maxWidth = SystemParameters.PrimaryScreenWidth / 2; + double maxHeight = SystemParameters.PrimaryScreenHeight / 2; + + double scaleX = maxWidth / element.Width; + double scaleY = maxHeight / element.Height; + double scale = Math.Min(scaleX, scaleY); + + TransformGroup transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(scale, scale)); + + double canvasWidth = inkCanvas.ActualWidth; + double canvasHeight = inkCanvas.ActualHeight; + double centerX = (canvasWidth - element.Width * scale) / 2; + double centerY = (canvasHeight - element.Height * scale) / 2; + + transformGroup.Children.Add(new TranslateTransform(centerX, centerY)); + + element.RenderTransform = transformGroup; + } } } diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 32206b45..bafc0805 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -672,7 +672,11 @@ namespace Ink_Canvas { if (Settings.Automation.IsAutoSaveStrokesAtClear && inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) { if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) - SaveScreenShot(true, $"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}"); + { + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + var presentationName = _pptManager?.GetPresentationName() ?? ""; + SaveScreenShot(true, $"{presentationName}/{currentSlide}_{DateTime.Now:HH-mm-ss}"); + } else SaveScreenShot(true); } @@ -1287,7 +1291,11 @@ namespace Ink_Canvas { if (inkCanvas.Strokes.Count > 0 && inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) { if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) - SaveScreenShot(true, $"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}"); + { + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + var presentationName = _pptManager?.GetPresentationName() ?? ""; + SaveScreenShot(true, $"{presentationName}/{currentSlide}_{DateTime.Now:HH-mm-ss}"); + } else SaveScreenShot(true); } @@ -1325,14 +1333,10 @@ namespace Ink_Canvas { if (currentMode != 0) { SaveStrokes(); - // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { - RestoreStrokes(true); - } else { - // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹 - TimeMachineHistories[0] = null; - timeMachine.ClearStrokeHistory(); - } + // 总是恢复备份墨迹,不管是否在PPT模式 + // PPT墨迹和白板墨迹应该分别管理,不应该互相影响 + RestoreStrokes(true); + LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace); } if (BtnSwitchTheme.Content.ToString() == "浅色") @@ -1650,14 +1654,10 @@ namespace Ink_Canvas { } public void BtnRestart_Click(object sender, RoutedEventArgs e) { - try { - Process.Start(System.Windows.Forms.Application.ExecutablePath, "-m"); - App.IsAppExitByUser = true; - CloseIsFromButton = true; - Application.Current.Shutdown(); - } catch (Exception ex) { - LogHelper.NewLog($"重启程序时出错: {ex.Message}"); - } + Process.Start(System.Windows.Forms.Application.ExecutablePath, "-m"); + App.IsAppExitByUser = true; + CloseIsFromButton = true; + Application.Current.Shutdown(); } private void SettingsOverlayClick(object sender, MouseButtonEventArgs e) { @@ -1780,26 +1780,13 @@ namespace Ink_Canvas { AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide); AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide); AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide); - - // 取消任何UI元素的选择 + DeselectUIElement(); SaveStrokes(true); ClearStrokes(true); - - // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { - LogHelper.WriteLogToFile("退出白板:当前在PPT放映模式,恢复备份墨迹", LogHelper.LogType.Trace); - RestoreStrokes(); - } else { - // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹 - LogHelper.WriteLogToFile("退出白板:当前不在PPT放映模式,清空备份以避免显示已结束PPT的墨迹", LogHelper.LogType.Trace); - TimeMachineHistories[0] = null; - timeMachine.ClearStrokeHistory(); - } - - // 退出白板时清空图片 - inkCanvas.Children.Clear(); + RestoreStrokes(true); + if (BtnSwitchTheme.Content.ToString() == "浅色") { BtnSwitch.Content = "黑板"; @@ -1837,18 +1824,7 @@ namespace Ink_Canvas { SaveStrokes(); ClearStrokes(true); - - // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { - RestoreStrokes(true); - } else { - // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹 - TimeMachineHistories[0] = null; - timeMachine.ClearStrokeHistory(); - } - - // 退出白板时清空图片 - inkCanvas.Children.Clear(); + RestoreStrokes(true); if (BtnSwitchTheme.Content.ToString() == "浅色") { BtnSwitch.Content = "黑板"; @@ -1883,14 +1859,9 @@ namespace Ink_Canvas { SaveStrokes(true); ClearStrokes(true); - // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { - RestoreStrokes(); - } else { - // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹 - TimeMachineHistories[0] = null; - timeMachine.ClearStrokeHistory(); - } + // 总是恢复备份墨迹,不管是否在PPT模式 + // PPT墨迹和白板墨迹应该分别管理,不应该互相影响 + RestoreStrokes(); BtnSwitch.Content = "屏幕"; if (BtnSwitchTheme.Content.ToString() == "浅色") { @@ -2058,17 +2029,10 @@ namespace Ink_Canvas { string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); image.Name = timestamp; - // 新缩放逻辑:最大宽高为画布一半,并居中 - double maxWidth = inkCanvas.ActualWidth / 2; - double maxHeight = inkCanvas.ActualHeight / 2; - double scaleX = maxWidth / image.Width; - double scaleY = maxHeight / image.Height; - double scale = Math.Min(1, Math.Min(scaleX, scaleY)); - image.Width = image.Width * scale; - image.Height = image.Height * scale; - InkCanvas.SetLeft(image, (inkCanvas.ActualWidth - image.Width) / 2); - InkCanvas.SetTop(image, (inkCanvas.ActualHeight - image.Height) / 2); + CenterAndScaleElement(image); + InkCanvas.SetLeft(image, 0); + InkCanvas.SetTop(image, 0); inkCanvas.Children.Add(image); timeMachine.CommitElementInsertHistory(image); @@ -2076,6 +2040,5 @@ namespace Ink_Canvas { } } - } } diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs index d3376635..85e658aa 100644 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs @@ -1,17 +1,11 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Timers; using System.Windows; -using System.Windows.Controls; -using System.Windows.Forms; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; @@ -23,12 +17,12 @@ using Microsoft.Office.Interop.PowerPoint; using Application = System.Windows.Application; using File = System.IO.File; using MessageBox = System.Windows.MessageBox; +using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs; using MouseEventArgs = System.Windows.Input.MouseEventArgs; -using Point = System.Drawing.Point; -using Timer = System.Timers.Timer; namespace Ink_Canvas { public partial class MainWindow : Window { + #region Win32 API Declarations [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); @@ -38,9 +32,6 @@ namespace Ink_Canvas { [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId, out uint lpdwThreadId); - [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool IsWindowVisible(IntPtr hWnd); @@ -51,8 +42,6 @@ namespace Ink_Canvas { [DllImport("user32.dll")] private static extern bool IsZoomed(IntPtr hWnd); - - [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); @@ -74,649 +63,231 @@ namespace Ink_Canvas { private const uint GW_HWNDNEXT = 2; private const uint GW_HWNDPREV = 3; - - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + #endregion + #region PPT Application Variables public static Microsoft.Office.Interop.PowerPoint.Application pptApplication; public static Presentation presentation; public static Slides slides; public static Slide slide; public static int slidescount; + #endregion - // 在类中添加字段 + #region PPT State Management private bool wasFloatingBarFoldedWhenEnterSlideShow; - - // 新增:用于控制WPS强制关闭提示只弹一次 private static bool hasShownWpsForceCloseWarning = false; + private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效 + private bool isPresentationHaveBlackSpace; + private string pptName; + private bool _isPptClickingBtnTurned; + #endregion - private void BtnCheckPPT_Click(object sender, RoutedEventArgs e) { - try { - pptApplication = - (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); - //pptApplication.SlideShowWindows[1].View.Next(); - if (pptApplication != null) { - //获得演示文稿对象 - presentation = pptApplication.ActivePresentation; - pptApplication.SlideShowBegin += PptApplication_SlideShowBegin; - pptApplication.SlideShowNextSlide += PptApplication_SlideShowNextSlide; - pptApplication.SlideShowEnd += PptApplication_SlideShowEnd; - // 获得幻灯片对象集合 - slides = presentation.Slides; - // 获得幻灯片的数量 - slidescount = slides.Count; - memoryStreams = new MemoryStream[slidescount + 2]; - // 获得当前选中的幻灯片 - try { - // 在普通视图下这种方式可以获得当前选中的幻灯片对象 - // 然而在阅读模式下,这种方式会出现异常 - slide = slides[pptApplication.ActiveWindow.Selection.SlideRange.SlideNumber]; - } - catch { - // 在阅读模式下出现异常时,通过下面的方式来获得当前选中的幻灯片对象 - try { - if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - { - slide = pptApplication.SlideShowWindows[1].View.Slide; - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"获取当前幻灯片失败: {ex}", LogHelper.LogType.Error); - } - } - } + #region PPT Managers + private PPTManager _pptManager; + private PPTInkManager _pptInkManager; + private PPTUIManager _pptUIManager; + #endregion - if (pptApplication == null) throw new Exception(); - //BtnCheckPPT.Visibility = Visibility.Collapsed; - StackPanelPPTControls.Visibility = Visibility.Visible; - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"检查PPT应用程序失败: {ex}", LogHelper.LogType.Error); - //BtnCheckPPT.Visibility = Visibility.Visible; - StackPanelPPTControls.Visibility = Visibility.Collapsed; - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - MessageBox.Show("未找到幻灯片"); - } - } + #region PPT Manager Initialization + private void InitializePPTManagers() + { + try + { + // 初始化PPT管理器 + _pptManager = new PPTManager(); + _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS; - private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e) { - if (!isLoaded) return; + // 注册事件 + _pptManager.PPTConnectionChanged += OnPPTConnectionChanged; + _pptManager.SlideShowBegin += OnPPTSlideShowBegin; + _pptManager.SlideShowNextSlide += OnPPTSlideShowNextSlide; + _pptManager.SlideShowEnd += OnPPTSlideShowEnd; + _pptManager.PresentationOpen += OnPPTPresentationOpen; + _pptManager.PresentationClose += OnPPTPresentationClose; - Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn; - SaveSettingsToFile(); - } + // 初始化墨迹管理器 + _pptInkManager = new PPTInkManager(); + _pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint; + _pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation; - private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS; + // 初始化UI管理器 + _pptUIManager = new PPTUIManager(this); + _pptUIManager.ShowPPTButton = Settings.PowerPointSettings.ShowPPTButton; + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; + _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption; + _pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition; + _pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition; + _pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable; - public static bool IsShowingRestoreHiddenSlidesWindow; - private static bool IsShowingAutoplaySlidesWindow; - - // WPP 相关变量 - private static Process wppProcess; - private static bool hasWppProcessID; - private static Timer wppProcessCheckTimer; - private static DateTime wppProcessRecordTime = DateTime.MinValue; // 记录进程时间 - private static int wppProcessCheckCount; // 检查次数计数器 - private static WpsWindowInfo lastForegroundWpsWindow; // 记录上次检测到的前台WPS窗口 - private static DateTime lastWindowCheckTime = DateTime.MinValue; // 记录上次窗口检查时间 - - - private void TimerCheckPPT_Elapsed(object sender, ElapsedEventArgs e) { - if (IsShowingRestoreHiddenSlidesWindow || IsShowingAutoplaySlidesWindow) return; - try { - - pptApplication = - (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); - - if (pptApplication != null) { - timerCheckPPT.Stop(); - //获得演示文稿对象 - presentation = pptApplication.ActivePresentation; - - // 获得幻灯片对象集合 - slides = presentation.Slides; - - // 获得幻灯片的数量 - slidescount = slides.Count; - memoryStreams = new MemoryStream[slidescount + 2]; - // 获得当前选中的幻灯片 - try { - // 在普通视图下这种方式可以获得当前选中的幻灯片对象 - // 然而在阅读模式下,这种方式会出现异常 - slide = slides[pptApplication.ActiveWindow.Selection.SlideRange.SlideNumber]; - } - catch { - // 在阅读模式下出现异常时,通过下面的方式来获得当前选中的幻灯片对象 - try { - if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - { - slide = pptApplication.SlideShowWindows[1].View.Slide; - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"获取当前幻灯片失败: {ex}", LogHelper.LogType.Error); - } - } - - pptApplication.PresentationOpen += PptApplication_PresentationOpen; - pptApplication.PresentationClose += PptApplication_PresentationClose; - pptApplication.SlideShowBegin += PptApplication_SlideShowBegin; - pptApplication.SlideShowNextSlide += PptApplication_SlideShowNextSlide; - pptApplication.SlideShowEnd += PptApplication_SlideShowEnd; - } - - if (pptApplication == null) return; - //BtnCheckPPT.Visibility = Visibility.Collapsed; - - // 此处是已经开启了 - PptApplication_PresentationOpen(null); - - //如果检测到已经开始放映,则立即进入画板模式 - if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - { - try { - PptApplication_SlideShowBegin(pptApplication.SlideShowWindows[1]); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"启动幻灯片放映失败: {ex}", LogHelper.LogType.Error); - } - } + LogHelper.WriteLogToFile("PPT管理器初始化完成", LogHelper.LogType.Event); } catch (Exception ex) { - // 忽略常见的COM对象失效错误 - if (ex is COMException comEx) - { - uint hr = (uint)comEx.HResult; - // 0x800401E3: 操作无法使用 - // 0x80004005: 未指定错误(常见于PPT已关闭) - // 0x800706B5: RPC服务器不可用 - // 0x80048240: 没有活动的演示文稿 - // 0x800706BE: 远程过程调用失败 - if (hr == 0x800401E3 || hr == 0x80004005 || hr == 0x800706B5 || hr == 0x80048240 || hr == 0x800706BE) - { - Application.Current.Dispatcher.Invoke(() => { BtnPPTSlideShow.Visibility = Visibility.Collapsed; }); - timerCheckPPT.Start(); - return; - } - } - LogHelper.WriteLogToFile($"检查PPT状态失败: {ex}", LogHelper.LogType.Error); - Application.Current.Dispatcher.Invoke(() => { BtnPPTSlideShow.Visibility = Visibility.Collapsed; }); - timerCheckPPT.Start(); + LogHelper.WriteLogToFile($"PPT管理器初始化失败: {ex}", LogHelper.LogType.Error); } } - private void PptApplication_PresentationOpen(Presentation Pres) { - // 新增逻辑:如果开启"重新进入放映时回到首页",则直接跳转第一页 - if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) + private void StartPPTMonitoring() + { + if (Settings.PowerPointSettings.PowerPointSupport) { - Application.Current.Dispatcher.BeginInvoke(new Action(() => { - try - { - if (presentation == null) - { - LogHelper.WriteLogToFile("演示文稿为空,无法跳转到首页", LogHelper.LogType.Warning); - return; - } - if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - presentation.SlideShowWindow.View.GotoSlide(1); - else if (presentation.Windows != null && presentation.Windows.Count >= 1) - presentation.Windows[1].View.GotoSlide(1); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"跳转到首页失败: {ex}", LogHelper.LogType.Error); - } - }), DispatcherPriority.Normal); - } - else if (Settings.PowerPointSettings.IsNotifyPreviousPage) - Application.Current.Dispatcher.BeginInvoke(new Action(() => { - try { - // 添加安全检查 - if (presentation == null) - { - LogHelper.WriteLogToFile("演示文稿为空,无法跳转到上次播放页", LogHelper.LogType.Warning); - return; - } - - // 使用更精确的文件标识符:文件名_页数_文件路径哈希值 - string presentationPath = presentation.FullName; - string fileHash = GetFileHash(presentationPath); - string folderName = presentation.Name + "_" + presentation.Slides.Count + "_" + fileHash; - var folderPath = Settings.Automation.AutoSavedStrokesLocation + - @"\Auto Saved - Presentations\" + folderName; - try { - if (!File.Exists(folderPath + "/Position")) return; - if (!int.TryParse(File.ReadAllText(folderPath + "/Position"), out var page)) return; - if (page <= 0) return; - new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => { - try { - if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - // 如果已经播放了的话, 跳转 - presentation.SlideShowWindow.View.GotoSlide(page); - else if (presentation.Windows != null && presentation.Windows.Count >= 1) - presentation.Windows[1].View.GotoSlide(page); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"跳转到指定页面失败: {ex}", LogHelper.LogType.Error); - } - }).ShowDialog(); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"读取上次播放位置失败: {ex}", LogHelper.LogType.Error); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"处理上次播放页跳转失败: {ex}", LogHelper.LogType.Error); - } - }), DispatcherPriority.Normal); - - - //检查是否有隐藏幻灯片 - if (Settings.PowerPointSettings.IsNotifyHiddenPage) { - try { - var isHaveHiddenSlide = false; - if (slides != null) - { - foreach (Slide slide in slides) - if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) { - isHaveHiddenSlide = true; - break; - } - } - - Application.Current.Dispatcher.BeginInvoke(new Action(() => { - if (isHaveHiddenSlide && !IsShowingRestoreHiddenSlidesWindow) { - IsShowingRestoreHiddenSlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?", - () => { - try { - if (slides != null) - { - foreach (Slide slide in slides) - if (slide.SlideShowTransition.Hidden == - MsoTriState.msoTrue) - slide.SlideShowTransition.Hidden = - MsoTriState.msoFalse; - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"取消隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); - } - finally { - IsShowingRestoreHiddenSlidesWindow = false; - } - }, () => { IsShowingRestoreHiddenSlidesWindow = false; }, - () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog(); - } - - BtnPPTSlideShow.Visibility = Visibility.Visible; - }), DispatcherPriority.Normal); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"检查隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); - } - } - - //检测是否有自动播放 - if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation - // && presentation.SlideShowSettings.AdvanceMode == PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings - && BtnPPTSlideShowEnd.Visibility != Visibility.Visible) { - try { - bool hasSlideTimings = false; - if (presentation != null && presentation.Slides != null) - { - foreach (Slide slide in presentation.Slides) { - if (slide.SlideShowTransition.AdvanceOnTime == MsoTriState.msoTrue && - slide.SlideShowTransition.AdvanceTime > 0) { - hasSlideTimings = true; - break; - } - } - } - - if (hasSlideTimings) { - Application.Current.Dispatcher.BeginInvoke((Action)(() => { - if (hasSlideTimings && !IsShowingAutoplaySlidesWindow) { - IsShowingAutoplaySlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?", - () => { - try { - if (presentation != null) - { - presentation.SlideShowSettings.AdvanceMode = - PpSlideShowAdvanceMode.ppSlideShowManualAdvance; - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"设置手动播放模式失败: {ex}", LogHelper.LogType.Error); - } - finally { - IsShowingAutoplaySlidesWindow = false; - } - }, () => { IsShowingAutoplaySlidesWindow = false; }, - () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog(); - } - })); - try { - if (presentation != null) - { - presentation.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowManualAdvance; - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"设置演示文稿播放模式失败: {ex}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"检查自动播放设置失败: {ex}", LogHelper.LogType.Error); - } + _pptManager?.StartMonitoring(); + LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Event); } } - private void PptApplication_PresentationClose(Presentation Pres) { - try { - pptApplication.PresentationOpen -= PptApplication_PresentationOpen; - pptApplication.PresentationClose -= PptApplication_PresentationClose; - pptApplication.SlideShowBegin -= PptApplication_SlideShowBegin; - pptApplication.SlideShowNextSlide -= PptApplication_SlideShowNextSlide; - pptApplication.SlideShowEnd -= PptApplication_SlideShowEnd; - - - timerCheckPPT.Start(); - - Application.Current.Dispatcher.Invoke(() => { - BtnPPTSlideShow.Visibility = Visibility.Collapsed; - BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; + private void StopPPTMonitoring() + { + _pptManager?.StopMonitoring(); + LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Event); + } + + private void DisposePPTManagers() + { + try + { + _pptManager?.Dispose(); + _pptInkManager?.Dispose(); + _pptManager = null; + _pptInkManager = null; + _pptUIManager = null; + LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放PPT管理器失败: {ex}", LogHelper.LogType.Error); + } + } + #endregion + + #region New PPT Event Handlers + private void OnPPTConnectionChanged(bool isConnected) + { + try + { + Application.Current.Dispatcher.InvokeAsync(() => + { + _pptUIManager?.UpdateConnectionStatus(isConnected); + + if (isConnected) + { + LogHelper.WriteLogToFile("PPT连接已建立", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event); + // 清理墨迹管理器 + _pptInkManager?.ClearAllStrokes(); + } }); } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理PPT连接状态变化失败: {ex}", LogHelper.LogType.Error); } } - private bool isPresentationHaveBlackSpace; - private string pptName; + private void OnPPTPresentationOpen(Presentation pres) + { + try + { + Application.Current.Dispatcher.InvokeAsync(() => + { + // 初始化墨迹管理器 + _pptInkManager?.InitializePresentation(pres); - private void UpdatePPTBtnStyleSettingsStatus() { - try { - var sopt = Settings.PowerPointSettings.PPTSButtonsOption.ToString(); - char[] soptc = sopt.ToCharArray(); - if (soptc[0] == '2') - { - PPTLSPageButton.Visibility = Visibility.Visible; - PPTRSPageButton.Visibility = Visibility.Visible; - } - else - { - PPTLSPageButton.Visibility = Visibility.Collapsed; - PPTRSPageButton.Visibility = Visibility.Collapsed; - } - if (soptc[2] == '2') - { - // 这里先堆一点屎山,没空用Resources了 - PPTBtnLSBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTBtnRSBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTBtnLSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91)); - PPTBtnRSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91)); - PPTLSPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTRSPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTLSNextButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTRSNextButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTLSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTLSPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRSPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTLSNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRSNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - TextBlock.SetForeground(PPTLSPageButton, new SolidColorBrush(Colors.White)); - TextBlock.SetForeground(PPTRSPageButton, new SolidColorBrush(Colors.White)); - } - else - { - PPTBtnLSBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245)); - PPTBtnRSBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245)); - PPTBtnLSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170)); - PPTBtnRSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170)); - PPTLSPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTRSPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTLSNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTRSNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTLSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTLSPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRSPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTLSNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRSNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - TextBlock.SetForeground(PPTLSPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27))); - TextBlock.SetForeground(PPTRSPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27))); - } - if (soptc[1] == '2') - { - PPTBtnLSBorder.Opacity = 0.5; - PPTBtnRSBorder.Opacity = 0.5; - } - else - { - PPTBtnLSBorder.Opacity = 1; - PPTBtnRSBorder.Opacity = 1; - } + // 处理跳转到首页或上次播放页的逻辑 + HandlePresentationOpenNavigation(pres); - var bopt = Settings.PowerPointSettings.PPTBButtonsOption.ToString(); - char[] boptc = bopt.ToCharArray(); - if (boptc[0] == '2') - { - PPTLBPageButton.Visibility = Visibility.Visible; - PPTRBPageButton.Visibility = Visibility.Visible; - } - else - { - PPTLBPageButton.Visibility = Visibility.Collapsed; - PPTRBPageButton.Visibility = Visibility.Collapsed; - } - if (boptc[2] == '2') - { - // 这里先堆一点屎山,没空用Resources了 - PPTBtnLBBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTBtnRBBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTBtnLBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91)); - PPTBtnRBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91)); - PPTLBPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTRBPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTLBNextButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTRBNextButtonGeometry.Brush = new SolidColorBrush(Colors.White); - PPTLBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTLBPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRBPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTLBNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - PPTRBNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White); - TextBlock.SetForeground(PPTLBPageButton, new SolidColorBrush(Colors.White)); - TextBlock.SetForeground(PPTRBPageButton, new SolidColorBrush(Colors.White)); - } - else - { - PPTBtnLBBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245)); - PPTBtnRBBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245)); - PPTBtnLBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170)); - PPTBtnRBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170)); - PPTLBPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTRBPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTLBNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTRBNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42)); - PPTLBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTLBPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRBPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTLBNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - PPTRBNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - TextBlock.SetForeground(PPTLBPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27))); - TextBlock.SetForeground(PPTRBPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27))); - } - if (boptc[1] == '2') - { - PPTBtnLBBorder.Opacity = 0.5; - PPTBtnRBBorder.Opacity = 0.5; - } - else - { - PPTBtnLBBorder.Opacity = 1; - PPTBtnRBBorder.Opacity = 1; - } + // 检查隐藏幻灯片 + if (Settings.PowerPointSettings.IsNotifyHiddenPage) + { + CheckAndNotifyHiddenSlides(pres); + } + + // 检查自动播放设置 + if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation) + { + CheckAndNotifyAutoPlaySettings(pres); + } + + _pptUIManager?.UpdateConnectionStatus(true); + }); } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); } } - private void UpdatePPTBtnDisplaySettingsStatus() { - try { - // 检查是否应该显示PPT按钮 - bool shouldShowButtons = Settings.PowerPointSettings.ShowPPTButton && - (BtnPPTSlideShowEnd.Visibility == Visibility.Visible || - (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count > 0)); - - if (!shouldShowButtons) + private void OnPPTPresentationClose(Presentation pres) + { + try + { + Application.Current.Dispatcher.InvokeAsync(() => { - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - return; - } + // 保存所有墨迹 + _pptInkManager?.SaveAllStrokesToFile(pres); - var lsp = Settings.PowerPointSettings.PPTLSButtonPosition; - LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, lsp*2); - var rsp = Settings.PowerPointSettings.PPTRSButtonPosition; - RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, rsp*2); + // 清理墨迹管理器 + _pptInkManager?.ClearAllStrokes(); - var dopt = Settings.PowerPointSettings.PPTButtonsDisplayOption.ToString(); - char[] doptc = dopt.ToCharArray(); - if (doptc[0] == '2') AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation); - else LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - if (doptc[1] == '2') AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation); - else RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - if (doptc[2] == '2') AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation); - else LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - if (doptc[3] == '2') AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation); - else RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + _pptUIManager?.UpdateConnectionStatus(false); + }); } - catch (Exception ex) { - LogHelper.WriteLogToFile($"更新PPT按钮显示状态失败: {ex}", LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error); } } - private async void PptApplication_SlideShowBegin(SlideShowWindow Wn) { - try { + private async void OnPPTSlideShowBegin(SlideShowWindow wn) + { + try + { // 记录进入放映时浮动栏收纳状态 wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded; - + if (Settings.Automation.IsAutoFoldInPPTSlideShow && !isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null); - else if (isFloatingBarFolded) await UnFoldFloatingBar(new object()); + else if (isFloatingBarFolded) + await UnFoldFloatingBar(new object()); isStopInkReplay = true; - LogHelper.WriteLogToFile("PowerPoint Application Slide Show Begin", LogHelper.LogType.Event); - - await Application.Current.Dispatcher.InvokeAsync(() => { - // 新增:如果设置开启,进入放映时强制跳转到第一页 + await Application.Current.Dispatcher.InvokeAsync(() => + { + // 处理跳转到首页 if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) { - try - { - if (Wn != null && Wn.Presentation != null && Wn.View != null) - { - Wn.View.GotoSlide(1); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"放映开始时跳转首页失败: {ex}", LogHelper.LogType.Error); - } + _pptManager?.TryNavigateToSlide(1); } - //调整颜色 - var screenRatio = SystemParameters.PrimaryScreenWidth / SystemParameters.PrimaryScreenHeight; - if (Math.Abs(screenRatio - 16.0 / 9) <= -0.01) { - if (Wn.Presentation.PageSetup.SlideWidth / Wn.Presentation.PageSetup.SlideHeight < 1.65) { - isPresentationHaveBlackSpace = true; + // 更新UI状态 + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + var totalSlides = _pptManager?.SlidesCount ?? 0; + _pptUIManager?.UpdateSlideShowStatus(true, currentSlide, totalSlides); - if (BtnSwitchTheme.Content.ToString() == "深色") { - //Light - BtnExit.Foreground = Brushes.White; - ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark; - } - //Dark - } - } else if (screenRatio == -256 / 135) { } + // 设置浮动栏透明度和边距 + _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue); + _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 10)); - lastDesktopInkColor = 1; - - slidescount = Wn.Presentation.Slides.Count; - previousSlideID = 0; - memoryStreams = new MemoryStream[slidescount + 2]; - - pptName = Wn.Presentation.Name; - LogHelper.NewLog("Name: " + Wn.Presentation.Name); - LogHelper.NewLog("Slides Count: " + slidescount); - - //检查是否有已有墨迹,并加载 - if (Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint) - { - // 使用更精确的文件标识符:文件名_页数_文件路径哈希值 - string presentationPath = Wn.Presentation.FullName; - string fileHash = GetFileHash(presentationPath); - string folderName = Wn.Presentation.Name + "_" + Wn.Presentation.Slides.Count + "_" + fileHash; - - if (Directory.Exists(Settings.Automation.AutoSavedStrokesLocation + - @"\Auto Saved - Presentations\" + folderName)) { - LogHelper.WriteLogToFile("Found saved strokes", LogHelper.LogType.Trace); - var files = new DirectoryInfo(Settings.Automation.AutoSavedStrokesLocation + - @"\Auto Saved - Presentations\" + folderName).GetFiles(); - var count = 0; - foreach (var file in files) - if (file.Name != "Position") { - var i = -1; - try { - i = int.Parse(Path.GetFileNameWithoutExtension(file.Name)); - memoryStreams[i] = new MemoryStream(File.ReadAllBytes(file.FullName)); - memoryStreams[i].Position = 0; - count++; - } - catch (Exception ex) { - LogHelper.WriteLogToFile( - $"Failed to load strokes on Slide {i}\n{ex}", - LogHelper.LogType.Error); - } - } - - LogHelper.WriteLogToFile($"Loaded {count.ToString()} saved strokes"); - } - } - - StackPanelPPTControls.Visibility = Visibility.Visible; - UpdatePPTBtnDisplaySettingsStatus(); - UpdatePPTBtnStyleSettingsStatus(); - - BtnPPTSlideShow.Visibility = Visibility.Collapsed; - BtnPPTSlideShowEnd.Visibility = Visibility.Visible; - ViewBoxStackPanelMain.Margin = new Thickness(10, 10, 10, 10); - ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue; + // 显示侧边栏退出按钮 + _pptUIManager?.UpdateSidebarExitButtons(true); + // 处理画板显示 if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow && !Settings.Automation.IsAutoFoldInPPTSlideShow && - GridTransparencyFakeBackground.Background == Brushes.Transparent && !isFloatingBarFolded) { + GridTransparencyFakeBackground.Background == Brushes.Transparent && !isFloatingBarFolded) + { BtnHideInkCanvas_Click(BtnHideInkCanvas, null); } if (currentMode != 0) { - ImageBlackboard_MouseUp(null,null); + ImageBlackboard_MouseUp(null, null); BtnHideInkCanvas_Click(BtnHideInkCanvas, null); } @@ -727,362 +298,483 @@ namespace Ink_Canvas { BtnColorRed_Click(null, null); isEnteredSlideShowEndEvent = false; - PPTBtnPageNow.Text = $"{Wn.View.CurrentShowPosition}"; - PPTBtnPageTotal.Text = $"/ {Wn.Presentation.Slides.Count}"; - LogHelper.NewLog("PowerPoint Slide Show Loading process complete"); - // 新增:主动加载当前页墨迹,解决首次放映时当前页墨迹不显示的问题 - try - { - var currentPage = Wn.View.CurrentShowPosition; - if (memoryStreams != null && currentPage < memoryStreams.Length && memoryStreams[currentPage] != null && memoryStreams[currentPage].Length > 0) - { - memoryStreams[currentPage].Position = 0; - inkCanvas.Strokes.Clear(); - inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[currentPage])); - } - else - { - inkCanvas.Strokes.Clear(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载当前页墨迹失败: {ex}", LogHelper.LogType.Error); - } + // 加载当前页墨迹 + LoadCurrentSlideInk(currentSlide); + }); - if (!isFloatingBarFolded) { - new Thread(() => { - Thread.Sleep(100); - Application.Current.Dispatcher.Invoke(() => { - ViewboxFloatingBarMarginAnimation(60); - }); - }).Start(); - } - }); - await Application.Current.Dispatcher.InvokeAsync(() => { - if (BtnExitPptFromSidebarLeft != null) - BtnExitPptFromSidebarLeft.Visibility = Visibility.Visible; - if (BtnExitPptFromSidebarRight != null) - BtnExitPptFromSidebarRight.Visibility = Visibility.Visible; - }); + if (!isFloatingBarFolded) + { + new Thread(() => + { + Thread.Sleep(100); + Application.Current.Dispatcher.Invoke(() => + { + ViewboxFloatingBarMarginAnimation(60); + }); + }).Start(); + } } - catch (Exception ex) { - LogHelper.WriteLogToFile("PowerPoint Application Slide Show Begin Error: " + ex, LogHelper.LogType.Error); - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error); } } - private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效 + private void OnPPTSlideShowNextSlide(SlideShowWindow wn) + { + try + { + Application.Current.Dispatcher.InvokeAsync(() => + { + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + var totalSlides = _pptManager?.SlidesCount ?? 0; - private async void PptApplication_SlideShowEnd(Presentation Pres) { - try { - // 新增逻辑:如果设置开启且进入PPT放映时浮动栏是收纳的,退出时也自动收纳;否则自动展开 - if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow) { + // 保存上一页墨迹并加载当前页墨迹 + SwitchSlideInk(currentSlide); + + // 更新UI + _pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides); + + LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace); + }); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error); + } + } + + private async void OnPPTSlideShowEnd(Presentation pres) + { + try + { + // 处理浮动栏状态 + if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow) + { if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null); - } else { + } + else + { if (isFloatingBarFolded) await UnFoldFloatingBar(new object()); } - // 记录 WPP 进程 ID,用于后续检测未关闭的进程 - if (pptApplication != null) + if (isEnteredSlideShowEndEvent) return; + isEnteredSlideShowEndEvent = true; + + // 保存所有墨迹 + _pptInkManager?.SaveAllStrokesToFile(pres); + + await Application.Current.Dispatcher.InvokeAsync(() => { try { - // 尝试多种方式获取WPS进程 - Process wpsProcess = null; - - // 方法1:通过应用程序路径检测 - if (pptApplication.Path.Contains("Kingsoft\\WPS Office\\") || - pptApplication.Path.Contains("WPS Office\\")) - { - uint processId; - GetWindowThreadProcessId((IntPtr)pptApplication.HWND, out processId); - wpsProcess = Process.GetProcessById((int)processId); - LogHelper.WriteLogToFile($"通过路径检测到WPS进程: {processId}", LogHelper.LogType.Trace); - } - - // 方法2:通过前台窗口检测 - if (wpsProcess == null) - { - var foregroundWpsWindow = GetForegroundWpsWindow(); - if (foregroundWpsWindow != null) - { - wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId); - LogHelper.WriteLogToFile($"通过前台窗口检测到WPS进程: {foregroundWpsWindow.ProcessId}", LogHelper.LogType.Trace); - } - } - - // 方法3:通过进程名检测 - if (wpsProcess == null) - { - var wpsProcesses = GetWpsProcesses(); - if (wpsProcesses.Count > 0) - { - wpsProcess = wpsProcesses.First(); - LogHelper.WriteLogToFile($"通过进程名检测到WPS进程: {wpsProcess.Id}", LogHelper.LogType.Trace); - } - } - - if (wpsProcess != null) - { - wppProcess = wpsProcess; - hasWppProcessID = true; - wppProcessRecordTime = DateTime.Now; - wppProcessCheckCount = 0; - LogHelper.WriteLogToFile($"成功记录 WPP 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace); - } - else - { - LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error); - } - } - - LogHelper.WriteLogToFile("PowerPoint Slide Show End", LogHelper.LogType.Event); - if (isEnteredSlideShowEndEvent) { - LogHelper.WriteLogToFile("Detected previous entrance, returning"); - return; - } - - isEnteredSlideShowEndEvent = true; - if (Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint) { - // 使用更精确的文件标识符:文件名_页数_文件路径哈希值 - string presentationPath = Pres.FullName; - string fileHash = GetFileHash(presentationPath); - string folderName = Pres.Name + "_" + Pres.Slides.Count + "_" + fileHash; - var folderPath = Settings.Automation.AutoSavedStrokesLocation + @"\Auto Saved - Presentations\" + folderName; - if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); - try { - File.WriteAllText(folderPath + "/Position", previousSlideID.ToString()); - } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - } - - for (var i = 1; i <= Pres.Slides.Count; i++) - if (memoryStreams[i] != null) - try { - if (memoryStreams[i].Length > 8) { - var srcBuf = new byte[memoryStreams[i].Length]; - memoryStreams[i].Position = 0; - var byteLength = memoryStreams[i].Read(srcBuf, 0, srcBuf.Length); - // 使用Path.Combine构建文件路径 - File.WriteAllBytes(folderPath + @"\" + i.ToString("0000") + ".icstk", srcBuf); - LogHelper.WriteLogToFile(string.Format( - "Saved strokes for Slide {0}, size={1}, byteLength={2}", i.ToString(), - memoryStreams[i].Length, byteLength)); - } else { - if (File.Exists(folderPath + @"\" + i.ToString("0000") + ".icstk")) - File.Delete(folderPath + @"\" + i.ToString("0000") + ".icstk"); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile( - $"Failed to save strokes for Slide {i}\n{ex}", - LogHelper.LogType.Error); - if (File.Exists(folderPath + @"\" + i.ToString("0000") + ".icstk")) - File.Delete(folderPath + @"\" + i.ToString("0000") + ".icstk"); - } - } - - await Application.Current.Dispatcher.InvokeAsync(() => { - try { isPresentationHaveBlackSpace = false; - if (BtnSwitchTheme.Content.ToString() == "深色") { - //Light + // 恢复主题 + if (BtnSwitchTheme.Content.ToString() == "深色") + { BtnExit.Foreground = Brushes.Black; ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light; } - //Dark - BtnPPTSlideShow.Visibility = Visibility.Visible; - BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; - StackPanelPPTControls.Visibility = Visibility.Collapsed; - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + // 更新UI状态 + _pptUIManager?.UpdateSlideShowStatus(false); + _pptUIManager?.UpdateSidebarExitButtons(false); + _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 55)); + _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityValue); - ViewBoxStackPanelMain.Margin = new Thickness(10, 10, 10, 55); - - if (currentMode != 0) { + if (currentMode != 0) + { CloseWhiteboardImmediately(); currentMode = 0; } ClearStrokes(true); - // 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹 + // 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上) TimeMachineHistories[0] = null; if (GridTransparencyFakeBackground.Background != Brushes.Transparent) BtnHideInkCanvas_Click(BtnHideInkCanvas, null); - - ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue; } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映结束UI更新失败: {ex}", LogHelper.LogType.Error); } }); await Task.Delay(150); - - await Application.Current.Dispatcher.InvokeAsync(() => { + await Application.Current.Dispatcher.InvokeAsync(() => + { ViewboxFloatingBarMarginAnimation(100, true); }); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error); + } + } + #endregion - // 启动 WPP 进程检测定时器 - if (hasWppProcessID && wppProcess != null) + #region Helper Methods + private void HandlePresentationOpenNavigation(Presentation pres) + { + try + { + if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) { - StartWppProcessCheckTimer(); + _pptManager?.TryNavigateToSlide(1); + } + else if (Settings.PowerPointSettings.IsNotifyPreviousPage) + { + ShowPreviousPageNotification(pres); } } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理演示文稿导航失败: {ex}", LogHelper.LogType.Error); } } - private int previousSlideID; - private MemoryStream[] memoryStreams = new MemoryStream[50]; + private void ShowPreviousPageNotification(Presentation pres) + { + try + { + if (pres == null) return; - private DateTime inkLockUntil = DateTime.MinValue; - private int lockedSlideIndex; - private const int InkLockMilliseconds = 500; + var presentationPath = pres.FullName; + var fileHash = GetFileHash(presentationPath); + var folderName = pres.Name + "_" + pres.Slides.Count + "_" + fileHash; + var folderPath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "Auto Saved - Presentations", folderName); + var positionFile = Path.Combine(folderPath, "Position"); - private void PptApplication_SlideShowNextSlide(SlideShowWindow Wn) { - try { - // 添加安全检查 - if (Wn == null || Wn.View == null) + if (!File.Exists(positionFile)) return; + + if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0) { - LogHelper.WriteLogToFile("幻灯片放映窗口或视图为空", LogHelper.LogType.Warning); - return; + new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => + { + _pptManager?.TryNavigateToSlide(page); + }).ShowDialog(); } - LogHelper.WriteLogToFile($"PowerPoint Next Slide (Slide {Wn.View.CurrentShowPosition})", - LogHelper.LogType.Event); - // 先保存旧页墨迹 - Application.Current.Dispatcher.Invoke(() => { - var ms = new MemoryStream(); - inkCanvas.Strokes.Save(ms); - ms.Position = 0; - if (previousSlideID > 0 && previousSlideID < memoryStreams.Length) - memoryStreams[previousSlideID] = ms; - ClearStrokes(true); - timeMachine.ClearStrokeHistory(); - int newPage = Wn.View.CurrentShowPosition; - if (newPage > 0 && newPage < memoryStreams.Length && - memoryStreams[newPage] != null && memoryStreams[newPage].Length > 0) { - memoryStreams[newPage].Position = 0; - inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[newPage])); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示上次播放页通知失败: {ex}", LogHelper.LogType.Error); + } + } + + private void CheckAndNotifyHiddenSlides(Presentation pres) + { + try + { + bool hasHiddenSlides = false; + if (pres?.Slides != null) + { + foreach (Slide slide in pres.Slides) + { + if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) + { + hasHiddenSlides = true; + break; + } } - PPTBtnPageNow.Text = $"{Wn.View.CurrentShowPosition}"; - PPTBtnPageTotal.Text = $"/ {Wn.Presentation.Slides.Count}"; - previousSlideID = newPage; // 最后赋值,确保索引同步 - LockInkForCurrentSlide(newPage); // 翻页后加锁 + } + + if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow) + { + IsShowingRestoreHiddenSlidesWindow = true; + new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?", + () => + { + try + { + if (pres?.Slides != null) + { + foreach (Slide slide in pres.Slides) + { + if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) + slide.SlideShowTransition.Hidden = MsoTriState.msoFalse; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"取消隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); + } + finally + { + IsShowingRestoreHiddenSlidesWindow = false; + } + }, + () => { IsShowingRestoreHiddenSlidesWindow = false; }, + () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); + } + } + + private void CheckAndNotifyAutoPlaySettings(Presentation pres) + { + try + { + if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) return; + + bool hasSlideTimings = false; + if (pres?.Slides != null) + { + foreach (Slide slide in pres.Slides) + { + if (slide.SlideShowTransition.AdvanceOnTime == MsoTriState.msoTrue && + slide.SlideShowTransition.AdvanceTime > 0) + { + hasSlideTimings = true; + break; + } + } + } + + if (hasSlideTimings && !IsShowingAutoplaySlidesWindow) + { + IsShowingAutoplaySlidesWindow = true; + new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?", + () => + { + try + { + if (pres != null) + { + pres.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowManualAdvance; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置手动播放模式失败: {ex}", LogHelper.LogType.Error); + } + finally + { + IsShowingAutoplaySlidesWindow = false; + } + }, + () => { IsShowingAutoplaySlidesWindow = false; }, + () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查自动播放设置失败: {ex}", LogHelper.LogType.Error); + } + } + + private void LoadCurrentSlideInk(int slideIndex) + { + try + { + var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex); + if (strokes != null) + { + inkCanvas.Strokes.Clear(); + inkCanvas.Strokes.Add(strokes); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载当前页墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + + private void SwitchSlideInk(int newSlideIndex) + { + try + { + var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes); + if (newStrokes != null) + { + inkCanvas.Strokes.Clear(); + inkCanvas.Strokes.Add(newStrokes); + } + + // 设置墨迹锁定 + _pptInkManager?.LockInkForSlide(newSlideIndex); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error); + } + } + + 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 ex) + { + LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error); + return "error"; + } + } + #endregion + + private void BtnCheckPPT_Click(object sender, RoutedEventArgs e) { + try { + // 使用新的PPT管理器进行连接检查 + if (_pptManager == null) + { + InitializePPTManagers(); + } + + // 手动触发一次连接检查 + _pptManager?.StartMonitoring(); + + // 等待一小段时间让连接建立 + Task.Delay(500).ContinueWith(_ => + { + Application.Current.Dispatcher.Invoke(() => + { + if (_pptManager?.IsConnected == true) + { + LogHelper.WriteLogToFile("手动PPT连接检查成功", LogHelper.LogType.Event); + } + else + { + MessageBox.Show("未找到幻灯片"); + LogHelper.WriteLogToFile("手动PPT连接检查失败", LogHelper.LogType.Warning); + } + }); }); } catch (Exception ex) { - LogHelper.WriteLogToFile($"幻灯片切换事件处理失败: {ex}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"手动检查PPT应用程序失败: {ex}", LogHelper.LogType.Error); + _pptUIManager?.UpdateConnectionStatus(false); + MessageBox.Show("未找到幻灯片"); } } - private bool _isPptClickingBtnTurned; + private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e) { + if (!isLoaded) return; + + Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn; + + // 更新PPT管理器的WPS支持设置 + if (_pptManager != null) + { + _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS; + } + + SaveSettingsToFile(); + } + + private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS; + + public static bool IsShowingRestoreHiddenSlidesWindow; + private static bool IsShowingAutoplaySlidesWindow; + + + + + + + + + + + + + + + + + + private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e) { Application.Current.Dispatcher.Invoke(() => { - // 切换前同步保存墨迹 - SaveCurrentSlideInkStrokes(); - _isPptClickingBtnTurned = true; - // 添加安全检查 - if (pptApplication == null) - { - LogHelper.WriteLogToFile("PPT应用程序为空,无法执行上一页操作", LogHelper.LogType.Warning); - return; - } try { - // 检查SlideShowWindows是否存在且有效 - if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0) + _isPptClickingBtnTurned = true; + + // 保存当前页墨迹 + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + if (currentSlide > 0) { - LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行上一页操作", LogHelper.LogType.Warning); - return; + _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); } - // 安全访问当前幻灯片信息 - if (pptApplication.SlideShowWindows.Count >= 1) + + // 保存截图(如果启用) + if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && + Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null && slideShowWindow.View != null) - { - if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && - Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) - SaveScreenShot(true, - slideShowWindow.Presentation.Name + "/" + - slideShowWindow.View.CurrentShowPosition); - slideShowWindow.Activate(); - slideShowWindow.View.Previous(); - LockInkForCurrentSlide(slideShowWindow.View.CurrentShowPosition); // 翻页后加锁 - } + var presentationName = _pptManager?.GetPresentationName() ?? ""; + SaveScreenShot(true, $"{presentationName}/{currentSlide}"); + } + + // 执行翻页 + if (_pptManager?.TryNavigatePrevious() == true) + { + // 翻页成功,等待事件处理墨迹切换 + LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace); + } + else + { + LogHelper.WriteLogToFile("切换到上一页失败", LogHelper.LogType.Warning); + _pptUIManager?.UpdateConnectionStatus(false); } } catch (Exception ex) { LogHelper.WriteLogToFile($"PPT上一页操作异常: {ex}", LogHelper.LogType.Error); - StackPanelPPTControls.Visibility = Visibility.Collapsed; - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + _pptUIManager?.UpdateConnectionStatus(false); } }); } private void BtnPPTSlidesDown_Click(object sender, RoutedEventArgs e) { Application.Current.Dispatcher.Invoke(() => { - // 切换前同步保存墨迹 - SaveCurrentSlideInkStrokes(); - _isPptClickingBtnTurned = true; - // 添加安全检查 - if (pptApplication == null) - { - LogHelper.WriteLogToFile("PPT应用程序为空,无法执行下一页操作", LogHelper.LogType.Warning); - return; - } try { - // 检查SlideShowWindows是否存在且有效 - if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0) + _isPptClickingBtnTurned = true; + + // 保存当前页墨迹 + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + if (currentSlide > 0) { - LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行下一页操作", LogHelper.LogType.Warning); - return; + _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); } - // 安全访问当前幻灯片信息 - if (pptApplication.SlideShowWindows.Count >= 1) + + // 保存截图(如果启用) + if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && + Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null && slideShowWindow.View != null) - { - if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && - Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) - SaveScreenShot(true, - slideShowWindow.Presentation.Name + "/" + - slideShowWindow.View.CurrentShowPosition); - slideShowWindow.Activate(); - slideShowWindow.View.Next(); - LockInkForCurrentSlide(slideShowWindow.View.CurrentShowPosition); // 翻页后加锁 - } + var presentationName = _pptManager?.GetPresentationName() ?? ""; + SaveScreenShot(true, $"{presentationName}/{currentSlide}"); + } + + // 执行翻页 + if (_pptManager?.TryNavigateNext() == true) + { + // 翻页成功,等待事件处理墨迹切换 + LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace); + } + else + { + LogHelper.WriteLogToFile("切换到下一页失败", LogHelper.LogType.Warning); + _pptUIManager?.UpdateConnectionStatus(false); } } catch (Exception ex) { LogHelper.WriteLogToFile($"PPT下一页操作异常: {ex}", LogHelper.LogType.Error); - StackPanelPPTControls.Visibility = Visibility.Collapsed; - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; + _pptUIManager?.UpdateConnectionStatus(false); } }); } @@ -1152,39 +844,27 @@ namespace Ink_Canvas { if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return; - // 添加安全检查 - if (pptApplication == null) + // 使用新的PPT管理器检查连接状态 + if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true) { - LogHelper.WriteLogToFile("PPT应用程序为空,无法执行翻页操作", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning); return; } try { - // 检查SlideShowWindows是否存在且有效 - if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0) - { - LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行翻页操作", LogHelper.LogType.Warning); - return; - } - GridTransparencyFakeBackground.Opacity = 1; GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF")); CursorIcon_Click(null, null); - - try { - // 安全访问SlideShowWindows[1] - if (pptApplication.SlideShowWindows.Count >= 1) - { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null) - { - slideShowWindow.SlideNavigation.Visible = true; - } - } + + // 使用新的PPT管理器显示导航 + if (_pptManager.TryShowSlideNavigation()) + { + LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace); } - catch (Exception ex) { - LogHelper.WriteLogToFile($"设置PPT导航可见性失败: {ex}", LogHelper.LogType.Error); + else + { + LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning); } // 控制居中 @@ -1202,71 +882,46 @@ namespace Ink_Canvas { private void BtnPPTSlideShow_Click(object sender, RoutedEventArgs e) { new Thread(() => { try { - presentation.SlideShowSettings.Run(); + if (_pptManager?.TryStartSlideShow() != true) + { + LogHelper.WriteLogToFile("启动幻灯片放映失败", LogHelper.LogType.Warning); + } + } + catch (Exception ex) { + LogHelper.WriteLogToFile($"启动幻灯片放映异常: {ex}", LogHelper.LogType.Error); } - catch { } }).Start(); } private async void BtnPPTSlideShowEnd_Click(object sender, RoutedEventArgs e) { - // 添加安全检查 - if (pptApplication == null) - { - LogHelper.WriteLogToFile("PPT应用程序为空,无法结束放映", LogHelper.LogType.Warning); - return; - } - - // 切换前同步保存墨迹 - Application.Current.Dispatcher.Invoke(() => { - SaveCurrentSlideInkStrokes(); - }); - try { - // 检查SlideShowWindows是否存在且有效 - if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0) + // 保存当前页墨迹 + var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; + if (currentSlide > 0) { - LogHelper.WriteLogToFile("PPT放映窗口不存在,无法结束放映", LogHelper.LogType.Warning); - return; + Application.Current.Dispatcher.Invoke(() => { + _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); + timeMachine.ClearStrokeHistory(); + }); } - Application.Current.Dispatcher.Invoke(() => { - try { - // 安全访问SlideShowWindows[1] - if (pptApplication.SlideShowWindows.Count >= 1) - { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null && slideShowWindow.View != null) - { - var ms = new MemoryStream(); - inkCanvas.Strokes.Save(ms); - ms.Position = 0; - memoryStreams[slideShowWindow.View.CurrentShowPosition] = ms; - timeMachine.ClearStrokeHistory(); - } - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error); - } - }); - - new Thread(() => { - try { - // 安全访问SlideShowWindows[1] - if (pptApplication.SlideShowWindows.Count >= 1) - { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null && slideShowWindow.View != null) - { - slideShowWindow.View.Exit(); - } - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"退出PPT放映失败: {ex}", LogHelper.LogType.Error); - } - }).Start(); + // 结束放映 + if (_pptManager?.TryEndSlideShow() == true) + { + LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("结束幻灯片放映失败", LogHelper.LogType.Warning); + + // 手动更新UI状态,防止事件未触发 + await Application.Current.Dispatcher.InvokeAsync(() => { + _pptUIManager?.UpdateSlideShowStatus(false); + _pptUIManager?.UpdateSidebarExitButtons(false); + LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace); + }); + } HideSubPanels("cursor"); await Task.Delay(150); @@ -1275,30 +930,13 @@ namespace Ink_Canvas { catch (Exception ex) { LogHelper.WriteLogToFile($"结束PPT放映操作异常: {ex}", LogHelper.LogType.Error); + + // 确保UI状态正确 + await Application.Current.Dispatcher.InvokeAsync(() => { + _pptUIManager?.UpdateSlideShowStatus(false); + _pptUIManager?.UpdateSidebarExitButtons(false); + }); } - await Application.Current.Dispatcher.InvokeAsync(() => { - // 隐藏侧边栏退出按钮 - if (BtnExitPptFromSidebarLeft != null) - BtnExitPptFromSidebarLeft.Visibility = Visibility.Collapsed; - if (BtnExitPptFromSidebarRight != null) - BtnExitPptFromSidebarRight.Visibility = Visibility.Collapsed; - - // 确保所有放映模式按钮都被隐藏,防止PptApplication_SlideShowEnd事件未触发的情况 - try { - BtnPPTSlideShow.Visibility = Visibility.Visible; - BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; - StackPanelPPTControls.Visibility = Visibility.Collapsed; - LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - - LogHelper.WriteLogToFile("手动隐藏所有放映模式按钮", LogHelper.LogType.Trace); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"手动隐藏放映模式按钮失败: {ex}", LogHelper.LogType.Error); - } - }); } private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e) @@ -1403,757 +1041,27 @@ namespace Ink_Canvas { BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null); } - private void StartWppProcessCheckTimer() - { - // 新增:WPS联动未启用时不查杀wpp进程 - if (!Settings.PowerPointSettings.IsSupportWPS) - { - LogHelper.WriteLogToFile("WPS联动未启用,跳过WPP进程查杀", LogHelper.LogType.Trace); - return; - } - if (wppProcessCheckTimer != null) - { - wppProcessCheckTimer.Stop(); - wppProcessCheckTimer.Dispose(); - } - wppProcessCheckTimer = new Timer(500); // 改为500ms检查一次,提高响应速度 - wppProcessCheckTimer.Elapsed += WppProcessCheckTimer_Elapsed; - wppProcessCheckTimer.Start(); - LogHelper.WriteLogToFile("启动 WPP 进程检测定时器(前台窗口监控模式)", LogHelper.LogType.Trace); - } - private void WppProcessCheckTimer_Elapsed(object sender, ElapsedEventArgs e) - { - // 新增:WPS联动未启用时不查杀wpp进程 - if (!Settings.PowerPointSettings.IsSupportWPS) - { - LogHelper.WriteLogToFile("WPS联动未启用,跳过WPP进程查杀", LogHelper.LogType.Trace); - StopWppProcessCheckTimer(); - return; - } - try - { - if (wppProcess == null || hasWppProcessID == false) - { - StopWppProcessCheckTimer(); - return; - } - if (!Settings.PowerPointSettings.EnableWppProcessKill) - { - LogHelper.WriteLogToFile("WPP进程查杀功能已被关闭,跳过查杀", LogHelper.LogType.Trace); - StopWppProcessCheckTimer(); - return; - } - // 刷新进程状态 - wppProcess.Refresh(); - wppProcessCheckCount++; - if (wppProcess.HasExited) - { - LogHelper.WriteLogToFile("WPP 进程已正常关闭", LogHelper.LogType.Trace); - StopWppProcessCheckTimer(); - return; - } - // 检查前台WPS窗口是否存在 - bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActive(); - - if (isForegroundWpsWindowActive) - { - // 前台窗口仍然存在,继续监控 - if (wppProcessCheckCount % 10 == 0) // 每5秒记录一次日志,避免日志过多 - { - LogHelper.WriteLogToFile($"前台WPS窗口仍然存在,继续监控(已检查{wppProcessCheckCount}次)", LogHelper.LogType.Trace); - } - return; - } - - // 前台窗口已消失,立即结束WPP进程 - LogHelper.WriteLogToFile("检测到前台WPS窗口已消失,立即结束WPP进程", LogHelper.LogType.Event); - - // 检查所有WPS文档是否已保存 - bool allSaved = true; - try - { - if (pptApplication != null) - { - foreach (Presentation pres in pptApplication.Presentations) - { - if (pres.Saved == MsoTriState.msoFalse) - { - allSaved = false; - break; - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查WPS文档保存状态失败: {ex}", LogHelper.LogType.Error); - allSaved = false; // 出错时默认不安全 - } - if (!allSaved) - { - // 直接跳过弹窗,自动继续 - LogHelper.WriteLogToFile("检测到有未保存的WPS文档,但已取消弹窗提示,自动继续。", LogHelper.LogType.Trace); - } - // 立即结束WPP进程 - try - { - LogHelper.WriteLogToFile("前台窗口消失,开始结束WPP进程", LogHelper.LogType.Event); - - // 尝试优雅地结束进程 - if (!wppProcess.HasExited) - { - wppProcess.Kill(); - LogHelper.WriteLogToFile("前台窗口消失,成功结束WPP进程", LogHelper.LogType.Event); - } - else - { - LogHelper.WriteLogToFile("WPP进程已经自然结束", LogHelper.LogType.Trace); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"结束WPP进程失败: {ex}", LogHelper.LogType.Error); - - // 如果常规方法失败,尝试强制结束 - try - { - var processes = Process.GetProcessesByName(wppProcess.ProcessName); - foreach (var process in processes) - { - if (process.Id == wppProcess.Id) - { - process.Kill(); - LogHelper.WriteLogToFile("强制结束WPP进程成功", LogHelper.LogType.Event); - break; - } - } - } - catch (Exception forceKillEx) - { - LogHelper.WriteLogToFile($"强制结束WPP进程也失败: {forceKillEx}", LogHelper.LogType.Error); - } - } - finally - { - StopWppProcessCheckTimer(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"WPP 进程检测失败: {ex}", LogHelper.LogType.Error); - StopWppProcessCheckTimer(); - } - } - private bool CheckForOtherWpsWindows() - { - try - { - if (wppProcess != null) - { - var wpsWindows = GetWpsWindowsByProcess(wppProcess.Id); - LogHelper.WriteLogToFile($"检测到{wpsWindows.Count}个WPS窗口", LogHelper.LogType.Trace); - - foreach (var window in wpsWindows) - { - LogHelper.WriteLogToFile($"WPS窗口: 标题='{window.Title}', 类名='{window.ClassName}', 可见={window.IsVisible}, 最小化={window.IsMinimized}", LogHelper.LogType.Trace); - } - - // 只要当前wpp进程没有可见窗口,就允许Kill - return wpsWindows.Any(w => w.IsVisible && !w.IsMinimized); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查WPP窗口失败: {ex}", LogHelper.LogType.Error); - } - return false; // 出错时,默认允许Kill - } - /// - /// WPS窗口信息结构 - /// - private class WpsWindowInfo - { - public IntPtr Handle { get; set; } - public string Title { get; set; } - public string ClassName { get; set; } - public bool IsVisible { get; set; } - public bool IsMinimized { get; set; } - public bool IsMaximized { get; set; } - public ForegroundWindowInfo.RECT Rect { get; set; } - public uint ProcessId { get; set; } - public string ProcessName { get; set; } // 新增 - } - /// - /// 获取指定进程的所有WPS窗口 - /// - private List GetWpsWindowsByProcess(int processId) - { - var wpsWindows = new List(); - - try - { - EnumWindows((hWnd, lParam) => - { - try - { - if (!IsWindow(hWnd)) return true; - - uint windowProcessId; - GetWindowThreadProcessId(hWnd, out windowProcessId); - - if ((int)windowProcessId == processId) - { - var windowInfo = GetWindowInfo(hWnd); - if (IsWpsWindow(windowInfo)) - { - wpsWindows.Add(windowInfo); - LogHelper.WriteLogToFile($"发现WPS窗口: {windowInfo.Title} ({windowInfo.ClassName})", LogHelper.LogType.Trace); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error); - } - return true; - }, IntPtr.Zero); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error); - } - - return wpsWindows; - } - /// - /// 获取窗口详细信息 - /// - private WpsWindowInfo GetWindowInfo(IntPtr hWnd) - { - var windowInfo = new WpsWindowInfo - { - Handle = hWnd, - IsVisible = IsWindowVisible(hWnd), - IsMinimized = IsIconic(hWnd), - IsMaximized = IsZoomed(hWnd) - }; - // 获取窗口标题 - var windowTitle = new StringBuilder(256); - GetWindowText(hWnd, windowTitle, 256); - windowInfo.Title = windowTitle.ToString().Trim(); - // 获取窗口类名 - var className = new StringBuilder(256); - GetClassName(hWnd, className, 256); - windowInfo.ClassName = className.ToString().Trim(); - // 获取窗口位置 - GetWindowRect(hWnd, out ForegroundWindowInfo.RECT rect); - windowInfo.Rect = rect; - // 获取进程ID - uint processId; - GetWindowThreadProcessId(hWnd, out processId); - windowInfo.ProcessId = processId; - // 新增:获取进程名 - windowInfo.ProcessName = ""; - try - { - var proc = Process.GetProcessById((int)processId); - windowInfo.ProcessName = proc.ProcessName.ToLower(); - } - catch { } - return windowInfo; - } - /// - /// 判断是否为WPS窗口 - /// - private bool IsWpsWindow(WpsWindowInfo windowInfo) - { - if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName)) - return false; - var title = windowInfo.Title.ToLower(); - var className = windowInfo.ClassName.ToLower(); - var processName = windowInfo.ProcessName ?? ""; - - // WPS相关关键词 - var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" }; - // 微软Office相关进程名 - var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" }; - - // 只要进程名是微软Office,直接排除 - if (msOfficeProcess.Any(keyword => processName.Contains(keyword))) - return false; - - // 只要进程名是WPS/WPP/Kingsoft,直接通过 - if (wpsKeywords.Any(keyword => processName.Contains(keyword))) - return true; - - // 标题或类名包含WPS相关关键词 - bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword)); - bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword)); - bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp"); - bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0; - - return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize; - } - - /// - /// 获取前台WPS窗口 - /// - private WpsWindowInfo GetForegroundWpsWindow() - { - try - { - var foregroundHwnd = GetForegroundWindow(); - if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd)) - { - var windowInfo = GetWindowInfo(foregroundHwnd); - if (IsWpsWindow(windowInfo)) - { - LogHelper.WriteLogToFile($"前台WPS窗口: {windowInfo.Title}", LogHelper.LogType.Trace); - return windowInfo; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 检查前台WPS窗口是否仍然存在(更精确的检测) - /// - private bool IsForegroundWpsWindowStillActive() - { - try - { - var currentTime = DateTime.Now; - var currentForegroundWindow = GetForegroundWpsWindow(); - - // 检查窗口状态是否发生变化 - bool windowStateChanged = false; - if (lastForegroundWpsWindow != null && currentForegroundWindow != null) - { - // 检查窗口是否发生了变化 - if (lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle || - lastForegroundWpsWindow.Title != currentForegroundWindow.Title) - { - windowStateChanged = true; - LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace); - } - } - else if (lastForegroundWpsWindow == null && currentForegroundWindow != null) - { - // 从无窗口变为有窗口 - windowStateChanged = true; - LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace); - } - else if (lastForegroundWpsWindow != null && currentForegroundWindow == null) - { - // 从有窗口变为无窗口 - windowStateChanged = true; - LogHelper.WriteLogToFile($"前台WPS窗口已消失: {lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace); - } - - // 更新记录 - lastForegroundWpsWindow = currentForegroundWindow; - lastWindowCheckTime = currentTime; - - if (currentForegroundWindow != null) - { - // 验证窗口仍然有效 - if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle)) - { - return true; - } - } - - // 方法2:检查所有WPS进程的活跃窗口 - var wpsProcesses = GetWpsProcesses(); - foreach (var process in wpsProcesses) - { - var windows = GetWpsWindowsByProcess(process.Id); - if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow())) - { - return true; - } - } - - // 方法3:检查顶级WPS窗口 - var topLevelWindows = GetTopLevelWpsWindows(); - if (topLevelWindows.Any(w => w.IsVisible && !w.IsMinimized)) - { - return true; - } - - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取顶级WPS窗口(包括前台窗口和最近的活跃窗口) - /// - private List GetTopLevelWpsWindows() - { - var topLevelWindows = new List(); - - try - { - // 获取前台窗口 - var foregroundWindow = GetForegroundWpsWindow(); - if (foregroundWindow != null) - { - topLevelWindows.Add(foregroundWindow); - } - - // 获取所有可见的WPS窗口,按层级排序 - var allWpsWindows = new List(); - var wpsProcesses = GetWpsProcesses(); - - foreach (var process in wpsProcesses) - { - var windows = GetWpsWindowsByProcess(process.Id); - allWpsWindows.AddRange(windows.Where(w => w.IsVisible && !w.IsMinimized)); - } - - // 按窗口位置排序,优先选择屏幕中央的窗口 - var screenCenter = new Point( - Screen.PrimaryScreen.Bounds.Width / 2, - Screen.PrimaryScreen.Bounds.Height / 2 - ); - - var sortedWindows = allWpsWindows - .Where(w => !topLevelWindows.Any(t => t.Handle == w.Handle)) // 排除已添加的前台窗口 - .OrderBy(w => Math.Abs((w.Rect.Left + w.Rect.Right) / 2 - screenCenter.X) + - Math.Abs((w.Rect.Top + w.Rect.Bottom) / 2 - screenCenter.Y)) - .Take(3); // 取最近的3个窗口 - - topLevelWindows.AddRange(sortedWindows); - - LogHelper.WriteLogToFile($"找到{topLevelWindows.Count}个顶级WPS窗口", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取顶级WPS窗口失败: {ex}", LogHelper.LogType.Error); - } - - return topLevelWindows; - } - - /// - /// 检查是否有活跃的WPS窗口(包括前台窗口) - /// - private bool HasActiveWpsWindows() - { - return HasActiveWpsWindowsWithRetry(3); // 重试3次 - } - - /// - /// 带重试机制的WPS窗口检测 - /// - private bool HasActiveWpsWindowsWithRetry(int maxRetries) - { - for (int attempt = 1; attempt <= maxRetries; attempt++) - { - try - { - LogHelper.WriteLogToFile($"第{attempt}次尝试检测WPS窗口", LogHelper.LogType.Trace); - - // 首先检查前台窗口 - var foregroundWpsWindow = GetForegroundWpsWindow(); - if (foregroundWpsWindow != null) - { - LogHelper.WriteLogToFile($"第{attempt}次尝试检测到前台WPS窗口", LogHelper.LogType.Trace); - return true; - } - - // 检查顶级WPS窗口 - var topLevelWindows = GetTopLevelWpsWindows(); - if (topLevelWindows.Any()) - { - LogHelper.WriteLogToFile($"第{attempt}次尝试检测到{topLevelWindows.Count}个顶级WPS窗口", LogHelper.LogType.Trace); - return true; - } - - // 然后检查所有WPS进程的窗口 - var wpsProcesses = GetWpsProcesses(); - foreach (var process in wpsProcesses) - { - var windows = GetWpsWindowsByProcess(process.Id); - if (windows.Any(w => w.IsVisible && !w.IsMinimized)) - { - LogHelper.WriteLogToFile($"第{attempt}次尝试检测到进程{process.Id}的活跃WPS窗口", LogHelper.LogType.Trace); - return true; - } - } - - // 如果还有重试机会,等待一小段时间再重试 - if (attempt < maxRetries) - { - Thread.Sleep(100); // 等待100ms - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"第{attempt}次尝试检查活跃WPS窗口失败: {ex}", LogHelper.LogType.Error); - - // 如果还有重试机会,等待一小段时间再重试 - if (attempt < maxRetries) - { - Thread.Sleep(100); // 等待100ms - } - } - } - - LogHelper.WriteLogToFile($"经过{maxRetries}次尝试,未检测到活跃的WPS窗口", LogHelper.LogType.Trace); - return false; - } - - /// - /// 获取所有WPS相关进程 - /// - private List GetWpsProcesses() - { - var wpsProcesses = new List(); - try - { - var allProcesses = Process.GetProcesses(); - foreach (var process in allProcesses) - { - try - { - var pname = process.ProcessName.ToLower(); - // 只允许WPS/WPP相关进程,排除PowerPoint及微软Office - if ((pname.Contains("wps") || pname.Contains("wpp") || pname.Contains("presentation")) - && !pname.Contains("powerpnt") - && !pname.Contains("office") - && !pname.Contains("onenote") - && !pname.Contains("excel") - && !pname.Contains("word") - && !pname.Contains("outlook") - && !pname.Contains("microsoft")) - { - wpsProcesses.Add(process); - LogHelper.WriteLogToFile($"发现WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error); - } - return wpsProcesses; - } - - /// - /// 调试方法:输出所有窗口信息 - /// - private void DebugAllWindows() - { - try - { - LogHelper.WriteLogToFile("开始调试所有窗口信息", LogHelper.LogType.Trace); - var windowCount = 0; - - EnumWindows((hWnd, lParam) => - { - try - { - if (!IsWindow(hWnd)) return true; - - var windowInfo = GetWindowInfo(hWnd); - if (!string.IsNullOrEmpty(windowInfo.Title) || !string.IsNullOrEmpty(windowInfo.ClassName)) - { - windowCount++; - LogHelper.WriteLogToFile($"窗口{windowCount}: 标题='{windowInfo.Title}', 类名='{windowInfo.ClassName}', 进程ID={windowInfo.ProcessId}, 可见={windowInfo.IsVisible}, 最小化={windowInfo.IsMinimized}", LogHelper.LogType.Trace); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"调试窗口时出错: {ex}", LogHelper.LogType.Error); - } - return true; - }, IntPtr.Zero); - - LogHelper.WriteLogToFile($"调试完成,共发现{windowCount}个有效窗口", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"调试窗口失败: {ex}", LogHelper.LogType.Error); - } - } - - private bool CheckForWpsWindowsByEnumeration() - { - try - { - var wpsWindowCount = 0; - var currentProcessId = wppProcess?.Id ?? 0; - - EnumWindows((hWnd, lParam) => - { - try - { - uint windowProcessId; - GetWindowThreadProcessId(hWnd, out windowProcessId); - - // 检查是否是WPP进程的窗口 - if (windowProcessId == currentProcessId) - { - var windowTitle = new StringBuilder(256); - GetWindowText(hWnd, windowTitle, 256); - var title = windowTitle.ToString().Trim(); - - // 检查窗口标题是否包含WPS相关标识 - if (!string.IsNullOrEmpty(title) && - (title.Contains("WPS") || title.Contains("演示文稿") || title.Contains(".ppt") || title.Contains(".pptx"))) - { - wpsWindowCount++; - LogHelper.WriteLogToFile($"发现WPS窗口: {title}", LogHelper.LogType.Trace); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error); - } - - return true; // 继续枚举 - }, IntPtr.Zero); - - if (wpsWindowCount > 1) - { - LogHelper.WriteLogToFile($"检测到{wpsWindowCount}个WPS窗口,可能存在多个文档", LogHelper.LogType.Trace); - return true; - } - - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"通过枚举检查WPS窗口失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - private void StopWppProcessCheckTimer() - { - if (wppProcessCheckTimer != null) - { - wppProcessCheckTimer.Stop(); - wppProcessCheckTimer.Dispose(); - wppProcessCheckTimer = null; - } - - wppProcess = null; - hasWppProcessID = false; - wppProcessRecordTime = DateTime.MinValue; - wppProcessCheckCount = 0; - lastForegroundWpsWindow = null; - lastWindowCheckTime = DateTime.MinValue; - LogHelper.WriteLogToFile("停止 WPP 进程检测定时器", LogHelper.LogType.Trace); - } - - /// - /// 计算文件路径的哈希值,用于生成唯一的文件夹标识符 - /// - /// 文件路径 - /// 文件路径的哈希值字符串 - 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 ex) - { - LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error); - return "error"; - } - } - - // 新增:同步保存当前页面墨迹的方法 - private void SaveCurrentSlideInkStrokes() - { - try - { - if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1) - { - var slideShowWindow = pptApplication.SlideShowWindows[1]; - if (slideShowWindow != null && slideShowWindow.View != null) - { - int currentSlide = slideShowWindow.View.CurrentShowPosition; - if (!CanWriteInk(currentSlide)) - { - LogHelper.WriteLogToFile($"墨迹写入被锁定或页码不符,当前页:{currentSlide},锁定页:{lockedSlideIndex}", LogHelper.LogType.Warning); - return; - } - var ms = new MemoryStream(); - inkCanvas.Strokes.Save(ms); - ms.Position = 0; - memoryStreams[currentSlide] = ms; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - - // 翻页后调用 - private void LockInkForCurrentSlide(int slideIndex) - { - inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds); - lockedSlideIndex = slideIndex; - } - - // 写入墨迹前调用 - private bool CanWriteInk(int currentSlideIndex) - { - if (DateTime.Now < inkLockUntil) return false; - if (currentSlideIndex != lockedSlideIndex) return false; - return true; - } } } diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs index 8fc996b1..16809605 100644 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs @@ -57,18 +57,21 @@ namespace Ink_Canvas { List allPageStrokes = new List(); // 检查PPT放映模式下的多页面墨迹 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && pptApplication != null) + if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true) { hasMultiplePages = true; // 收集PPT放映模式下的所有页面墨迹 - for (int i = 1; i <= pptApplication.SlideShowWindows[1].Presentation.Slides.Count; i++) + var totalSlides = _pptManager.SlidesCount; + var currentSlide = _pptManager.GetCurrentSlideNumber(); + + for (int i = 1; i <= totalSlides; i++) { - if (memoryStreams[i] != null && memoryStreams[i].Length > 8) + var slideStrokes = _pptInkManager?.LoadSlideStrokes(i); + if (slideStrokes != null && slideStrokes.Count > 0) { - memoryStreams[i].Position = 0; - allPageStrokes.Add(new StrokeCollection(memoryStreams[i])); + allPageStrokes.Add(slideStrokes); } - else if (i == previousSlideID && inkCanvas.Strokes.Count > 0) + else if (i == currentSlide && inkCanvas.Strokes.Count > 0) { // 当前页面的墨迹 allPageStrokes.Add(inkCanvas.Strokes.Clone()); @@ -485,10 +488,8 @@ namespace Ink_Canvas { timeMachine.ClearStrokeHistory(); // 重置PPT墨迹存储 - if (memoryStreams == null) { - memoryStreams = new MemoryStream[50]; - } - + _pptInkManager?.ClearAllStrokes(); + // 读取所有页面的墨迹文件 var files = Directory.GetFiles(tempDir, "page_*.icstk"); foreach (var file in files) { @@ -497,23 +498,19 @@ namespace Ink_Canvas { using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read)) { var strokes = new StrokeCollection(fs); if (strokes.Count > 0) { - var ms = new MemoryStream(); - strokes.Save(ms); - ms.Position = 0; - memoryStreams[pageNumber] = ms; + _pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes); } } } } - + // 恢复当前页面的墨迹 - if (pptApplication.SlideShowWindows.Count > 0) { - int currentSlide = pptApplication.SlideShowWindows[1].View.CurrentShowPosition; - if (memoryStreams[currentSlide] != null && memoryStreams[currentSlide].Length > 0) { - memoryStreams[currentSlide].Position = 0; - inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[currentSlide])); + if (_pptManager?.IsInSlideShow == true) { + int currentSlide = _pptManager.GetCurrentSlideNumber(); + var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide); + if (currentStrokes != null && currentStrokes.Count > 0) { + inkCanvas.Strokes.Add(currentStrokes); } - previousSlideID = currentSlide; } LogHelper.WriteLogToFile($"成功恢复PPT墨迹,共{files.Length}页"); diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs index dc3c2c53..6e7042fb 100644 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs @@ -6,6 +6,7 @@ using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; +using Ink_Canvas.Helpers; using iNKORE.UI.WPF.Modern.Controls; using Point = System.Windows.Point; diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index b0fd4ffb..527cc08b 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -95,10 +95,19 @@ namespace Ink_Canvas { Settings.PowerPointSettings.PowerPointSupport = ToggleSwitchSupportPowerPoint.IsOn; SaveSettingsToFile(); + // 使用新的PPT管理器 if (Settings.PowerPointSettings.PowerPointSupport) - timerCheckPPT.Start(); + { + if (_pptManager == null) + { + InitializePPTManagers(); + } + StartPPTMonitoring(); + } else - timerCheckPPT.Stop(); + { + StopPPTMonitoring(); + } } private void ToggleSwitchShowCanvasAtNewSlideShow_Toggled(object sender, RoutedEventArgs e) { @@ -419,7 +428,12 @@ namespace Ink_Canvas { if (!isLoaded) return; Settings.PowerPointSettings.ShowPPTButton = ToggleSwitchShowPPTButton.IsOn; SaveSettingsToFile(); - UpdatePPTBtnDisplaySettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null) + { + _pptUIManager.ShowPPTButton = Settings.PowerPointSettings.ShowPPTButton; + _pptUIManager.UpdateNavigationPanelsVisibility(); + } UpdatePPTBtnPreview(); } @@ -436,7 +450,12 @@ namespace Ink_Canvas { c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.UpdateNavigationPanelsVisibility(); + } UpdatePPTBtnPreview(); } @@ -448,7 +467,12 @@ namespace Ink_Canvas { c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.UpdateNavigationPanelsVisibility(); + } UpdatePPTBtnPreview(); } @@ -460,7 +484,12 @@ namespace Ink_Canvas { c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.UpdateNavigationPanelsVisibility(); + } UpdatePPTBtnPreview(); } @@ -472,7 +501,12 @@ namespace Ink_Canvas { c[3] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.UpdateNavigationPanelsVisibility(); + } UpdatePPTBtnPreview(); } @@ -484,7 +518,12 @@ namespace Ink_Canvas { c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; + _pptUIManager.UpdateNavigationButtonStyles(); + } UpdatePPTBtnPreview(); } @@ -496,7 +535,12 @@ namespace Ink_Canvas { c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; + _pptUIManager.UpdateNavigationButtonStyles(); + } UpdatePPTBtnPreview(); } @@ -508,7 +552,12 @@ namespace Ink_Canvas { c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; + _pptUIManager.UpdateNavigationButtonStyles(); + } UpdatePPTBtnPreview(); } @@ -520,7 +569,12 @@ namespace Ink_Canvas { c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + // 更新PPT UI管理器设置 + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption; + _pptUIManager.UpdateNavigationButtonStyles(); + } UpdatePPTBtnPreview(); } @@ -532,7 +586,7 @@ namespace Ink_Canvas { c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + UpdatePPTUIManagerSettings(); UpdatePPTBtnPreview(); } @@ -544,7 +598,7 @@ namespace Ink_Canvas { c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1'; Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c)); SaveSettingsToFile(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus(); + UpdatePPTUIManagerSettings(); UpdatePPTBtnPreview(); } @@ -552,7 +606,7 @@ namespace Ink_Canvas { if (!isLoaded) return; Settings.PowerPointSettings.PPTLSButtonPosition = (int)PPTButtonLeftPositionValueSlider.Value; UpdatePPTBtnSlidersStatus(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + UpdatePPTUIManagerSettings(); SliderDelayAction.DebounceAction(2000, null, SaveSettingsToFile); UpdatePPTBtnPreview(); } @@ -686,11 +740,28 @@ namespace Ink_Canvas { if (!isLoaded) return; Settings.PowerPointSettings.PPTRSButtonPosition = (int)PPTButtonRightPositionValueSlider.Value; UpdatePPTBtnSlidersStatus(); - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus(); + UpdatePPTUIManagerSettings(); SliderDelayAction.DebounceAction(2000,null, SaveSettingsToFile); UpdatePPTBtnPreview(); } + /// + /// 更新PPT UI管理器设置的通用方法 + /// + private void UpdatePPTUIManagerSettings() + { + if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible) + { + _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; + _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; + _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption; + _pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition; + _pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition; + _pptUIManager.UpdateNavigationPanelsVisibility(); + _pptUIManager.UpdateNavigationButtonStyles(); + } + } + private void UpdatePPTBtnPreview() { //new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/unfold-chevron.png")); var bopt = Settings.PowerPointSettings.PPTBButtonsOption.ToString(); diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 8f761e53..03e8dcd7 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -278,10 +278,10 @@ namespace Ink_Canvas { if (Settings.PowerPointSettings.PowerPointSupport) { ToggleSwitchSupportPowerPoint.IsOn = true; - timerCheckPPT.Start(); + // PPT监控将在Window_Loaded中启动 } else { ToggleSwitchSupportPowerPoint.IsOn = false; - timerCheckPPT.Stop(); + // PPT监控将保持停止状态 } ToggleSwitchShowCanvasAtNewSlideShow.IsOn = Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow; diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs index 4f04ed8d..bcd43ce2 100644 --- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs +++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs @@ -653,9 +653,9 @@ namespace Ink_Canvas { // New method: Checks if a stroke is potentially a straight line private bool IsPotentialStraightLine(Stroke stroke) { // 确保有足够的点来进行线条分析 - if (stroke.StylusPoints.Count < 5) + if (stroke.StylusPoints.Count < 5) return false; - + Point start = stroke.StylusPoints.First().ToPoint(); Point end = stroke.StylusPoints.Last().ToPoint(); double lineLength = GetDistance(start, end); @@ -664,6 +664,14 @@ namespace Ink_Canvas { // 线条必须足够长才考虑拉直,使用自适应阈值 if (lineLength < adaptiveThreshold) return false; + + // 新增:检查墨迹复杂度,避免将复杂图形拉直 + if (IsComplexShape(stroke)) + return false; + + // 新增:检查是否为明显的曲线 + if (IsObviousCurve(stroke)) + return false; // 获取用户设置的灵敏度值,确保使用正确的设置 double sensitivity = Settings.InkToShape.LineStraightenSensitivity; @@ -726,7 +734,284 @@ namespace Ink_Canvas { return true; } - + + /// + /// 检查墨迹是否为复杂形状(如一团墨迹、涂鸦等) + /// + private bool IsComplexShape(Stroke stroke) + { + if (stroke.StylusPoints.Count < 10) return false; + + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + double lineLength = GetDistance(start, end); + + // 计算墨迹的实际路径长度 + double actualLength = 0; + for (int i = 1; i < stroke.StylusPoints.Count; i++) + { + Point p1 = stroke.StylusPoints[i - 1].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + actualLength += GetDistance(p1, p2); + } + + // 如果实际路径长度远大于直线距离,说明是复杂形状 + double complexityRatio = actualLength / Math.Max(lineLength, 1); + if (complexityRatio > 2.5) // 实际路径是直线距离的2.5倍以上 + { + Debug.WriteLine($"检测到复杂形状:复杂度比率 = {complexityRatio:F2}"); + return true; + } + + // 检查方向变化次数 + int directionChanges = CountDirectionChanges(stroke); + int maxAllowedChanges = Math.Max(3, stroke.StylusPoints.Count / 20); // 动态阈值 + if (directionChanges > maxAllowedChanges) + { + Debug.WriteLine($"检测到复杂形状:方向变化次数 = {directionChanges},阈值 = {maxAllowedChanges}"); + return true; + } + + // 检查是否有明显的回环或重叠 + if (HasSignificantLoops(stroke)) + { + Debug.WriteLine("检测到复杂形状:存在明显回环"); + return true; + } + + return false; + } + + /// + /// 检查是否为明显的曲线(如圆弧、抛物线等) + /// + private bool IsObviousCurve(Stroke stroke) + { + if (stroke.StylusPoints.Count < 10) return false; + + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + double lineLength = GetDistance(start, end); + + // 检查曲率一致性 + if (HasConsistentCurvature(stroke)) + { + Debug.WriteLine("检测到明显曲线:曲率一致"); + return true; + } + + // 检查中点偏移(对圆弧特别有效) + int midIndex = stroke.StylusPoints.Count / 2; + Point midPoint = stroke.StylusPoints[midIndex].ToPoint(); + double midDeviation = DistanceFromLineToPoint(start, end, midPoint); + + // 如果中点偏移超过线长的15%,且偏移方向一致,可能是圆弧 + if (midDeviation > lineLength * 0.15) + { + // 检查偏移方向的一致性 + if (IsConsistentArcDirection(stroke)) + { + Debug.WriteLine($"检测到明显曲线:中点偏移 = {midDeviation:F2},线长 = {lineLength:F2}"); + return true; + } + } + + return false; + } + + /// + /// 计算方向变化次数 + /// + private int CountDirectionChanges(Stroke stroke) + { + if (stroke.StylusPoints.Count < 3) return 0; + + int changes = 0; + double lastAngle = 0; + bool hasLastAngle = false; + + for (int i = 1; i < stroke.StylusPoints.Count - 1; i++) + { + Point p1 = stroke.StylusPoints[i - 1].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + Point p3 = stroke.StylusPoints[i + 1].ToPoint(); + + // 计算角度变化 + double angle1 = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); + double angle2 = Math.Atan2(p3.Y - p2.Y, p3.X - p2.X); + double angleDiff = Math.Abs(angle2 - angle1); + + // 处理角度跨越问题 + if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; + + // 如果角度变化超过30度,认为是方向变化 + if (angleDiff > Math.PI / 6) // 30度 + { + if (hasLastAngle && Math.Abs(angleDiff - lastAngle) > Math.PI / 12) // 15度 + { + changes++; + } + lastAngle = angleDiff; + hasLastAngle = true; + } + } + + return changes; + } + + /// + /// 检查是否有明显的回环 + /// + private bool HasSignificantLoops(Stroke stroke) + { + if (stroke.StylusPoints.Count < 20) return false; + + // 检查起点和终点是否接近(可能是闭合图形) + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + double startEndDistance = GetDistance(start, end); + + // 计算平均点间距 + double totalDistance = 0; + for (int i = 1; i < stroke.StylusPoints.Count; i++) + { + Point p1 = stroke.StylusPoints[i - 1].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + totalDistance += GetDistance(p1, p2); + } + double avgPointDistance = totalDistance / (stroke.StylusPoints.Count - 1); + + // 如果起点和终点很接近,可能是闭合图形 + if (startEndDistance < avgPointDistance * 5) + { + return true; + } + + // 检查是否有点重复经过相似区域 + int overlapCount = 0; + double overlapThreshold = avgPointDistance * 3; + + for (int i = 0; i < stroke.StylusPoints.Count - 10; i += 5) + { + Point p1 = stroke.StylusPoints[i].ToPoint(); + for (int j = i + 10; j < stroke.StylusPoints.Count; j += 5) + { + Point p2 = stroke.StylusPoints[j].ToPoint(); + if (GetDistance(p1, p2) < overlapThreshold) + { + overlapCount++; + if (overlapCount > 3) return true; + } + } + } + + return false; + } + + /// + /// 检查曲率是否一致(用于识别圆弧等规则曲线) + /// + private bool HasConsistentCurvature(Stroke stroke) + { + if (stroke.StylusPoints.Count < 15) return false; + + List curvatures = new List(); + + // 计算每个点的曲率 + for (int i = 2; i < stroke.StylusPoints.Count - 2; i++) + { + Point p1 = stroke.StylusPoints[i - 2].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + Point p3 = stroke.StylusPoints[i + 2].ToPoint(); + + double curvature = CalculateCurvature(p1, p2, p3); + if (!double.IsNaN(curvature) && !double.IsInfinity(curvature)) + { + curvatures.Add(Math.Abs(curvature)); + } + } + + if (curvatures.Count < 5) return false; + + // 计算曲率的标准差 + double avgCurvature = curvatures.Average(); + double variance = curvatures.Select(c => Math.Pow(c - avgCurvature, 2)).Average(); + double stdDev = Math.Sqrt(variance); + + // 如果曲率变化很小且平均曲率不为零,可能是规则曲线 + return avgCurvature > 0.001 && stdDev / avgCurvature < 0.5; + } + + /// + /// 检查圆弧方向是否一致 + /// + private bool IsConsistentArcDirection(Stroke stroke) + { + if (stroke.StylusPoints.Count < 10) return false; + + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + + int positiveDeviations = 0; + int negativeDeviations = 0; + + // 检查多个点相对于直线的偏移方向 + for (int i = 1; i < stroke.StylusPoints.Count - 1; i += Math.Max(1, stroke.StylusPoints.Count / 10)) + { + Point p = stroke.StylusPoints[i].ToPoint(); + double signedDistance = SignedDistanceFromLineToPoint(start, end, p); + + if (Math.Abs(signedDistance) > 5) // 忽略很小的偏移 + { + if (signedDistance > 0) positiveDeviations++; + else negativeDeviations++; + } + } + + // 如果大部分点都在直线的同一侧,说明是一致的弧形 + int totalSignificantDeviations = positiveDeviations + negativeDeviations; + if (totalSignificantDeviations < 3) return false; + + double consistency = Math.Max(positiveDeviations, negativeDeviations) / (double)totalSignificantDeviations; + return consistency > 0.8; // 80%的点在同一侧 + } + + /// + /// 计算三点的曲率 + /// + private double CalculateCurvature(Point p1, Point p2, Point p3) + { + // 使用三点计算曲率的公式 + double a = GetDistance(p1, p2); + double b = GetDistance(p2, p3); + double c = GetDistance(p1, p3); + + if (a == 0 || b == 0 || c == 0) return 0; + + // 使用海伦公式计算面积 + double s = (a + b + c) / 2; + double area = Math.Sqrt(s * (s - a) * (s - b) * (s - c)); + + // 曲率 = 4 * 面积 / (a * b * c) + return 4 * area / (a * b * c); + } + + /// + /// 计算点到直线的有符号距离 + /// + private double SignedDistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point) + { + // 使用叉积计算有符号距离 + double dx = lineEnd.X - lineStart.X; + double dy = lineEnd.Y - lineStart.Y; + double lineLength = Math.Sqrt(dx * dx + dy * dy); + + if (lineLength == 0) return 0; + + return ((lineEnd.Y - lineStart.Y) * point.X - (lineEnd.X - lineStart.X) * point.Y + + lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength; + } + // New method: Determines if a stroke should be straightened into a line private bool ShouldStraightenLine(Stroke stroke) { Point start = stroke.StylusPoints.First().ToPoint(); @@ -737,8 +1022,24 @@ namespace Ink_Canvas { double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale(); // 如果线条太短,不进行拉直处理,使用自适应阈值 if (lineLength < adaptiveThreshold) { - // 显示调试信息 - 线条长度不足 - // MessageBox.Show($"线条太短: {lineLength} < {Settings.Canvas.AutoStraightenLineThreshold}", "调试信息"); + Debug.WriteLine($"线条太短: {lineLength} < {adaptiveThreshold}"); + return false; + } + + // 新增:再次检查复杂度(双重保险) + if (IsComplexShape(stroke)) + { + Debug.WriteLine("拒绝拉直:检测到复杂形状"); + return false; + } + + // 新增:检查线条的直线度评分 + double straightnessScore = CalculateStraightnessScore(stroke); + double minStraightnessThreshold = 0.7; // 最低直线度要求 + + if (straightnessScore < minStraightnessThreshold) + { + Debug.WriteLine($"拒绝拉直:直线度评分过低 {straightnessScore:F3} < {minStraightnessThreshold}"); return false; } @@ -976,9 +1277,111 @@ namespace Ink_Canvas { } } - Debug.WriteLine("接受拉直"); + Debug.WriteLine($"接受拉直:直线度评分 = {straightnessScore:F3}"); return true; } + + /// + /// 计算墨迹的直线度评分(0-1,1表示完美直线) + /// + private double CalculateStraightnessScore(Stroke stroke) + { + if (stroke.StylusPoints.Count < 3) return 0; + + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + double lineLength = GetDistance(start, end); + + if (lineLength == 0) return 0; + + // 1. 计算偏差评分(基于点到直线的距离) + double totalDeviation = 0; + double maxDeviation = 0; + int pointCount = 0; + + foreach (StylusPoint sp in stroke.StylusPoints) + { + Point p = sp.ToPoint(); + double deviation = DistanceFromLineToPoint(start, end, p); + totalDeviation += deviation; + maxDeviation = Math.Max(maxDeviation, deviation); + pointCount++; + } + + double avgDeviation = totalDeviation / pointCount; + + // 偏差评分:基于平均偏差和最大偏差 + double deviationScore = Math.Max(0, 1 - (avgDeviation / (lineLength * 0.05)) - (maxDeviation / (lineLength * 0.1))); + + // 2. 计算方向一致性评分 + double directionScore = CalculateDirectionConsistency(stroke); + + // 3. 计算路径效率评分(实际路径长度 vs 直线距离) + double actualLength = 0; + for (int i = 1; i < stroke.StylusPoints.Count; i++) + { + Point p1 = stroke.StylusPoints[i - 1].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + actualLength += GetDistance(p1, p2); + } + double efficiencyScore = Math.Max(0, Math.Min(1, lineLength / actualLength)); + + // 4. 计算端点连接度评分(起点到终点的直接性) + double endpointScore = 1.0; // 默认满分,因为我们已经有了起点和终点 + + // 综合评分(加权平均) + double finalScore = (deviationScore * 0.4 + directionScore * 0.3 + efficiencyScore * 0.2 + endpointScore * 0.1); + + Debug.WriteLine($"直线度评分详情: 偏差={deviationScore:F3}, 方向={directionScore:F3}, 效率={efficiencyScore:F3}, 综合={finalScore:F3}"); + + return Math.Max(0, Math.Min(1, finalScore)); + } + + /// + /// 计算方向一致性评分 + /// + private double CalculateDirectionConsistency(Stroke stroke) + { + if (stroke.StylusPoints.Count < 5) return 1.0; + + Point start = stroke.StylusPoints.First().ToPoint(); + Point end = stroke.StylusPoints.Last().ToPoint(); + + // 目标方向 + double targetAngle = Math.Atan2(end.Y - start.Y, end.X - start.X); + + double totalAngleDifference = 0; + int segmentCount = 0; + + // 计算每个线段与目标方向的角度差 + for (int i = 1; i < stroke.StylusPoints.Count; i++) + { + Point p1 = stroke.StylusPoints[i - 1].ToPoint(); + Point p2 = stroke.StylusPoints[i].ToPoint(); + + double segmentLength = GetDistance(p1, p2); + if (segmentLength < 2) continue; // 忽略太短的线段 + + double segmentAngle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); + double angleDiff = Math.Abs(segmentAngle - targetAngle); + + // 处理角度跨越问题 + if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; + + totalAngleDifference += angleDiff; + segmentCount++; + } + + if (segmentCount == 0) return 1.0; + + double avgAngleDifference = totalAngleDifference / segmentCount; + + // 将角度差转换为评分(0-1) + // 0度差 = 1分,90度差 = 0分 + double directionScore = Math.Max(0, 1 - (avgAngleDifference / (Math.PI / 2))); + + return directionScore; + } // New method: Creates a straight line stroke between two points private StylusPointCollection CreateStraightLine(Point start, Point end) { diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs index d5409d6a..6642350e 100644 --- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs +++ b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs @@ -139,8 +139,28 @@ namespace Ink_Canvas { targetCanvas.Children.Remove(item.InsertedElement); } else { // Redo: 添加元素 - if (item.InsertedElement != null && !targetCanvas.Children.Contains(item.InsertedElement)) + if (item.InsertedElement != null && !targetCanvas.Children.Contains(item.InsertedElement)) { targetCanvas.Children.Add(item.InsertedElement); + + // 重新绑定事件处理器(仅对主画布) + if (targetCanvas == inkCanvas) { + if (item.InsertedElement is Image img) { + img.MouseDown -= UIElement_MouseDown; + img.MouseDown += UIElement_MouseDown; + img.IsManipulationEnabled = true; + + // 重新应用CenterAndScaleElement变换 + CenterAndScaleElement(img); + } else if (item.InsertedElement is MediaElement media) { + media.MouseDown -= UIElement_MouseDown; + media.MouseDown += UIElement_MouseDown; + media.IsManipulationEnabled = true; + + // 重新应用CenterAndScaleElement变换 + CenterAndScaleElement(media); + } + } + } } } diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs index 26d3dd55..f1948f2d 100644 --- a/Ink Canvas/MainWindow_cs/MW_Timer.cs +++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs @@ -88,8 +88,9 @@ namespace Ink_Canvas { } private void InitTimers() { - timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed; - timerCheckPPT.Interval = 500; + // PPT检查现在由PPTManager处理,不再需要定时器 + // timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed; + // timerCheckPPT.Interval = 500; timerKillProcess.Elapsed += TimerKillProcess_Elapsed; timerKillProcess.Interval = 2000; timerCheckAutoFold.Elapsed += timerCheckAutoFold_Elapsed; diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs index c182ced8..d3c22217 100644 --- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs +++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs @@ -62,21 +62,20 @@ namespace Ink_Canvas var mainWin = (MainWindow)Current.MainWindow; if (mainWin.IsLoaded) { IsAppExitByUser = true; - + try { - // 启动新实例,添加 -m 参数允许多实例启动 + // 启动新实例 string exePath = Process.GetCurrentProcess().MainModule.FileName; ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = exePath; - startInfo.Arguments = "-m"; startInfo.UseShellExecute = true; - + // 启动进程但不等待 Process.Start(startInfo); } catch (Exception ex) { LogHelper.NewLog($"重启程序时出错: {ex.Message}"); } - + // 退出当前实例 Current.Shutdown(); } diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs index ad173d2c..9c2ed554 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/Ink Canvas/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Windows; [assembly: AssemblyTitle("InkCanvasForClass")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("HARKOTEK Studio")] +[assembly: AssemblyCompany("CJK_mkp")] [assembly: AssemblyProduct("InkCanvasForClass")] [assembly: AssemblyCopyright("© Copyright HARKOTEK Studio 2024-now")] [assembly: AssemblyTrademark("")] @@ -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.5.0")] -[assembly: AssemblyFileVersion("1.7.5.0")] +[assembly: AssemblyVersion("1.7.6.0")] +[assembly: AssemblyFileVersion("1.7.6.0")] diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 17d7b2d5..8d9291f2 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -56,7 +56,7 @@ namespace Ink_Canvas [JsonProperty("useHardwareAcceleration")] public bool UseHardwareAcceleration { get; set; } = true; // 默认启用硬件加速 [JsonProperty("inkSmoothingQuality")] - public int InkSmoothingQuality { get; set; } = 1; // 0-低质量高性能, 1-平衡, 2-高质量低性能 + public int InkSmoothingQuality { get; set; } = 2; // 0-低质量高性能, 1-平衡, 2-高质量低性能,默认为高质量 [JsonProperty("maxConcurrentSmoothingTasks")] public int MaxConcurrentSmoothingTasks { get; set; } // 0表示自动检测CPU核心数 [JsonProperty("clearCanvasAndClearTimeMachine")] diff --git a/Ink Canvas/app.manifest b/Ink Canvas/app.manifest index df1798c0..89e4303d 100644 --- a/Ink Canvas/app.manifest +++ b/Ink Canvas/app.manifest @@ -1,6 +1,6 @@  - + diff --git a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache b/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache index 4af914e2..df53462f 100644 Binary files a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache and b/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache differ