From c76021194ac5893e17690b2bd445f92d93a270ac Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 24 Feb 2026 12:21:46 +0800
Subject: [PATCH] =?UTF-8?q?improve:ROT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 19 +-
Ink Canvas/Helpers/ROTPPTManager.cs | 3920 +++++++++++++++++++++------
Ink Canvas/MainWindow_cs/MW_PPT.cs | 95 +-
3 files changed, 3214 insertions(+), 820 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 6579551f..68e47cb9 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -459,6 +459,12 @@ namespace Ink_Canvas.Helpers
try
{
Thread.Sleep(2000);
+
+ if (_disposed)
+ {
+ _isModuleUnloading = false;
+ return;
+ }
GC.Collect();
GC.WaitForPendingFinalizers();
@@ -467,7 +473,18 @@ namespace Ink_Canvas.Helpers
Thread.Sleep(1000);
_isModuleUnloading = false;
- _unifiedPptTimer?.Start();
+
+ try
+ {
+ if (!_disposed)
+ {
+ _unifiedPptTimer?.Start();
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ LogHelper.WriteLogToFile("PPT联动模块重载时计时器已释放,跳过重启", LogHelper.LogType.Trace);
+ }
LogHelper.WriteLogToFile("PPT联动模块已重新加载", LogHelper.LogType.Trace);
}
diff --git a/Ink Canvas/Helpers/ROTPPTManager.cs b/Ink Canvas/Helpers/ROTPPTManager.cs
index cd4a9f7e..96aa1ba6 100644
--- a/Ink Canvas/Helpers/ROTPPTManager.cs
+++ b/Ink Canvas/Helpers/ROTPPTManager.cs
@@ -1,6 +1,11 @@
using Microsoft.Office.Interop.PowerPoint;
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
using System.Runtime.InteropServices;
+using System.Text;
using System.Threading;
using System.Timers;
using System.Windows.Threading;
@@ -9,9 +14,6 @@ using Timer = System.Timers.Timer;
namespace Ink_Canvas.Helpers
{
- ///
- /// 基于 ROT 的 PPT 联动管理器实现。
- ///
public class ROTPPTManager : IPPTLinkManager
{
#region Events
@@ -25,36 +27,33 @@ namespace Ink_Canvas.Helpers
#endregion
#region Properties
- ///
- /// 通过 ROT 获取到的 PowerPoint.Application 实例。
- ///
- public object PPTApplication => _pptApplication;
-
+ public dynamic PPTApplication { get; private set; }
+ public dynamic CurrentPresentation { get; private set; }
+ public dynamic CurrentSlides { get; private set; }
+ public dynamic 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;
+ if (PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
- try
+ // 尝试访问一个简单的属性来验证连接是否有效
+ var _ = PPTApplication.Name;
+ return true;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ // 如果COM对象已失效,返回false
+ if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5)
{
- dynamic app = _pptApplication;
- var _ = app.Name;
- return true;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- return true;
- }
- if (hr == 0x800706B5) return false;
return false;
}
+ return false;
}
catch
{
@@ -62,7 +61,6 @@ namespace Ink_Canvas.Helpers
}
}
}
-
public bool IsInSlideShow
{
get
@@ -72,12 +70,11 @@ namespace Ink_Canvas.Helpers
object view = null;
try
{
- if (_pptApplication == null || !Marshal.IsComObject(_pptApplication)) return false;
+ 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;
if (ssw.Count == 0) return false;
@@ -89,7 +86,7 @@ namespace Ink_Canvas.Helpers
dynamic sswObj = slideShowWindow;
view = sswObj.View;
if (view == null) return false;
-
+
return true;
}
catch (COMException comEx)
@@ -97,6 +94,7 @@ namespace Ink_Canvas.Helpers
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005)
{
+ DisconnectFromPPT();
}
return false;
}
@@ -106,16 +104,14 @@ namespace Ink_Canvas.Helpers
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005)
{
+ DisconnectFromPPT();
}
- else
- {
- LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
- }
+ LogHelper.WriteLogToFile($"检查PPT放映状态失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
return false;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态时发生意外错误: {ex}", LogHelper.LogType.Warning);
+ LogHelper.WriteLogToFile($"检查PPT放映状态时发生意外错误: {ex}", LogHelper.LogType.Warning);
return false;
}
finally
@@ -126,211 +122,660 @@ namespace Ink_Canvas.Helpers
}
}
}
-
public bool IsSupportWPS { get; set; } = false;
public bool SkipAnimationsWhenNavigating { get; set; } = false;
-
- ///
- /// 当前演示文稿的总页数(每次按需计算,不缓存 COM 对象)。
- ///
- public int SlidesCount
- {
- get
- {
- object pres = null;
- object slides = null;
- try
- {
- pres = GetCurrentActivePresentation();
- if (pres == null) return 0;
-
- dynamic dp = pres;
- slides = dp.Slides;
- if (slides == null) return 0;
-
- dynamic ds = slides;
- return (int)ds.Count;
- }
- catch
- {
- return 0;
- }
- finally
- {
- SafeReleaseComObject(slides);
- SafeReleaseComObject(pres);
- }
- }
- }
#endregion
#region Private Fields
- // 唯一持久化的 COM 对象字段:PPT 应用程序实例
- private object _pptApplication;
-
- private Timer _unifiedRotTimer;
- private int _rotTickCount;
-
- private bool _isModuleUnloading;
+ private Thread _monitoringThread;
+ private bool _shouldStop = false;
+ private readonly object _monitoringLock = new object();
+ private Timer _wpsProcessCheckTimer;
+ private Process _wpsProcess;
+ private bool _isModuleUnloading = false;
+ private bool _hasWpsProcessId;
+ private DateTime _wpsProcessRecordTime = DateTime.MinValue;
+ private int _wpsProcessCheckCount;
+ private WpsWindowInfo _lastForegroundWpsWindow;
+ private DateTime _lastWindowCheckTime = DateTime.MinValue;
private bool _lastSlideShowState;
private readonly object _lockObject = new object();
private bool _disposed;
+
+ private dynamic _pptActivePresentation;
+ private dynamic _pptSlideShowWindow;
+ private int _polling = 0;
+ private bool _forcePolling = true;
+ private bool _bindingEvents = false;
+ private DateTime _updateTime;
+ private int _lastPolledSlideNumber = -1;
#endregion
- #region Lifecycle
+ #region Constructor & Initialization
public ROTPPTManager()
{
- InitializeTimers();
}
- private void InitializeTimers()
- {
- _unifiedRotTimer = new Timer(500);
- _unifiedRotTimer.Elapsed += OnUnifiedRotTimerElapsed;
- _unifiedRotTimer.AutoReset = true;
- }
-
- private void OnUnifiedRotTimerElapsed(object sender, ElapsedEventArgs e)
- {
- var tick = Interlocked.Increment(ref _rotTickCount);
-
- OnConnectionCheckTimerElapsed(sender, e);
-
- if (tick % 2 == 0)
- OnSlideShowStateCheckTimerElapsed(sender, e);
- }
-
- ///
- /// 启动内部轮询计时器以开始监测 ROT/PPT 连接状态和幻灯片放映状态。
- ///
- ///
- /// 如果实例已被释放(Dispose 已调用)或计时器未初始化,则此方法不会执行任何操作。
- ///
public void StartMonitoring()
{
if (_disposed) return;
- _unifiedRotTimer?.Start();
+ lock (_monitoringLock)
+ {
+ if (_monitoringThread != null && _monitoringThread.IsAlive)
+ {
+ return;
+ }
+
+ _shouldStop = false;
+ _monitoringThread = new Thread(PptComService)
+ {
+ IsBackground = true,
+ Name = "PPTMonitoringThread"
+ };
+ _monitoringThread.Start();
+ LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace);
+ }
}
- ///
- /// 停止内部的 ROT 监视计时器并断开与当前 PowerPoint 实例的连接。
- ///
public void StopMonitoring()
{
- _unifiedRotTimer?.Stop();
- DisconnectFromPPT(restartMonitoring: false);
+ 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);
+ }
}
- ///
- /// 强制断开当前与 PowerPoint 的 ROT 连接并触发后续的重连流程。
- ///
public void ReloadConnection()
{
if (_disposed) return;
+
LogHelper.WriteLogToFile("[ROT] 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
- DisconnectFromPPT(restartMonitoring: true);
+
+ lock (_monitoringLock)
+ {
+ _shouldStop = true;
+
+ if (_monitoringThread != null && _monitoringThread.IsAlive)
+ {
+ if (!_monitoringThread.Join(2000))
+ {
+ LogHelper.WriteLogToFile("等待监控线程退出超时(热重载)", LogHelper.LogType.Warning);
+ }
+ }
+
+ DisconnectFromPPT();
+ _monitoringThread = null;
+ _shouldStop = false;
+ _isModuleUnloading = false;
+ }
+
+ StartMonitoring();
}
#endregion
#region Connection Management
- ///
- /// 在计时器触发时检查与 PowerPoint 的 ROT 连接,并在尚未处于已释放或模块卸载期间时尝试通过 ROT 建立连接。
- ///
- ///
- /// 如果在检查或连接过程中发生异常,异常信息会记录到日志并被吞掉,不会向上抛出。
- ///
- private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e)
+ private void PptComService()
{
+ LogHelper.WriteLogToFile("PPT Monitor ReStarted", LogHelper.LogType.Trace);
+
+ _bindingEvents = false;
+ _lastPolledSlideNumber = -1;
+ _polling = 0;
+
+ int tempTotalPage = -1;
+
try
{
- if (_disposed || _isModuleUnloading)
- return;
- CheckAndConnectToPPTViaRot();
+ while (!_shouldStop && !_isModuleUnloading)
+ {
+ object bestApp = PPTROTConnectionHelper.GetAnyActivePowerPoint(PPTApplication, out int bestPriority, out int targetPriority);
+ bool needRebind = false;
+
+ if (bestApp == null)
+ {
+ var appFallback = TryConnectToPowerPoint();
+ if (appFallback != null)
+ {
+ bestApp = appFallback;
+ bestPriority = 1;
+ }
+ }
+
+ if (PPTApplication == null && bestApp != null)
+ {
+ needRebind = true;
+ }
+ else if (PPTApplication != null && bestApp != null && bestPriority > targetPriority)
+ {
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp))
+ {
+ needRebind = true;
+ }
+ }
+
+ if (needRebind)
+ {
+ bool wait = (PPTApplication != null);
+ DisconnectFromPPT();
+
+ if (bestApp != null)
+ {
+ if (wait) Thread.Sleep(1000);
+
+ PPTApplication = bestApp;
+
+ try
+ {
+ _pptActivePresentation = PPTApplication.ActivePresentation;
+ _updateTime = DateTime.Now;
+
+ try
+ {
+ _pptSlideShowWindow = _pptActivePresentation.SlideShowWindow;
+ tempTotalPage = GetTotalSlideIndex(_pptActivePresentation);
+ }
+ catch
+ {
+ tempTotalPage = -1;
+ }
+
+ if (tempTotalPage == -1)
+ {
+ _lastPolledSlideNumber = -1;
+ _polling = 0;
+ }
+ else
+ {
+ try
+ {
+ _lastPolledSlideNumber = GetCurrentSlideIndex(_pptSlideShowWindow);
+
+ if (GetCurrentSlideIndex(_pptSlideShowWindow) >= GetTotalSlideIndex(_pptActivePresentation)) _polling = 1;
+ else _polling = 0;
+ }
+ catch
+ {
+ _lastPolledSlideNumber = -1;
+ _polling = 1;
+ }
+ }
+
+ ConnectToPPT(null);
+
+ try
+ {
+ dynamic pptAppDynamic = PPTApplication;
+ LogHelper.WriteLogToFile($"成功绑定! {pptAppDynamic.Name}", LogHelper.LogType.Trace);
+ }
+ catch
+ {
+ LogHelper.WriteLogToFile("成功绑定!", LogHelper.LogType.Trace);
+ }
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] PPT 连接检查失败: {ex}", LogHelper.LogType.Error);
- }
- }
+ LogHelper.WriteLogToFile($"绑定失败: {ex.Message}", LogHelper.LogType.Warning);
+ DisconnectFromPPT();
+ }
+ }
+ }
+ else
+ {
+ if (bestApp != null && (PPTApplication == null || !PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp)))
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(bestApp);
+ bestApp = null;
+ }
+ else if (bestApp == null && PPTApplication != null)
+ {
+ LogHelper.WriteLogToFile("检测到PPT已关闭,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ }
+ }
- ///
- /// 在定时器触发时检查并在必要时更新 PPT 的放映状态。
- ///
- ///
- /// 当实例未被释放、模块未在卸载过程中且与 PowerPoint 仍保持连接时会调用内部的放映状态检查逻辑。若检查过程中发生异常,会将错误写入日志并吞并异常以保证定时器循环继续运行。
- ///
- private void OnSlideShowStateCheckTimerElapsed(object sender, ElapsedEventArgs e)
- {
- try
- {
- if (_disposed || _isModuleUnloading || !IsConnected)
- return;
- CheckSlideShowState();
+ if (PPTApplication != null && _pptActivePresentation != null)
+ {
+ dynamic activePresentation = null;
+ dynamic slideShowWindow = null;
+
+ try
+ {
+ // 检查COM对象是否仍然有效
+ if (!System.Runtime.InteropServices.Marshal.IsComObject(PPTApplication))
+ {
+ DisconnectFromPPT();
+ continue;
+ }
+
+ try
+ {
+ var _ = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(PPTApplication);
+ System.Runtime.InteropServices.Marshal.Release(_);
+ }
+ catch (System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ // COM对象已失效
+ DisconnectFromPPT();
+ continue;
+ }
+
+ activePresentation = PPTApplication.ActivePresentation;
+
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))
+ {
+ LogHelper.WriteLogToFile("检测到演示文稿切换,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ continue;
+ }
+ }
+ catch (System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ // COM对象已失效
+ LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ continue;
+ }
+ catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A)
+ {
+ LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace);
+ }
+ catch (COMException comEx)
+ {
+ // COM异常,对象可能已失效
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile($"检查演示文稿状态COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
+ DisconnectFromPPT();
+ continue;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] PPT 放映状态检查失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"检查演示文稿状态失败: {ex.Message}", LogHelper.LogType.Warning);
+ DisconnectFromPPT();
+ continue;
+ }
+ finally
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(activePresentation);
+ activePresentation = null;
+ }
+
+ bool isSlideShowActive = false;
+ try
+ {
+ activePresentation = PPTApplication.ActivePresentation;
+
+ dynamic slideShowWindows = PPTApplication.SlideShowWindows;
+ int count = 0;
+ if (slideShowWindows != null)
+ {
+ count = slideShowWindows.Count;
+ }
+
+ if (activePresentation != null && count > 0)
+ {
+ isSlideShowActive = true;
+
+ dynamic activeSlideShowWindow = null;
+
+ try
+ {
+ for (int i = 1; i <= count; i++)
+ {
+ try
+ {
+ dynamic ssw = slideShowWindows[i];
+ if (PPTROTConnectionHelper.IsSlideShowWindowActive(ssw))
+ {
+ activeSlideShowWindow = ssw;
+ break;
+ }
+ }
+ catch { }
+ }
+ }
+ catch { }
+
+ if (activeSlideShowWindow == null)
+ {
+ try
+ {
+ activeSlideShowWindow = activePresentation.SlideShowWindow;
+ }
+ catch { }
+ }
+
+ if (activeSlideShowWindow != null)
+ {
+ slideShowWindow = activeSlideShowWindow;
+ if (_pptSlideShowWindow == null || !PPTROTConnectionHelper.IsValidSlideShowWindow(_pptSlideShowWindow))
+ {
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow))
+ {
+ bool isNewWindow = _pptSlideShowWindow == null;
+ PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow);
+ _pptSlideShowWindow = slideShowWindow;
+
+ if (isNewWindow)
+ {
+ try
+ {
+ OnSlideShowBegin(_pptSlideShowWindow);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"手动触发SlideShowBegin失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ }
+ }
+
+ PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindows);
+ }
+ else
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindows);
+ }
+ }
+ catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A)
+ {
+ LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"发现窗口失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ finally
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(activePresentation);
+ activePresentation = null;
+
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow))
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindow);
+ slideShowWindow = null;
+ }
+ }
+
+ if (isSlideShowActive)
+ {
+ if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling)
+ {
+
+ try
+ {
+ slideShowWindow = _pptActivePresentation.SlideShowWindow;
+
+ if (slideShowWindow != null)
+ {
+ tempTotalPage = GetTotalSlideIndex(_pptActivePresentation);
+ }
+ else
+ {
+ tempTotalPage = -1;
+ }
+ }
+ catch (Exception ex)
+ {
+ tempTotalPage = -1;
+ LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ finally
+ {
+ PPTROTConnectionHelper.SafeReleaseComObject(slideShowWindow);
+ slideShowWindow = null;
+ }
+
+ if (tempTotalPage == -1)
+ {
+ _lastPolledSlideNumber = -1;
+ _polling = 0;
+ }
+ else
+ {
+ try
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+ int lastPage = _lastPolledSlideNumber;
+
+ if (currentPage >= GetTotalSlideIndex(_pptActivePresentation)) _polling = 1;
+ else _polling = 0;
+
+ if (lastPage != -1 && currentPage != lastPage)
+ {
+ try
+ {
+ SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ _lastPolledSlideNumber = currentPage;
+ }
+ catch (Exception ex)
+ {
+ _lastPolledSlideNumber = -1;
+ _polling = 1;
+ LogHelper.WriteLogToFile($"获取当前页数失败: {ex}", LogHelper.LogType.Warning);
+ }
+ }
+
+ _updateTime = DateTime.Now;
+ }
+
+ if (_polling != 0)
+ {
+ try
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+
+ if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
+ {
+ try
+ {
+ SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ _lastPolledSlideNumber = currentPage;
+ UpdateCurrentPresentationInfo();
+ _polling = 2;
+ }
+ catch
+ {
+ _lastPolledSlideNumber = -1;
+ }
+ }
+ }
+ else
+ {
+ if (_lastSlideShowState)
+ {
+ LogHelper.WriteLogToFile("轮询检测到放映已结束", LogHelper.LogType.Trace);
+
+ PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow);
+ _pptSlideShowWindow = null;
+ _lastPolledSlideNumber = -1;
+ _polling = 1;
+ SlidesCount = 0;
+
+ _lastSlideShowState = false;
+ SlideShowStateChanged?.Invoke(false);
+
+ if (_pptActivePresentation != null)
+ {
+ try
+ {
+ OnSlideShowEnd(_pptActivePresentation);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发SlideShowEnd事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ else
+ {
+ _lastPolledSlideNumber = -1;
+ SlidesCount = 0;
+ }
+ }
+ }
+ else
+ {
+ _lastPolledSlideNumber = -1;
+ SlidesCount = 0;
+ }
+
+ if (_shouldStop || _isModuleUnloading)
+ {
+ LogHelper.WriteLogToFile("收到停止信号,退出循环", LogHelper.LogType.Trace);
+ break;
+ }
+
+ Thread.Sleep(500);
+ }
+ }
+ catch (InvalidComObjectException ex)
+ {
+ LogHelper.WriteLogToFile($"PptComService异常: COM对象已失效 - {ex.Message}", LogHelper.LogType.Error);
+ DisconnectFromPPT();
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile($"PptComService异常: COM异常 (HR: 0x{hr:X8}) - {comEx.Message}", LogHelper.LogType.Error);
+ DisconnectFromPPT();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PptComService异常: {ex.Message}", LogHelper.LogType.Error);
+ DisconnectFromPPT();
}
}
- ///
- /// 使用 ROT 检查并在必要时建立、切换或断开与 PowerPoint 的 COM 连接。
- ///
- ///
- /// 当管理器已释放或模块正在卸载时不执行任何操作。此方法在内部加锁以防止并发切换;会通过 ROT 获取当前可用的 PowerPoint 实例并:
- /// - 若尚未连接则建立连接;
- /// - 若当前无可用实例则断开现有连接;
- /// - 若检测到已连接的实例发生变化则先断开再重新连接。
- /// 遇到异常时会记录错误日志,并在存在活动连接时尝试断开以保证状态一致性。
- ///
- private void CheckAndConnectToPPTViaRot()
+ private void CheckAndConnectToPPT()
{
- if (_disposed || _isModuleUnloading) return;
-
+ if (_isModuleUnloading) return;
+
lock (_lockObject)
{
try
{
if (_isModuleUnloading) return;
+
+ object bestApp = PPTROTConnectionHelper.GetAnyActivePowerPoint(PPTApplication, out int bestPriority, out int targetPriority);
+ bool needRebind = false;
- if (_pptApplication != null && !IsConnected)
- {
- DisconnectFromPPT();
- return;
- }
+ LogHelper.WriteLogToFile($"ROT扫描结果: now={targetPriority}, best={bestPriority}, bestApp={(bestApp != null ? "found" : "null")}", LogHelper.LogType.Trace);
- // 使用 ROT 获取当前最佳 PPT 实例
- var bestApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS);
-
- if (bestApp != null && _pptApplication == null)
+ if (PPTApplication == null && bestApp != null)
{
- // 从未连接 -> 连接
- ConnectToPPT(bestApp);
+ needRebind = true;
}
- else if (bestApp == null && _pptApplication != null)
+ else if (PPTApplication != null && bestApp != null && bestPriority > targetPriority)
{
- // 原来有,现在没有 -> 断开
- DisconnectFromPPT();
- }
- else if (bestApp != null && _pptApplication != null)
- {
- // 已连接,检查是否切换到了另一份 PPT
- if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptApplication, bestApp))
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp))
{
- DisconnectFromPPT();
- ConnectToPPT(bestApp);
+ needRebind = true;
}
- else
+ }
+
+ if (needRebind)
+ {
+ LogHelper.WriteLogToFile($"需要重新绑定: bestPriority={bestPriority}, targetPriority={targetPriority}", LogHelper.LogType.Trace);
+
+ bool wait = (PPTApplication != null);
+ DisconnectFromPPT();
+
+ if (bestApp != null)
+ {
+ if (wait) Thread.Sleep(1000);
+
+ try
+ {
+ LogHelper.WriteLogToFile("使用dynamic类型连接", LogHelper.LogType.Trace);
+ PPTApplication = bestApp;
+ ConnectToPPT(null);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"连接失败: {ex.Message}", LogHelper.LogType.Warning);
+ PPTROTConnectionHelper.SafeReleaseComObject(bestApp);
+ }
+ }
+ }
+ else
+ {
+ if (bestApp != null && (PPTApplication == null || !PPTROTConnectionHelper.AreComObjectsEqual(PPTApplication, bestApp)))
{
PPTROTConnectionHelper.SafeReleaseComObject(bestApp);
+ bestApp = null;
}
}
+
+ if (PPTApplication != null)
+ {
+ // 即使 _pptActivePresentation 为 null,也要检查(可能在轮询模式下需要初始化)
+ if (_pptActivePresentation == null)
+ {
+ try
+ {
+ _pptActivePresentation = PPTApplication.ActivePresentation;
+ if (_pptActivePresentation != null)
+ {
+ LogHelper.WriteLogToFile("轮询模式:初始化_pptActivePresentation", LogHelper.LogType.Trace);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"轮询模式:无法获取ActivePresentation: {ex.Message}", LogHelper.LogType.Trace);
+ }
+ }
+
+ if (_pptActivePresentation != null)
+ {
+ CheckPresentationAndSlideShowState();
+ }
+ }
+ }
+ catch (InvalidComObjectException)
+ {
+ // COM对象已失效,断开连接
+ LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ }
+ catch (COMException comEx)
+ {
+ // COM异常,记录并断开连接
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile($"PPT连接检查COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
+ DisconnectFromPPT();
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] PPT 连接检查异常: {ex}", LogHelper.LogType.Error);
- if (_pptApplication != null)
+ LogHelper.WriteLogToFile($"PptComService异常: {ex.Message}", LogHelper.LogType.Error);
+ if (PPTApplication != null)
{
DisconnectFromPPT();
}
@@ -338,6 +783,290 @@ namespace Ink_Canvas.Helpers
}
}
+ private void CheckPresentationAndSlideShowState()
+ {
+ try
+ {
+ // 检查COM对象是否仍然有效
+ if (PPTApplication == null || !System.Runtime.InteropServices.Marshal.IsComObject(PPTApplication))
+ {
+ return;
+ }
+
+ try
+ {
+ var _ = System.Runtime.InteropServices.Marshal.GetIUnknownForObject(PPTApplication);
+ System.Runtime.InteropServices.Marshal.Release(_);
+ }
+ catch (System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ // COM对象已失效
+ DisconnectFromPPT();
+ return;
+ }
+
+ dynamic activePresentation = null;
+ dynamic slideShowWindow = null;
+
+ try
+ {
+ activePresentation = PPTApplication.ActivePresentation;
+
+ if (activePresentation != null && _pptActivePresentation != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))
+ {
+ LogHelper.WriteLogToFile("检测到演示文稿切换,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ return;
+ }
+ }
+ catch (System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ // COM对象已失效
+ LogHelper.WriteLogToFile("检测到COM对象失效,断开连接", LogHelper.LogType.Trace);
+ DisconnectFromPPT();
+ return;
+ }
+ catch (COMException ex) when ((uint)ex.HResult == 0x8001010A)
+ {
+ LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace);
+ return;
+ }
+ catch (COMException comEx)
+ {
+ // COM异常,对象可能已失效
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile($"检查演示文稿状态COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
+ DisconnectFromPPT();
+ return;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查演示文稿状态失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning);
+ activePresentation = null;
+ }
+ finally
+ {
+ if (activePresentation != null && (_pptActivePresentation == null || !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation)))
+ {
+ SafeReleaseComObject(activePresentation);
+ }
+ }
+
+ bool isSlideShowActive = false;
+ try
+ {
+ if (activePresentation == null)
+ {
+ try
+ {
+ activePresentation = PPTApplication.ActivePresentation;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"访问ActivePresentation失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning);
+ }
+ }
+
+ if (activePresentation != null)
+ {
+ try
+ {
+ dynamic slideShowWindows = PPTApplication.SlideShowWindows;
+ if (slideShowWindows != null && slideShowWindows.Count > 0)
+ {
+ isSlideShowActive = true;
+ LogHelper.WriteLogToFile($"检测到放映模式,轮询模式={_forcePolling}", LogHelper.LogType.Trace);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查SlideShowWindows失败: {ex.Message}", LogHelper.LogType.Trace);
+ }
+ }
+
+ if (isSlideShowActive && activePresentation != null)
+ {
+ dynamic pres = activePresentation;
+ slideShowWindow = pres.SlideShowWindow;
+ if (_pptSlideShowWindow == null || !PPTROTConnectionHelper.IsValidSlideShowWindow(_pptSlideShowWindow))
+ {
+ if (!PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow))
+ {
+ SafeReleaseComObject(_pptSlideShowWindow);
+ _pptSlideShowWindow = slideShowWindow;
+ LogHelper.WriteLogToFile("发现窗口,成功设置 slideshowwindow", LogHelper.LogType.Trace);
+ }
+ }
+ }
+ }
+ catch (COMException ex) when ((uint)ex.HResult == 0x8001010A)
+ {
+ LogHelper.WriteLogToFile("PowerPoint 忙,稍后重试", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"发现窗口失败: {ex}", LogHelper.LogType.Warning);
+ }
+ finally
+ {
+ if (activePresentation != null && (_pptActivePresentation == null || !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation)))
+ {
+ SafeReleaseComObject(activePresentation);
+ }
+ if (slideShowWindow != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptSlideShowWindow, slideShowWindow))
+ {
+ SafeReleaseComObject(slideShowWindow);
+ }
+ }
+
+ if (isSlideShowActive)
+ {
+ if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling)
+ {
+ LogHelper.WriteLogToFile($"轮询", LogHelper.LogType.Trace);
+
+ try
+ {
+ dynamic pres = _pptActivePresentation;
+ if (pres == null)
+ {
+ LogHelper.WriteLogToFile("_pptActivePresentation为null,无法轮询", LogHelper.LogType.Warning);
+ }
+ else
+ {
+ slideShowWindow = pres.SlideShowWindow;
+
+ int tempTotalPage = -1;
+ if (slideShowWindow != null)
+ {
+ tempTotalPage = GetTotalSlideIndex(_pptActivePresentation);
+ }
+
+ if (tempTotalPage == -1)
+ {
+ SlidesCount = 0;
+ _polling = 0;
+ }
+ else
+ {
+ try
+ {
+ if (_pptSlideShowWindow == null)
+ {
+ LogHelper.WriteLogToFile("轮询: _pptSlideShowWindow为null", LogHelper.LogType.Warning);
+ }
+ else
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+ SlidesCount = tempTotalPage;
+ if (currentPage >= tempTotalPage) _polling = 1;
+ else _polling = 0;
+
+ if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
+ {
+ try
+ {
+ LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace);
+ SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ _lastPolledSlideNumber = currentPage;
+ }
+ }
+ catch (Exception ex)
+ {
+ SlidesCount = 0;
+ _polling = 1;
+ LogHelper.WriteLogToFile($"获取当前页数失败: {ex}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ SlidesCount = 0;
+ LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ finally
+ {
+ SafeReleaseComObject(slideShowWindow);
+ }
+
+ _updateTime = DateTime.Now;
+ }
+
+ if (_polling != 0)
+ {
+ try
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+
+ if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
+ {
+ try
+ {
+ LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace);
+ SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ _lastPolledSlideNumber = currentPage;
+ UpdateCurrentPresentationInfo();
+ _polling = 2;
+ }
+ catch
+ {
+ }
+ }
+ }
+ else
+ {
+ if (_lastSlideShowState)
+ {
+ LogHelper.WriteLogToFile("轮询检测到放映已结束", LogHelper.LogType.Trace);
+
+ PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow);
+ _pptSlideShowWindow = null;
+ _lastPolledSlideNumber = -1;
+ _polling = 1;
+ SlidesCount = 0;
+
+ _lastSlideShowState = false;
+ SlideShowStateChanged?.Invoke(false);
+
+ if (_pptActivePresentation != null)
+ {
+ try
+ {
+ OnSlideShowEnd(_pptActivePresentation);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发SlideShowEnd事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ else
+ {
+ SlidesCount = 0;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查演示文稿和放映状态失败: {ex}", LogHelper.LogType.Warning);
+ }
+ }
+
private void CheckSlideShowState()
{
try
@@ -347,304 +1076,890 @@ namespace Ink_Canvas.Helpers
var currentSlideShowState = IsInSlideShow;
if (currentSlideShowState != _lastSlideShowState)
{
- var wasInSlideShow = _lastSlideShowState;
_lastSlideShowState = currentSlideShowState;
SlideShowStateChanged?.Invoke(currentSlideShowState);
- if (currentSlideShowState && !wasInSlideShow)
- {
- try
- {
- var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application;
- if (app != null && app.SlideShowWindows.Count >= 1)
- {
- var wn = app.SlideShowWindows[1];
- if (wn != null)
- {
- OnSlideShowBeginInternal(wn);
- }
- }
- }
- catch (COMException)
- {
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 检查 PPT 放映状态异常: {ex}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 将指定的 PowerPoint COM 应用对象作为当前连接并在 UI 线程上注册必要的演示文稿与幻灯片放映事件,触发连接通知并在已处于放映时尝试触发一次放映开始回调。
- ///
- /// 要绑定的 PowerPoint COM 应用对象(通常来自 ROT)。
- ///
- /// 如果事件注册或后续处理失败,会记录错误并在异常路径上清除内部引用(将内部应用对象置空)。方法会触发 PPTConnectionChanged(true) 并记录连接成功的事件日志;若检测到当前正处于幻灯片放映,则尝试调用一次放映开始的内部处理以同步状态。异常信息会写入日志,但方法不会向外抛出未捕获的异常(内部会捕获并记录错误后清理状态)。
- ///
- private void ConnectToPPT(object appObj)
- {
- try
- {
- _pptApplication = appObj;
-
- // 在 UI 线程上注册事件(无打开文档时可能失败,不因此断开连接)
- Application.Current?.Dispatcher?.Invoke(() =>
- {
- try
- {
- var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application;
- if (app == null) return;
-
- app.PresentationOpen += OnPresentationOpenInternal;
- app.PresentationClose += OnPresentationCloseInternal;
- app.SlideShowBegin += OnSlideShowBeginInternal;
- app.SlideShowNextSlide += OnSlideShowNextSlideInternal;
- app.SlideShowEnd += OnSlideShowEndInternal;
- LogHelper.WriteLogToFile("[ROT] PPT 事件注册成功", LogHelper.LogType.Trace);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] PPT 事件注册失败(将依赖轮询检测放映状态): {ex}", LogHelper.LogType.Warning);
- }
- }, DispatcherPriority.Normal);
-
- PPTConnectionChanged?.Invoke(true);
- LogHelper.WriteLogToFile("[ROT] 成功连接到 PPT 应用程序", LogHelper.LogType.Event);
-
- // 如果已在放映中,根据当前状态触发一次开始事件
- if (IsInSlideShow)
- {
- try
- {
- var app = _pptApplication as Microsoft.Office.Interop.PowerPoint.Application;
- if (app != null && app.SlideShowWindows.Count > 0)
- {
- OnSlideShowBeginInternal(app.SlideShowWindows[1]);
- }
- }
- catch (COMException)
+ if (!currentSlideShowState)
{
}
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 连接 PPT 应用程序失败: {ex}", LogHelper.LogType.Error);
- _pptApplication = null;
+ LogHelper.WriteLogToFile($"检查PPT放映状态异常: {ex}", LogHelper.LogType.Error);
}
}
- private void DisconnectFromPPT(bool restartMonitoring = true)
+ private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint()
{
- object appToRelease = null;
try
{
- PPTConnectionChanged?.Invoke(false);
- LogHelper.WriteLogToFile("[ROT] 准备断开 PPT 连接,先卸载监控模块", LogHelper.LogType.Event);
+ var pptApp = PPTROTConnectionHelper.TryConnectViaROT(IsSupportWPS);
+ return pptApp;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"ROT连接PowerPoint异常: {ex}", LogHelper.LogType.Error);
+ return null;
+ }
+ }
- lock (_lockObject)
- {
- _isModuleUnloading = true;
- _unifiedRotTimer?.Stop();
- appToRelease = _pptApplication;
- _pptApplication = null;
+ private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPS()
+ {
+ try
+ {
+ var wpsApp = PPTROTConnectionHelper.TryConnectViaROT(true);
+ return wpsApp;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"ROT连接WPS异常: {ex}", LogHelper.LogType.Error);
+ return null;
+ }
+ }
+
+ private void ConnectToPPT(object pptApp)
+ {
+ try
+ {
+ if (pptApp != null)
+ {
+ PPTApplication = pptApp;
}
- if (appToRelease != null)
+ try
{
+ dynamic pptAppDynamic = PPTApplication;
try
{
- if (Marshal.IsComObject(appToRelease))
- {
- Application.Current?.Dispatcher?.Invoke(() =>
- {
- try
- {
- var app = appToRelease as Microsoft.Office.Interop.PowerPoint.Application;
- if (app != null)
- {
- app.PresentationOpen -= OnPresentationOpenInternal;
- app.PresentationClose -= OnPresentationCloseInternal;
- app.SlideShowBegin -= OnSlideShowBeginInternal;
- app.SlideShowNextSlide -= OnSlideShowNextSlideInternal;
- app.SlideShowEnd -= OnSlideShowEndInternal;
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 取消 PPT 事件注册异常: {ex}", LogHelper.LogType.Warning);
- }
- }, DispatcherPriority.Normal);
- }
+ _pptActivePresentation = pptAppDynamic.ActivePresentation;
+ _updateTime = DateTime.Now;
+ _lastPolledSlideNumber = -1;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 取消 PPT 事件注册失败: {ex}", LogHelper.LogType.Warning);
+ LogHelper.WriteLogToFile($"访问ActivePresentation失败: {ex.Message},继续使用轮询模式", LogHelper.LogType.Warning);
+ _pptActivePresentation = null;
+ _updateTime = DateTime.Now;
+ _lastPolledSlideNumber = -1;
}
- if (Marshal.IsComObject(appToRelease))
+ int tempTotalPage = -1;
+ if (_pptActivePresentation != null)
{
try
{
- Marshal.FinalReleaseComObject(appToRelease);
+ _pptSlideShowWindow = _pptActivePresentation.SlideShowWindow;
+ tempTotalPage = GetTotalSlideIndex(_pptActivePresentation);
}
catch
{
- try
- {
- int refCount = Marshal.ReleaseComObject(appToRelease);
- while (refCount > 0)
- {
- refCount = Marshal.ReleaseComObject(appToRelease);
- }
- }
- catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
+ tempTotalPage = -1;
}
}
- }
+ else
+ {
+ tempTotalPage = -1;
+ }
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
+ if (tempTotalPage == -1)
+ {
+ SlidesCount = 0;
+ _polling = 0;
+ }
+ else
+ {
+ try
+ {
+ if (_pptSlideShowWindow != null)
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+ SlidesCount = tempTotalPage;
+ if (currentPage >= tempTotalPage) _polling = 1;
+ else _polling = 0;
+ _lastPolledSlideNumber = currentPage;
+ }
+ else
+ {
+ SlidesCount = tempTotalPage;
+ _polling = 0;
+ _lastPolledSlideNumber = -1;
+ }
+ }
+ catch
+ {
+ SlidesCount = 0;
+ _polling = 1;
+ }
+ }
- LogHelper.WriteLogToFile("[ROT] 已断开 PPT 连接并尝试释放所有 COM 对象", LogHelper.LogType.Event);
-
- System.Threading.ThreadPool.QueueUserWorkItem(_ =>
- {
try
{
- System.Threading.Thread.Sleep(300);
-
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
-
- System.Threading.Thread.Sleep(200);
-
- lock (_lockObject)
+ if (PPTApplication != null)
{
- _isModuleUnloading = false;
- if (restartMonitoring && !_disposed)
- _unifiedRotTimer?.Start();
+ try
+ {
+ Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
+
+ if (pptAppForEvents != null)
+ {
+ pptAppForEvents.SlideShowNextSlide += new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
+ pptAppForEvents.SlideShowBegin += new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
+ pptAppForEvents.SlideShowEnd += new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent);
+
+ try
+ {
+ pptAppForEvents.PresentationBeforeClose += new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose);
+ }
+ catch
+ {
+ LogHelper.WriteLogToFile("无法注册PresentationBeforeClose事件", LogHelper.LogType.Warning);
+ }
+
+ _bindingEvents = true;
+
+ LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace);
+ }
+ else
+ {
+ _bindingEvents = false;
+ _forcePolling = true;
+ LogHelper.WriteLogToFile("无法转换为强类型Application,使用轮询模式", LogHelper.LogType.Trace);
+ }
+ }
+ catch (Exception ex)
+ {
+ _bindingEvents = false;
+ _forcePolling = true;
+ LogHelper.WriteLogToFile($"事件注册失败: {ex.Message},使用轮询模式", LogHelper.LogType.Trace);
+ }
+ }
+ else
+ {
+ _bindingEvents = false;
+ _forcePolling = true;
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 重新进入监控状态失败: {ex}", LogHelper.LogType.Error);
- lock (_lockObject)
+ _bindingEvents = false;
+ _forcePolling = true;
+ LogHelper.WriteLogToFile($"无法注册事件: {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ _bindingEvents = false;
+ _forcePolling = true;
+
+ if (_pptActivePresentation != null)
+ {
+ UpdateCurrentPresentationInfo();
+ }
+
+ PPTConnectionChanged?.Invoke(true);
+
+ try
+ {
+ dynamic pptAppDynamic2 = PPTApplication;
+ LogHelper.WriteLogToFile($"成功绑定! {pptAppDynamic2.Name}", LogHelper.LogType.Event);
+ }
+ catch
+ {
+ LogHelper.WriteLogToFile("成功绑定!", LogHelper.LogType.Event);
+ }
+
+ TryRaiseSlideShowBeginOnConnect();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"ConnectToPPT内部异常: {ex.Message}", LogHelper.LogType.Warning);
+ if (_pptActivePresentation == null && PPTApplication == null)
+ {
+ DisconnectFromPPT();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"连接PPT应用程序失败: {ex}", LogHelper.LogType.Error);
+ PPTApplication = null;
+ }
+ }
+
+ private void TryRaiseSlideShowBeginOnConnect()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null)
+ return;
+
+ if (!Marshal.IsComObject(PPTApplication))
+ return;
+
+ dynamic app = PPTApplication;
+ dynamic slideShowWindows = app.SlideShowWindows;
+ if (slideShowWindows == null || slideShowWindows.Count == 0)
+ return;
+
+ dynamic slideShowWindow = slideShowWindows[1];
+ if (slideShowWindow == null)
+ return;
+
+ SlideShowBegin?.Invoke(slideShowWindow);
+ LogHelper.WriteLogToFile("检测到放映已在进行中,热重载", LogHelper.LogType.Trace);
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile($"TryRaiseSlideShowBeginOnConnect COM 异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"TryRaiseSlideShowBeginOnConnect 异常: {ex}", LogHelper.LogType.Trace);
+ }
+ }
+
+ private void UnbindEvents()
+ {
+ try
+ {
+ if (_bindingEvents && PPTApplication != null)
+ {
+ try
+ {
+ Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
+
+ if (app != null)
{
- _isModuleUnloading = false;
+ app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
+ app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
+ app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent);
+ app.PresentationBeforeClose -= new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose);
}
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.Message}", LogHelper.LogType.Trace);
+ }
+
+ _bindingEvents = false;
+ }
+ }
+ catch { }
+ }
+
+ private void DisconnectFromPPT()
+ {
+ if (PPTApplication == null && _pptActivePresentation == null && _pptSlideShowWindow == null &&
+ CurrentPresentation == null && CurrentSlides == null && CurrentSlide == null)
+ {
+ return;
+ }
+
+ _isModuleUnloading = true;
+
+ try
+ {
+ UnbindEvents();
+
+ if (_pptActivePresentation != null)
+ {
+ try
+ {
+ PresentationClose?.Invoke(_pptActivePresentation);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"触发PresentationClose事件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ SafeReleaseComObject(_pptSlideShowWindow, "_pptSlideShowWindow");
+ SafeReleaseComObject(_pptActivePresentation, "_pptActivePresentation");
+ SafeReleaseComObject(CurrentSlide, "CurrentSlide");
+ SafeReleaseComObject(CurrentSlides, "CurrentSlides");
+ SafeReleaseComObject(CurrentPresentation, "CurrentPresentation");
+
+ if (PPTApplication != null)
+ {
+ try
+ {
+ if (Marshal.IsComObject(PPTApplication))
+ {
+ try
+ {
+ var unk = Marshal.GetIUnknownForObject(PPTApplication);
+ Marshal.Release(unk);
+
+ try
+ {
+ Marshal.FinalReleaseComObject(PPTApplication);
+ }
+ catch
+ {
+ try
+ {
+ int refCount = Marshal.ReleaseComObject(PPTApplication);
+ while (refCount > 0)
+ {
+ refCount = Marshal.ReleaseComObject(PPTApplication);
+ }
+ }
+ catch
+ {
+ }
+ }
+ }
+ catch (InvalidComObjectException)
+ {
+ LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace);
+ }
+ }
+ }
+ catch (InvalidComObjectException)
+ {
+ LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"释放PPTApplication时发生异常: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ PPTApplication = null;
+ _pptActivePresentation = null;
+ _pptSlideShowWindow = null;
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ _polling = 0;
+ _forcePolling = true;
+ _bindingEvents = false;
+
+ PPTConnectionChanged?.Invoke(false);
+
+ LogHelper.WriteLogToFile("已断开PPT连接,并显式释放所有COM对象", LogHelper.LogType.Event);
+
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ try
+ {
+ Thread.Sleep(2000);
+ _isModuleUnloading = false;
+
+ LogHelper.WriteLogToFile("PPT联动模块已允许重新连接", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"重新启用PPT联动模块失败: {ex}", LogHelper.LogType.Error);
+ _isModuleUnloading = false;
}
});
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 断开 PPT 连接失败: {ex}", LogHelper.LogType.Error);
- lock (_lockObject)
+ LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error);
+ _isModuleUnloading = false;
+ }
+ }
+
+ ///
+ /// 安全释放COM对象
+ ///
+ private void SafeReleaseComObject(object comObject)
+ {
+ try
+ {
+ if (comObject != null && Marshal.IsComObject(comObject))
{
- _isModuleUnloading = false;
+ Marshal.ReleaseComObject(comObject);
}
}
+ catch { }
}
- #endregion
- #region PPT Event Handlers (internal, strong-typed)
- private void OnPresentationOpenInternal(Presentation pres)
+ private void SafeReleaseComObject(object comObject, string objectName)
{
try
{
- PresentationOpen?.Invoke(pres);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error);
- }
- }
+ if (comObject != null)
+ {
+ // 检查是否为有效的COM对象
+ if (!Marshal.IsComObject(comObject))
+ {
+ return; // 不是COM对象,无需释放
+ }
- private void OnPresentationCloseInternal(Presentation pres)
- {
- try
+ // 检查COM对象是否仍然有效
+ try
+ {
+ // 尝试访问对象以验证其有效性
+ var _ = Marshal.GetIUnknownForObject(comObject);
+ Marshal.Release(_);
+ }
+ catch (InvalidComObjectException)
+ {
+ // COM对象已失效,直接返回
+ LogHelper.WriteLogToFile($"COM对象 {objectName} 已失效,跳过释放", LogHelper.LogType.Trace);
+ return;
+ }
+
+ int refCount = Marshal.ReleaseComObject(comObject);
+ LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace);
+ }
+ }
+ catch (InvalidComObjectException)
{
- PresentationClose?.Invoke(pres);
+ // COM对象已失效,这是正常的,无需记录错误
+ LogHelper.WriteLogToFile($"COM对象 {objectName} 已失效,跳过释放", LogHelper.LogType.Trace);
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
+ LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning);
+ }
+ }
+
+ private void UpdateCurrentPresentationInfo()
+ {
+ object activeWindow = null;
+ object view = null;
+ object selection = null;
+ object slideRange = null;
+
+ try
+ {
+ if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
{
- // COM 对象已失效,静默忽略
+ try
+ {
+ if (_pptActivePresentation != null)
+ {
+ try
+ {
+ // 检查COM对象是否仍然有效
+ if (!Marshal.IsComObject(_pptActivePresentation))
+ {
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ }
+ else
+ {
+ try
+ {
+ var _ = Marshal.GetIUnknownForObject(_pptActivePresentation);
+ Marshal.Release(_);
+ }
+ catch (InvalidComObjectException)
+ {
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ return;
+ }
+
+ dynamic pres = _pptActivePresentation;
+ CurrentPresentation = pres;
+ CurrentSlides = pres.Slides;
+ }
+ }
+ catch (InvalidComObjectException)
+ {
+ LogHelper.WriteLogToFile($"访问演示文稿属性失败: COM对象已失效", LogHelper.LogType.Warning);
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"访问演示文稿属性失败: {ex.Message}", LogHelper.LogType.Warning);
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ }
+
+ if (CurrentSlides != null)
+ {
+ try
+ {
+ var slideCount = CurrentSlides.Count;
+ if (slideCount > 0)
+ {
+ SlidesCount = slideCount;
+ }
+ else
+ {
+ SlidesCount = 0;
+ LogHelper.WriteLogToFile("PPT演示文稿页数为0,可能为空演示文稿", LogHelper.LogType.Warning);
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ SlidesCount = 0;
+ LogHelper.WriteLogToFile($"读取PPT页数失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
+ }
+
+ try
+ {
+ if (IsInSlideShow && _pptSlideShowWindow != null)
+ {
+ try
+ {
+ dynamic ssw = _pptSlideShowWindow;
+ view = ssw.View;
+ if (view != null)
+ {
+ dynamic viewObj = view;
+ CurrentSlide = viewObj.Slide;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取SlideShowWindow的Slide失败: {ex.Message}", LogHelper.LogType.Trace);
+ }
+ }
+ else
+ {
+ activeWindow = PPTApplication.ActiveWindow;
+ if (activeWindow != null)
+ {
+ dynamic aw = activeWindow;
+ selection = aw.Selection;
+ if (selection != null)
+ {
+ dynamic sel = selection;
+ slideRange = sel.SlideRange;
+ if (slideRange != null)
+ {
+ dynamic sr = slideRange;
+ int slideNumber = sr.SlideNumber;
+ if (slideNumber > 0 && slideNumber <= SlidesCount)
+ {
+ CurrentSlide = CurrentSlides[slideNumber];
+ }
+ }
+ }
+ }
+
+ if (CurrentSlide == null && SlidesCount > 0)
+ {
+ CurrentSlide = CurrentSlides[1];
+ }
+ }
+ }
+ catch (COMException comEx)
+ {
+ 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)
+ {
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ }
+ else
+ {
+ LogHelper.WriteLogToFile($"访问活动演示文稿失败: {comEx}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ else
+ {
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
}
}
- catch (Exception)
+ catch (InvalidComObjectException)
{
- }
- }
-
- private void OnSlideShowBeginInternal(SlideShowWindow wn)
- {
- try
- {
- SlideShowBegin?.Invoke(wn);
+ LogHelper.WriteLogToFile($"更新演示文稿信息失败: COM对象已失效", LogHelper.LogType.Warning);
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 处理放映开始事件失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error);
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
}
- }
-
- private void OnSlideShowNextSlideInternal(SlideShowWindow wn)
- {
- try
+ finally
{
- SlideShowNextSlide?.Invoke(wn);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 处理放映翻页事件失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- private void OnSlideShowEndInternal(Presentation pres)
- {
- try
- {
- SlideShowEnd?.Invoke(pres);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 处理放映结束事件失败: {ex}", LogHelper.LogType.Error);
+ SafeReleaseComObject(slideRange);
+ SafeReleaseComObject(selection);
+ SafeReleaseComObject(view);
+ SafeReleaseComObject(activeWindow);
}
}
#endregion
- #region IPPTLinkManager Methods
- public bool TryStartSlideShow()
+ #region Event Handlers
+ private void OnPresentationOpen(Presentation pres)
{
- object pres = null;
try
{
- if (!IsConnected || _pptApplication == null) return false;
- if (!Marshal.IsComObject(_pptApplication)) return false;
+ if (PPTApplication != null && pres != null)
+ {
+ try
+ {
+ _pptActivePresentation = PPTApplication.ActivePresentation;
+ _updateTime = DateTime.Now;
+ }
+ catch { }
+ }
+
+ UpdateCurrentPresentationInfo();
+ PresentationOpen?.Invoke(pres);
+ LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnPresentationBeforeClose(Presentation pres, ref bool cancel)
+ {
+ try
+ {
+ LogHelper.WriteLogToFile("PresentationBeforeClose事件触发", LogHelper.LogType.Trace);
+
+ if (_bindingEvents && PPTApplication != null)
+ {
+ try
+ {
+ Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
+
+ if (app != null)
+ {
+ app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
+ app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
+ app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEndForComEvent);
+ app.PresentationBeforeClose -= new EApplication_PresentationBeforeCloseEventHandler(OnPresentationBeforeClose);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.Message}", LogHelper.LogType.Trace);
+ }
+
+ _bindingEvents = false;
+ }
+
+ DisconnectFromPPT();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿关闭前事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowBegin(object wn)
+ {
+ try
+ {
+ _updateTime = DateTime.Now;
+ _pptSlideShowWindow = wn;
+ _lastPolledSlideNumber = -1; // 重置页码跟踪
- dynamic app = _pptApplication;
try
{
- pres = app.ActivePresentation;
+ if (_pptActivePresentation != null)
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+ int totalPage = GetTotalSlideIndex(_pptActivePresentation);
+
+ if (currentPage >= totalPage) _polling = 1;
+ else _polling = 0;
+
+ SlidesCount = totalPage;
+ _lastPolledSlideNumber = currentPage; // 初始化页码跟踪
+ }
}
catch
{
- pres = null;
+ _polling = 1;
+ _lastPolledSlideNumber = -1;
}
- if (pres == null || !Marshal.IsComObject(pres)) return false;
+ UpdateCurrentPresentationInfo();
- dynamic dp = pres;
- dp.SlideShowSettings.Run();
- return true;
+ if (!_lastSlideShowState)
+ {
+ _lastSlideShowState = true;
+ SlideShowStateChanged?.Invoke(true);
+ }
+
+ SlideShowBegin?.Invoke(wn);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowNextSlide(SlideShowWindow wn)
+ {
+ try
+ {
+ _updateTime = DateTime.Now;
+
+ try
+ {
+ if (_pptActivePresentation != null && _pptSlideShowWindow != null)
+ {
+ int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
+ int totalPage = GetTotalSlideIndex(_pptActivePresentation);
+
+ if (currentPage >= totalPage) _polling = 1;
+ else _polling = 0;
+
+ _lastPolledSlideNumber = currentPage; // 更新页码跟踪
+ }
+ }
+ catch
+ {
+ _polling = 1;
+ }
+
+ UpdateCurrentPresentationInfo();
+ SlideShowNextSlide?.Invoke(wn);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowEnd(object pres)
+ {
+ try
+ {
+ _updateTime = DateTime.Now;
+ SlidesCount = 0;
+ _lastPolledSlideNumber = -1;
+ _polling = 1;
+
+ PPTROTConnectionHelper.SafeReleaseComObject(_pptSlideShowWindow);
+ _pptSlideShowWindow = null;
+
+ if (IsSupportWPS && PPTApplication != null)
+ {
+ RecordWpsProcessForManagement();
+ }
+
+ UpdateCurrentPresentationInfo();
+
+ if (_lastSlideShowState)
+ {
+ _lastSlideShowState = false;
+ SlideShowStateChanged?.Invoke(false);
+ }
+
+ SlideShowEnd?.Invoke(pres);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowEndForComEvent(Presentation pres)
+ {
+ OnSlideShowEnd(pres);
+ }
+ #endregion
+
+ #region Public Methods
+ public bool TryNavigateToSlide(int slideNumber)
+ {
+ object slideShowWindows = null;
+ object slideShowWindow = null;
+ object view = null;
+ object windows = null;
+ object window = null;
+ object windowView = null;
+ try
+ {
+ if (!IsConnected || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ if (IsInSlideShow)
+ {
+ slideShowWindows = PPTApplication.SlideShowWindows;
+ if (slideShowWindows != null)
+ {
+ dynamic ssw = slideShowWindows;
+ if (ssw.Count >= 1)
+ {
+ slideShowWindow = ssw[1];
+ if (slideShowWindow != null)
+ {
+ dynamic sswObj = slideShowWindow;
+ view = sswObj.View;
+ if (view != null)
+ {
+ dynamic viewObj = view;
+ viewObj.GotoSlide(slideNumber);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ else if (CurrentPresentation != null)
+ {
+ windows = CurrentPresentation.Windows;
+ if (windows != null)
+ {
+ dynamic win = windows;
+ if (win.Count >= 1)
+ {
+ window = win[1];
+ if (window != null)
+ {
+ dynamic winObj = window;
+ windowView = winObj.View;
+ if (windowView != null)
+ {
+ dynamic viewObj = windowView;
+ viewObj.GotoSlide(slideNumber);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
}
catch (COMException comEx)
{
@@ -653,17 +1968,148 @@ namespace Ink_Canvas.Helpers
{
DisconnectFromPPT();
}
- LogHelper.WriteLogToFile($"[ROT] 开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error);
return false;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 开始幻灯片放映失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error);
return false;
}
finally
{
- SafeReleaseComObject(pres);
+ SafeReleaseComObject(windowView);
+ SafeReleaseComObject(window);
+ SafeReleaseComObject(windows);
+ SafeReleaseComObject(view);
+ SafeReleaseComObject(slideShowWindow);
+ SafeReleaseComObject(slideShowWindows);
+ }
+ }
+
+ public bool TryNavigateNext()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ // 在新线程中执行翻页操作,避免等待动画完成
+ new Thread(() =>
+ {
+ try
+ {
+ object slideShowWindows = PPTApplication.SlideShowWindows;
+ if (slideShowWindows != null)
+ {
+ dynamic ssw = slideShowWindows;
+ object slideShowWindow = ssw[1];
+ if (slideShowWindow != null)
+ {
+ dynamic sswObj = slideShowWindow;
+ try
+ {
+ sswObj.Activate();
+ }
+ catch { }
+ try
+ {
+ object view = sswObj.View;
+ if (view != null)
+ {
+ dynamic viewObj = view;
+ viewObj.Next();
+ }
+ }
+ catch { }
+ SafeReleaseComObject(slideShowWindow);
+ }
+ SafeReleaseComObject(slideShowWindows);
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换到下一页失败: {ex}", LogHelper.LogType.Error);
+ }
+ }).Start();
+ return true;
+ }
+ 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;
+
+ // 在新线程中执行翻页操作,避免等待动画完成
+ new Thread(() =>
+ {
+ try
+ {
+ object slideShowWindows = PPTApplication.SlideShowWindows;
+ if (slideShowWindows != null)
+ {
+ dynamic ssw = slideShowWindows;
+ object slideShowWindow = ssw[1];
+ if (slideShowWindow != null)
+ {
+ dynamic sswObj = slideShowWindow;
+ try
+ {
+ sswObj.Activate();
+ }
+ catch { }
+ try
+ {
+ object view = sswObj.View;
+ if (view != null)
+ {
+ dynamic viewObj = view;
+ viewObj.Previous();
+ }
+ }
+ catch { }
+ SafeReleaseComObject(slideShowWindow);
+ }
+ SafeReleaseComObject(slideShowWindows);
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换到上一页失败: {ex}", LogHelper.LogType.Error);
+ }
+ }).Start();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"启动上一页线程失败: {ex}", LogHelper.LogType.Error);
+ return false;
}
}
@@ -674,47 +2120,59 @@ namespace Ink_Canvas.Helpers
object view = null;
try
{
- if (!IsConnected || _pptApplication == null) return false;
- if (!Marshal.IsComObject(_pptApplication)) return false;
+ 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;
- int count = 0;
- try { count = ssw.Count; } catch { count = 0; }
-
- for (int i = 1; i <= count; i++)
+ slideShowWindow = ssw[1];
+ if (slideShowWindow != null)
{
- try
+ dynamic sswObj = slideShowWindow;
+ view = sswObj.View;
+ if (view != null)
{
- slideShowWindow = ssw[i];
- if (slideShowWindow != null)
- {
- dynamic sswObj = slideShowWindow;
- view = sswObj.View;
- if (view != null)
- {
- dynamic viewObj = view;
- viewObj.Exit();
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 结束第 {i} 个放映窗口失败: {ex}", LogHelper.LogType.Warning);
- }
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- view = null;
- slideShowWindow = null;
+ dynamic viewObj = view;
+ viewObj.Exit();
+ return true;
}
}
}
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"结束幻灯片放映失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ finally
+ {
+ SafeReleaseComObject(view);
+ SafeReleaseComObject(slideShowWindow);
+ SafeReleaseComObject(slideShowWindows);
+ }
+ }
+ 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)
@@ -722,365 +2180,51 @@ namespace Ink_Canvas.Helpers
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005)
{
+ // COM对象已失效,触发断开连接
DisconnectFromPPT();
}
- LogHelper.WriteLogToFile($"[ROT] 结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
return false;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 结束幻灯片放映失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error);
return false;
}
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
- }
-
- public bool TryNavigateToSlide(int slideNumber)
- {
- object slideShowWindows = null;
- object slideShowWindow = null;
- object view = null;
- try
- {
- if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false;
- if (!Marshal.IsComObject(_pptApplication)) return false;
-
- dynamic app = _pptApplication;
- slideShowWindows = app.SlideShowWindows;
- if (slideShowWindows != null)
- {
- dynamic ssw = slideShowWindows;
- slideShowWindow = ssw[1];
- if (slideShowWindow != null)
- {
- dynamic sswObj = slideShowWindow;
- if (!SkipAnimationsWhenNavigating)
- sswObj.Activate();
- view = sswObj.View;
- if (view != null)
- {
- dynamic viewObj = view;
- viewObj.GotoSlide(slideNumber);
- return true;
- }
- }
- }
- return false;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- DisconnectFromPPT();
- }
- LogHelper.WriteLogToFile($"[ROT] 跳转到第 {slideNumber} 页失败: {comEx.Message}", LogHelper.LogType.Error);
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 跳转到第 {slideNumber} 页失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
- }
-
- public bool TryNavigateNext()
- {
- object slideShowWindows = null;
- object slideShowWindow = null;
- object view = null;
- try
- {
- if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false;
- if (!Marshal.IsComObject(_pptApplication)) return false;
-
- dynamic app = _pptApplication;
- slideShowWindows = app.SlideShowWindows;
- if (slideShowWindows != null)
- {
- dynamic ssw = slideShowWindows;
- slideShowWindow = ssw[1];
- if (slideShowWindow != null)
- {
- dynamic sswObj = slideShowWindow;
- if (!SkipAnimationsWhenNavigating)
- sswObj.Activate();
- view = sswObj.View;
- if (view != null)
- {
- dynamic viewObj = view;
- viewObj.Next();
- return true;
- }
- }
- }
- return false;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- DisconnectFromPPT();
- }
- LogHelper.WriteLogToFile($"[ROT] 切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error);
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 切换到下一页失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
- }
-
- public bool TryNavigatePrevious()
- {
- object slideShowWindows = null;
- object slideShowWindow = null;
- object view = null;
- try
- {
- if (!IsConnected || !IsInSlideShow || _pptApplication == null) return false;
- if (!Marshal.IsComObject(_pptApplication)) return false;
-
- dynamic app = _pptApplication;
- slideShowWindows = app.SlideShowWindows;
- if (slideShowWindows != null)
- {
- dynamic ssw = slideShowWindows;
- slideShowWindow = ssw[1];
- if (slideShowWindow != null)
- {
- dynamic sswObj = slideShowWindow;
- if (!SkipAnimationsWhenNavigating)
- sswObj.Activate();
- view = sswObj.View;
- if (view != null)
- {
- dynamic viewObj = view;
- viewObj.Previous();
- return true;
- }
- }
- }
- return false;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- DisconnectFromPPT();
- }
- LogHelper.WriteLogToFile($"[ROT] 切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error);
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 切换到上一页失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
- }
-
- public int GetCurrentSlideNumber()
- {
- object slideShowWindows = null;
- object slideShowWindow = null;
- object view = null;
- try
- {
- if (!IsConnected || _pptApplication == null) return 0;
- if (!Marshal.IsComObject(_pptApplication)) return 0;
-
- dynamic app = _pptApplication;
- slideShowWindows = app.SlideShowWindows;
- if (slideShowWindows != null)
- {
- dynamic ssw = slideShowWindows;
- slideShowWindow = ssw[1];
- if (slideShowWindow != null)
- {
- dynamic sswObj = slideShowWindow;
- view = sswObj.View;
- if (view != null)
- {
- dynamic viewObj = view;
- return (int)viewObj.CurrentShowPosition;
- }
- }
- }
- return 0;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- DisconnectFromPPT();
- }
- LogHelper.WriteLogToFile($"[ROT] 获取当前页码失败: {comEx.Message}", LogHelper.LogType.Error);
- return 0;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 获取当前页码失败: {ex}", LogHelper.LogType.Error);
- return 0;
- }
- finally
- {
- SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
- }
-
- public string GetPresentationName()
- {
- object pres = null;
- try
- {
- pres = GetCurrentActivePresentation();
- if (pres == null) return "";
-
- dynamic dp = pres;
- return (string)dp.Name;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 获取演示文稿名称失败: {ex}", LogHelper.LogType.Error);
- return "";
- }
- finally
- {
- SafeReleaseComObject(pres);
- }
- }
-
- public bool TryShowSlideNavigation()
- {
- object slideShowWindows = null;
- object slideShowWindow = null;
- object slideNavigation = null;
- try
- {
- LogHelper.WriteLogToFile($"[ROT] 尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace);
-
- if (!IsConnected || !IsInSlideShow || _pptApplication == null)
- {
- LogHelper.WriteLogToFile("[ROT] PPT 未连接或未在放映状态", LogHelper.LogType.Warning);
- return false;
- }
-
- if (!Marshal.IsComObject(_pptApplication))
- {
- LogHelper.WriteLogToFile("[ROT] PPT 应用程序 COM 对象无效", LogHelper.LogType.Warning);
- return false;
- }
-
- dynamic app = _pptApplication;
- slideShowWindows = app.SlideShowWindows;
- if (slideShowWindows != null)
- {
- dynamic ssw = slideShowWindows;
- slideShowWindow = ssw[1];
- if (slideShowWindow == null)
- {
- LogHelper.WriteLogToFile("[ROT] 幻灯片放映窗口为空", LogHelper.LogType.Warning);
- return false;
- }
-
- try
- {
- dynamic sswObj = slideShowWindow;
- slideNavigation = sswObj.SlideNavigation;
- if (slideNavigation != null)
- {
- dynamic sn = slideNavigation;
- sn.Visible = true;
- LogHelper.WriteLogToFile("[ROT] 成功显示幻灯片导航", LogHelper.LogType.Event);
- return true;
- }
-
- LogHelper.WriteLogToFile("[ROT] SlideNavigation 对象为空,可能当前环境不支持", LogHelper.LogType.Warning);
- return false;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x80020006)
- {
- LogHelper.WriteLogToFile("[ROT] 当前 PPT 实例不支持 SlideNavigation 功能", LogHelper.LogType.Warning);
- return false;
- }
- throw;
- }
- }
- return false;
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005)
- {
- DisconnectFromPPT();
- }
- LogHelper.WriteLogToFile($"[ROT] 显示幻灯片导航失败: {comEx.Message}", LogHelper.LogType.Error);
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"[ROT] 显示幻灯片导航失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- finally
- {
- SafeReleaseComObject(slideNavigation);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- }
}
+ ///
+ /// 获取当前活跃的演示文稿
+ ///
public object GetCurrentActivePresentation()
{
- object pres = null;
object slideShowWindows = null;
object slideShowWindow = null;
object view = null;
object slide = null;
+ object activeWindow = null;
+ object presentation = null;
try
{
- if (!IsConnected || _pptApplication == null) return null;
- if (!Marshal.IsComObject(_pptApplication)) return null;
+ if (!IsConnected || PPTApplication == null) return null;
+ if (!Marshal.IsComObject(PPTApplication)) return null;
- dynamic app = _pptApplication;
+ // 检查COM对象是否仍然有效
+ try
+ {
+ var _ = Marshal.GetIUnknownForObject(PPTApplication);
+ Marshal.Release(_);
+ }
+ catch (InvalidComObjectException)
+ {
+ // COM对象已失效
+ return null;
+ }
- // 优先使用放映窗口里的当前演示文稿
if (IsInSlideShow)
{
- slideShowWindows = app.SlideShowWindows;
+ dynamic pptAppForSSW = PPTApplication;
+ slideShowWindows = pptAppForSSW.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1097,9 +2241,9 @@ namespace Ink_Canvas.Helpers
slide = viewObj.Slide;
if (slide != null)
{
- dynamic ds = slide;
- pres = ds.Parent;
- return pres;
+ dynamic slideObj = slide;
+ presentation = slideObj.Parent;
+ return presentation;
}
}
}
@@ -1107,82 +2251,1272 @@ namespace Ink_Canvas.Helpers
}
}
- // 其次尝试 ActiveWindow.Presentation
- try
+ activeWindow = PPTApplication.ActiveWindow;
+ if (activeWindow != null)
{
- var aw = app.ActiveWindow;
- if (aw != null)
+ dynamic aw = activeWindow;
+ presentation = aw.Presentation;
+ if (presentation != null)
{
- dynamic daw = aw;
- pres = daw.Presentation;
- if (pres != null) return pres;
+ return presentation;
}
}
- catch
- {
- }
- return null;
+ return CurrentPresentation;
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
- if (hr != 0x8001010E && hr != 0x80004005)
+ if (hr == 0x8001010E || hr == 0x80004005)
{
- LogHelper.WriteLogToFile($"[ROT] 获取当前演示文稿失败: {comEx.Message}", LogHelper.LogType.Error);
+ DisconnectFromPPT();
}
- return null;
+ if (hr == 0x80048240)
+ {
+ return null;
+ }
+ LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning);
+ return CurrentPresentation;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"[ROT] 获取当前演示文稿失败: {ex}", LogHelper.LogType.Error);
- return null;
+ LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
+ return CurrentPresentation;
+ }
+ finally
+ {
+ if (presentation != null && !ReferenceEquals(presentation, CurrentPresentation))
+ {
+ SafeReleaseComObject(presentation);
+ }
+ SafeReleaseComObject(slide);
+ SafeReleaseComObject(view);
+ SafeReleaseComObject(slideShowWindow);
+ SafeReleaseComObject(slideShowWindows);
+ SafeReleaseComObject(activeWindow);
+ }
+ }
+
+ ///
+ /// 获取当前幻灯片编号
+ ///
+ private int GetCurrentSlideIndex(dynamic slideShowWindow)
+ {
+ object view = null;
+ object slide = null;
+
+ try
+ {
+ if (slideShowWindow == null) return 0;
+
+ // 检查COM对象是否有效
+ if (!Marshal.IsComObject(slideShowWindow))
+ {
+ return 0;
+ }
+
+ try
+ {
+ var _ = Marshal.GetIUnknownForObject(slideShowWindow);
+ Marshal.Release(_);
+ }
+ catch (InvalidComObjectException)
+ {
+ return 0;
+ }
+
+ dynamic ssw = slideShowWindow;
+ view = ssw.View;
+ if (view != null)
+ {
+ dynamic viewObj = view;
+ slide = viewObj.Slide;
+ if (slide != null)
+ {
+ dynamic slideObj = slide;
+ return slideObj.SlideIndex;
+ }
+ }
+ return 0;
+ }
+ catch (COMException comEx)
+ {
+ // 处理 0x80048240: SlideShowView.Slide : Invalid request. No slide is currently in view.
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x80048240)
+ {
+ return 0;
+ }
+ throw;
+ }
+ catch (InvalidComObjectException)
+ {
+ return 0;
}
finally
{
SafeReleaseComObject(slide);
SafeReleaseComObject(view);
- SafeReleaseComObject(slideShowWindow);
- SafeReleaseComObject(slideShowWindows);
- // 注意:pres 作为返回值时不在这里释放,由调用方负责(如有需要)
}
}
- #endregion
- #region Helpers
- ///
- /// 安全释放 COM 对象。
- ///
- private void SafeReleaseComObject(object comObject)
+ private int GetTotalSlideIndex(dynamic presentation)
{
try
{
- if (comObject != null && Marshal.IsComObject(comObject))
- {
- Marshal.ReleaseComObject(comObject);
- }
+ if (presentation == null) return 0;
+ dynamic pres = presentation;
+ return pres.Slides.Count;
}
catch
{
+ return 0;
+ }
+ }
+
+ public int GetCurrentSlideNumber()
+ {
+ object activeWindow = null;
+ object selection = null;
+ object slideRange = null;
+ try
+ {
+ if (!IsConnected || PPTApplication == null) return 0;
+ if (!Marshal.IsComObject(PPTApplication)) return 0;
+
+ if (IsInSlideShow && _pptSlideShowWindow != null)
+ {
+ try
+ {
+ return GetCurrentSlideIndex(_pptSlideShowWindow);
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ activeWindow = PPTApplication.ActiveWindow;
+ if (activeWindow != null)
+ {
+ dynamic aw = activeWindow;
+ selection = aw.Selection;
+ if (selection != null)
+ {
+ dynamic sel = selection;
+ slideRange = sel.SlideRange;
+ if (slideRange != null)
+ {
+ dynamic sr = slideRange;
+ int slideNumber = sr.SlideNumber;
+ if (slideNumber > 0)
+ {
+ return slideNumber;
+ }
+ }
+ }
+ }
+
+ if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
+ {
+ return CurrentSlide.SlideNumber;
+ }
+
+ return 0;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ DisconnectFromPPT();
+ }
+ return 0;
+ }
+ catch (Exception)
+ {
+ return 0;
+ }
+ finally
+ {
+ SafeReleaseComObject(slideRange);
+ SafeReleaseComObject(selection);
+ SafeReleaseComObject(activeWindow);
+ }
+ }
+
+ 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()
+ {
+ object slideShowWindows = null;
+ object slideShowWindow = null;
+ object slideNavigation = null;
+ 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;
+ }
+
+ slideShowWindows = PPTApplication.SlideShowWindows;
+ if (slideShowWindows != null)
+ {
+ dynamic ssw = slideShowWindows;
+ slideShowWindow = ssw[1];
+ if (slideShowWindow == null)
+ {
+ LogHelper.WriteLogToFile("幻灯片放映窗口为空", LogHelper.LogType.Warning);
+ return false;
+ }
+
+ try
+ {
+ dynamic sswObj = slideShowWindow;
+ slideNavigation = sswObj.SlideNavigation;
+ if (slideNavigation != null)
+ {
+ dynamic sn = slideNavigation;
+ sn.Visible = true;
+ LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event);
+ return true;
+ }
+
+ LogHelper.WriteLogToFile("SlideNavigation对象为空,可能是WPS不支持此功能", LogHelper.LogType.Warning);
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x80020006)
+ {
+ LogHelper.WriteLogToFile("WPS不支持SlideNavigation功能", LogHelper.LogType.Warning);
+ return false;
+ }
+ throw;
+ }
+ }
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ 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;
+ }
+ finally
+ {
+ SafeReleaseComObject(slideNavigation);
+ SafeReleaseComObject(slideShowWindow);
+ SafeReleaseComObject(slideShowWindows);
}
}
#endregion
- #region IDisposable
- public void Dispose()
+ #region WPS Process Management
+ private void RecordWpsProcessForManagement()
{
- if (_disposed) return;
- _disposed = true;
+ if (!IsSupportWPS || PPTApplication == null) return;
try
{
- StopMonitoring();
+ 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($"[ROT] Dispose 异常: {ex}", LogHelper.LogType.Error);
+ 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)
+ if (PPTApplication != null)
+ {
+ try
+ {
+ Marshal.ReleaseComObject(PPTApplication);
+ PPTApplication = null;
+ LogHelper.WriteLogToFile("已释放pptApp对象", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"释放pptApp对象失败: {ex.Message}", LogHelper.LogType.Trace);
+ PPTApplication = null;
+ }
+ }
+
+ // 第四步:强制垃圾回收及等待终结器执行
+ 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();
+
+ 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")]
+ private static extern int GetWindowTextLength(IntPtr hWnd);
+
+ [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 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()
+ {
+ if (!_disposed)
+ {
+ StopMonitoring();
+ StopWpsProcessCheckTimer();
+
+ _disposed = true;
}
}
#endregion
}
-}
\ No newline at end of file
+}
+
+
+
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index 595cd5bb..73e62c9f 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -724,23 +724,13 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event);
_singlePPTInkManager?.ClearAllStrokes();
_exitPPTModeAfterDisconnectTimer?.Stop();
- _exitPPTModeAfterDisconnectTimer = new DispatcherTimer
- {
- Interval = TimeSpan.FromMilliseconds(ExitPPTModeAfterDisconnectDelayMs)
- };
- _exitPPTModeAfterDisconnectTimer.Tick += (s, e) =>
- {
- _exitPPTModeAfterDisconnectTimer?.Stop();
- _exitPPTModeAfterDisconnectTimer = null;
- if (_pptManager?.IsConnected != true)
- {
- _pptUIManager?.UpdateSlideShowStatus(false);
- _pptUIManager?.UpdateSidebarExitButtons(false);
- ResetPPTStateVariables();
- _ = HandleManualSlideShowEnd();
- }
- };
- _exitPPTModeAfterDisconnectTimer.Start();
+ _exitPPTModeAfterDisconnectTimer = null;
+ _pptUIManager?.UpdateSlideShowStatus(false);
+ _pptUIManager?.UpdateSidebarExitButtons(false);
+ ResetPPTStateVariables();
+ _ = HandleManualSlideShowEnd();
+ if (Settings.PowerPointSettings.UseRotPptLink)
+ _pptManager?.ReloadConnection();
}
});
}
@@ -921,14 +911,36 @@ namespace Ink_Canvas
string presentationName = null;
Presentation activePresentation = null;
- if (wn?.View != null && wn.Presentation != null)
+ if (wn != null)
{
- activePresentation = wn.Presentation;
- currentSlide = wn.View.CurrentShowPosition;
- totalSlides = activePresentation.Slides.Count;
- presentationName = activePresentation.Name;
+ try
+ {
+ if (wn.View != null && wn.Presentation != null)
+ {
+ activePresentation = wn.Presentation;
+ currentSlide = wn.View.CurrentShowPosition;
+ totalSlides = activePresentation.Slides.Count;
+ presentationName = activePresentation.Name;
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ activePresentation = null;
+ currentSlide = 0;
+ totalSlides = 0;
+ presentationName = null;
+ }
+ catch (Exception ex)
+ {
+ activePresentation = null;
+ currentSlide = 0;
+ totalSlides = 0;
+ presentationName = null;
+ }
}
- else
+
+ if (activePresentation == null)
{
activePresentation = _pptManager?.GetCurrentActivePresentation() as Presentation;
currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
@@ -1134,10 +1146,41 @@ namespace Ink_Canvas
{
try
{
- if (wn?.View == null || wn.Presentation == null) return;
+ int currentSlide = 0;
+ int totalSlides = 0;
- int currentSlide = wn.View.CurrentShowPosition;
- int totalSlides = wn.Presentation.Slides.Count;
+ if (wn != null)
+ {
+ try
+ {
+ if (wn.View != null)
+ {
+ currentSlide = wn.View.CurrentShowPosition;
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ LogHelper.WriteLogToFile(
+ $"通过 SlideShowWindow.View 获取当前页失败: {comEx.Message} (HR: 0x{hr:X8}),将回退到 PPT 管理器获取",
+ LogHelper.LogType.Warning);
+ currentSlide = 0;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile(
+ $"通过 SlideShowWindow.View 获取当前页时发生异常,将回退到 PPT 管理器获取: {ex}",
+ LogHelper.LogType.Warning);
+ currentSlide = 0;
+ }
+ }
+
+ if (currentSlide <= 0)
+ {
+ currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ }
+
+ totalSlides = _pptManager?.SlidesCount ?? 0;
if (currentSlide == _previousSlideID) return;