diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 7884baab..84446eea 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -29,7 +29,7 @@ namespace Ink_Canvas.Helpers #endregion #region Properties - public object PPTApplication { get; private set; } + public dynamic PPTApplication { get; private set; } public dynamic CurrentPresentation { get; private set; } public dynamic CurrentSlides { get; private set; } public dynamic CurrentSlide { get; private set; } @@ -44,8 +44,7 @@ namespace Ink_Canvas.Helpers if (!Marshal.IsComObject(PPTApplication)) return false; // 尝试访问一个简单的属性来验证连接是否有效 - dynamic app = PPTApplication; - var _ = app.Name; + var _ = PPTApplication.Name; return true; } catch (COMException comEx) @@ -75,8 +74,7 @@ namespace Ink_Canvas.Helpers { if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false; - dynamic app = PPTApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows == null) return false; dynamic ssw = slideShowWindows; @@ -130,9 +128,9 @@ namespace Ink_Canvas.Helpers #endregion #region Private Fields - private Timer _connectionCheckTimer; - private Timer _slideShowStateCheckTimer; - private Timer _wpsProcessCheckTimer; + private Thread _monitoringThread; + private bool _shouldStop = false; + private readonly object _monitoringLock = new object(); private Process _wpsProcess; private bool _isModuleUnloading = false; private bool _hasWpsProcessId; @@ -147,7 +145,7 @@ namespace Ink_Canvas.Helpers private dynamic _pptActivePresentation; private dynamic _pptSlideShowWindow; private int _polling = 0; - private bool _forcePolling = false; + private bool _forcePolling = true; private bool _bindingEvents = false; private DateTime _updateTime; private int _lastPolledSlideNumber = -1; @@ -156,36 +154,48 @@ namespace Ink_Canvas.Helpers #region Constructor & Initialization public PPTManager() { - InitializeConnectionTimer(); - } - - private void InitializeConnectionTimer() - { - _connectionCheckTimer = new Timer(500); - _connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed; - _connectionCheckTimer.AutoReset = true; - - _slideShowStateCheckTimer = new Timer(1000); - _slideShowStateCheckTimer.Elapsed += OnSlideShowStateCheckTimerElapsed; - _slideShowStateCheckTimer.AutoReset = true; } public void StartMonitoring() { - if (!_disposed) + if (_disposed) return; + + lock (_monitoringLock) { - _connectionCheckTimer?.Start(); - _slideShowStateCheckTimer?.Start(); + if (_monitoringThread != null && _monitoringThread.IsAlive) + { + return; // 已经在运行 + } + + _shouldStop = false; + _monitoringThread = new Thread(PptComService) + { + IsBackground = true, + Name = "PPTMonitoringThread" + }; + _monitoringThread.Start(); LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace); } } public void StopMonitoring() { - _connectionCheckTimer?.Stop(); - _slideShowStateCheckTimer?.Stop(); - DisconnectFromPPT(); - LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace); + lock (_monitoringLock) + { + _shouldStop = true; + + if (_monitoringThread != null && _monitoringThread.IsAlive) + { + // 等待线程退出,最多等待2秒 + if (!_monitoringThread.Join(2000)) + { + LogHelper.WriteLogToFile("等待监控线程退出超时", LogHelper.LogType.Warning); + } + } + + DisconnectFromPPT(); + LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace); + } } #endregion @@ -287,8 +297,7 @@ namespace Ink_Canvas.Helpers { try { - dynamic app = PPTApplication; - _pptActivePresentation = app.ActivePresentation; + _pptActivePresentation = PPTApplication.ActivePresentation; if (_pptActivePresentation != null) { LogHelper.WriteLogToFile("轮询模式:初始化_pptActivePresentation", LogHelper.LogType.Trace); @@ -326,8 +335,7 @@ namespace Ink_Canvas.Helpers try { - dynamic app = PPTApplication; - activePresentation = app.ActivePresentation; + activePresentation = PPTApplication.ActivePresentation; if (activePresentation != null && _pptActivePresentation != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation)) { @@ -357,12 +365,11 @@ namespace Ink_Canvas.Helpers bool isSlideShowActive = false; try { - dynamic app = PPTApplication; if (activePresentation == null) { try { - activePresentation = app.ActivePresentation; + activePresentation = PPTApplication.ActivePresentation; } catch (Exception ex) { @@ -374,7 +381,7 @@ namespace Ink_Canvas.Helpers { try { - dynamic slideShowWindows = app.SlideShowWindows; + dynamic slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null && slideShowWindows.Count > 0) { isSlideShowActive = true; @@ -424,13 +431,9 @@ namespace Ink_Canvas.Helpers if (isSlideShowActive) { - // 在轮询模式下,更频繁地检查(每500ms,与定时器同步) - // 否则每3秒检查一次 - bool shouldPoll = _forcePolling || (DateTime.Now - _updateTime).TotalMilliseconds > 3000; - - if (shouldPoll) + if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling) { - LogHelper.WriteLogToFile($"开始轮询检查: forcePolling={_forcePolling}, 距离上次更新={(DateTime.Now - _updateTime).TotalMilliseconds}ms", LogHelper.LogType.Trace); + LogHelper.WriteLogToFile($"轮询", LogHelper.LogType.Trace); try { @@ -453,7 +456,6 @@ namespace Ink_Canvas.Helpers { SlidesCount = 0; _polling = 0; - LogHelper.WriteLogToFile("轮询: 无法获取总页数", LogHelper.LogType.Trace); } else { @@ -470,10 +472,7 @@ namespace Ink_Canvas.Helpers if (currentPage >= tempTotalPage) _polling = 1; else _polling = 0; - LogHelper.WriteLogToFile($"轮询: 当前页={currentPage}, 总页={tempTotalPage}, 上次页={_lastPolledSlideNumber}, forcePolling={_forcePolling}", LogHelper.LogType.Trace); - - // 在轮询模式下,检测页码变化并触发事件 - if (_forcePolling && _lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) + if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) { try { @@ -501,7 +500,7 @@ namespace Ink_Canvas.Helpers catch (Exception ex) { SlidesCount = 0; - LogHelper.WriteLogToFile($"轮询检查异常: {ex.Message}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning); } finally { @@ -517,17 +516,16 @@ namespace Ink_Canvas.Helpers { int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow); - // 在轮询模式下,检测页码变化并触发事件 - if (_forcePolling && _lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) + if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber) { try { - LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage}", LogHelper.LogType.Trace); + LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace); SlideShowNextSlide?.Invoke(_pptSlideShowWindow); } catch (Exception ex) { - LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Trace); + LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning); } } @@ -683,10 +681,11 @@ namespace Ink_Canvas.Helpers { try { - Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application); - if (appType.IsInstanceOfType(PPTApplication)) + // 使用 as 操作符进行类型转换,与 inkeys 保持一致 + Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (pptAppForEvents != null) { - Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication; pptAppForEvents.SlideShowNextSlide += new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); pptAppForEvents.SlideShowBegin += new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); pptAppForEvents.SlideShowEnd += new EApplication_SlideShowEndEventHandler(OnSlideShowEnd); @@ -701,7 +700,6 @@ namespace Ink_Canvas.Helpers } _bindingEvents = true; - _forcePolling = false; LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace); } @@ -732,6 +730,9 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"无法注册事件: {ex.Message}", LogHelper.LogType.Warning); } + _bindingEvents = false; + _forcePolling = true; + if (_pptActivePresentation != null) { UpdateCurrentPresentationInfo(); @@ -773,10 +774,11 @@ namespace Ink_Canvas.Helpers { try { - Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application); - if (appType.IsInstanceOfType(PPTApplication)) + // 使用 as 操作符进行类型转换,与 inkeys 保持一致 + Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) { - Microsoft.Office.Interop.PowerPoint.Application app = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication; app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEnd); @@ -789,7 +791,6 @@ namespace Ink_Canvas.Helpers } _bindingEvents = false; - _forcePolling = false; } } catch { } @@ -840,7 +841,7 @@ namespace Ink_Canvas.Helpers CurrentSlide = null; SlidesCount = 0; _polling = 0; - _forcePolling = false; + _forcePolling = true; _bindingEvents = false; GC.Collect(); @@ -993,8 +994,7 @@ namespace Ink_Canvas.Helpers } else { - dynamic app = PPTApplication; - activeWindow = app.ActiveWindow; + activeWindow = PPTApplication.ActiveWindow; if (activeWindow != null) { dynamic aw = activeWindow; @@ -1095,8 +1095,7 @@ namespace Ink_Canvas.Helpers { try { - dynamic app = PPTApplication; - _pptActivePresentation = app.ActivePresentation; + _pptActivePresentation = PPTApplication.ActivePresentation; _updateTime = DateTime.Now; } catch { } @@ -1122,10 +1121,11 @@ namespace Ink_Canvas.Helpers { try { - Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application); - if (appType.IsInstanceOfType(PPTApplication)) + // 使用 as 操作符进行类型转换,与 inkeys 保持一致 + Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) { - Microsoft.Office.Interop.PowerPoint.Application app = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication; app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide); app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin); app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEnd); @@ -1138,7 +1138,6 @@ namespace Ink_Canvas.Helpers } _bindingEvents = false; - _forcePolling = false; } DisconnectFromPPT(); @@ -1258,8 +1257,7 @@ namespace Ink_Canvas.Helpers if (IsInSlideShow) { - dynamic app = PPTApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1343,8 +1341,7 @@ namespace Ink_Canvas.Helpers { try { - dynamic app = PPTApplication; - object slideShowWindows = app.SlideShowWindows; + object slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1407,8 +1404,7 @@ namespace Ink_Canvas.Helpers { try { - dynamic app = PPTApplication; - object slideShowWindows = app.SlideShowWindows; + object slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1469,8 +1465,7 @@ namespace Ink_Canvas.Helpers if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; if (!Marshal.IsComObject(PPTApplication)) return false; - dynamic app = PPTApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1586,8 +1581,7 @@ namespace Ink_Canvas.Helpers } } - dynamic app = PPTApplication; - activeWindow = app.ActiveWindow; + activeWindow = PPTApplication.ActiveWindow; if (activeWindow != null) { dynamic aw = activeWindow; @@ -1701,8 +1695,7 @@ namespace Ink_Canvas.Helpers } } - dynamic app = PPTApplication; - activeWindow = app.ActiveWindow; + activeWindow = PPTApplication.ActiveWindow; if (activeWindow != null) { dynamic aw = activeWindow; @@ -1824,8 +1817,7 @@ namespace Ink_Canvas.Helpers return false; } - dynamic app = PPTApplication; - slideShowWindows = app.SlideShowWindows; + slideShowWindows = PPTApplication.SlideShowWindows; if (slideShowWindows != null) { dynamic ssw = slideShowWindows; @@ -1898,12 +1890,11 @@ namespace Ink_Canvas.Helpers Process wpsProcess = null; // 方法1:通过应用程序路径检测 - dynamic app = PPTApplication; - if (app.Path.Contains("Kingsoft\\WPS Office\\") || - app.Path.Contains("WPS Office\\")) + if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") || + PPTApplication.Path.Contains("WPS Office\\")) { uint processId; - GetWindowThreadProcessId((IntPtr)app.HWND, out processId); + GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId); wpsProcess = Process.GetProcessById((int)processId); } @@ -2159,10 +2150,9 @@ namespace Ink_Canvas.Helpers { if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) { - dynamic app = PPTApplication; - if (app.SlideShowWindows?.Count > 0) + if (PPTApplication.SlideShowWindows?.Count > 0) { - pptActWindow = app.SlideShowWindows[1]; + pptActWindow = PPTApplication.SlideShowWindows[1]; } } } @@ -2332,6 +2322,9 @@ namespace Ink_Canvas.Helpers [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll")] + private static extern int GetWindowTextLength(IntPtr hWnd); + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool IsWindowVisible(IntPtr hWnd); @@ -2634,6 +2627,185 @@ namespace Ink_Canvas.Helpers } #endregion + #region Window Handle Methods + /// + /// 获取PPT窗口句柄 + /// + /// 窗口句柄,如果获取失败返回 IntPtr.Zero + public IntPtr GetPptHwnd() + { + IntPtr ret = IntPtr.Zero; + + // 方法1: 尝试从 SlideShowWindow 获取 + ret = GetPptHwndFromSlideShowWindow(_pptSlideShowWindow); + + if (ret == IntPtr.Zero) + { + // 方法2: 通过窗口标题匹配获取(备用方法) + try + { + if (_pptActivePresentation != null && PPTApplication != null) + { + dynamic pres = _pptActivePresentation; + string fullName = pres.FullName; + string appName = PPTApplication.Name; + ret = GetPptHwndWin32(fullName, appName); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取PPT窗口句柄失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + + return ret; + } + + /// + /// 从 SlideShowWindow 对象获取窗口句柄 + /// + private IntPtr GetPptHwndFromSlideShowWindow(object pptSlideShowWindowObj) + { + IntPtr hwnd = IntPtr.Zero; + if (pptSlideShowWindowObj == null) return IntPtr.Zero; + + try + { + // 尝试强类型转换 + Microsoft.Office.Interop.PowerPoint.SlideShowWindow slideWindow = + pptSlideShowWindowObj as Microsoft.Office.Interop.PowerPoint.SlideShowWindow; + + if (slideWindow != null) + { + int hwndVal = slideWindow.HWND; + hwnd = new IntPtr(hwndVal); + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄成功: {hwnd}", LogHelper.LogType.Trace); + } + else + { + // 如果强类型转换失败,尝试使用dynamic + try + { + dynamic ssw = pptSlideShowWindowObj; + int hwndVal = ssw.HWND; + hwnd = new IntPtr(hwndVal); + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄成功(dynamic): {hwnd}", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄失败: {ex.Message}", LogHelper.LogType.Trace); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从SlideShowWindow获取窗口句柄异常: {ex.Message}", LogHelper.LogType.Trace); + } + + return hwnd; + } + + /// + /// 通过窗口标题匹配获取PPT窗口句柄(备用方法) + /// + private IntPtr GetPptHwndWin32(string presFullName, string appName) + { + try + { + // 步骤 A: 基础参数校验 + if (string.IsNullOrWhiteSpace(presFullName) || string.IsNullOrWhiteSpace(appName)) + { + return IntPtr.Zero; + } + + // 步骤 B: 提取关键信息 (应用类型 & 文件名) + string targetAppKeyword; + if (appName.IndexOf("WPS", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "WPS"; + } + else if (appName.IndexOf("PowerPoint", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "PowerPoint"; + } + else + { + // 既不是 WPS 也不是 PowerPoint,视为不支持 + return IntPtr.Zero; + } + + // 从路径中安全提取文件名(包含扩展名),如 "myppt.pptx" + string targetFileName = System.IO.Path.GetFileName(presFullName); + if (string.IsNullOrWhiteSpace(targetFileName)) + { + return IntPtr.Zero; + } + + // 步骤 C: 枚举窗口并查找匹配项 + List candidates = new List(); + + // 调用 EnumWindows,使用 Lambda 表达式直接嵌入回调逻辑 + EnumWindows((hWnd, lParam) => + { + try + { + // [安全过滤] 1. 忽略不可见窗口 + if (!IsWindowVisible(hWnd)) return true; + + // [安全获取] 2. 获取窗口标题长度 + int length = GetWindowTextLength(hWnd); + if (length == 0) return true; + + // [安全获取] 3. 获取窗口标题文本 + StringBuilder sb = new StringBuilder(length + 1); + GetWindowText(hWnd, sb, sb.Capacity); + string title = sb.ToString(); + + if (string.IsNullOrWhiteSpace(title)) return true; + + // [核心匹配] 4. 判断标题是否同时包含 "文件名" 和 "应用关键字" + bool hasFileName = title.IndexOf(targetFileName, StringComparison.OrdinalIgnoreCase) >= 0; + bool hasAppKey = title.IndexOf(targetAppKeyword, StringComparison.OrdinalIgnoreCase) >= 0; + + if (hasFileName && hasAppKey) + { + candidates.Add(hWnd); + } + + // 继续枚举其他窗口 + return true; + } + catch + { + // 回调内部容错,忽略单个窗口获取信息的错误,继续枚举 + return true; + } + }, IntPtr.Zero); + + // 步骤 D: 结果判定 + // 只有当匹配到的窗口数量 唯一 (Count == 1) 时才返回句柄 + // 0 个表示没找到,>1 个表示有歧义(无法确定是哪一个),均视为失败 + if (candidates.Count == 1) + { + LogHelper.WriteLogToFile($"通过窗口标题匹配获取窗口句柄成功: {candidates[0]}", LogHelper.LogType.Trace); + return candidates[0]; + } + else if (candidates.Count > 1) + { + LogHelper.WriteLogToFile($"通过窗口标题匹配找到多个候选窗口({candidates.Count}个),无法确定唯一窗口", LogHelper.LogType.Trace); + } + + return IntPtr.Zero; + } + catch (Exception ex) + { + // 发生任何不可预知的异常(如Path解析错误等),返回安全值 + LogHelper.WriteLogToFile($"GetPptHwndWin32异常: {ex.Message}", LogHelper.LogType.Trace); + return IntPtr.Zero; + } + } + #endregion + #region Dispose public void Dispose() {