improve:PPT联动

This commit is contained in:
2026-01-18 02:03:39 +08:00
parent c11ca7a3f7
commit f65341d906
+261 -89
View File
@@ -29,7 +29,7 @@ namespace Ink_Canvas.Helpers
#endregion
#region Properties
public object PPTApplication { get; private set; }
public dynamic PPTApplication { get; private set; }
public dynamic CurrentPresentation { get; private set; }
public dynamic CurrentSlides { get; private set; }
public dynamic CurrentSlide { get; private set; }
@@ -44,8 +44,7 @@ namespace Ink_Canvas.Helpers
if (!Marshal.IsComObject(PPTApplication)) return false;
// 尝试访问一个简单的属性来验证连接是否有效
dynamic app = PPTApplication;
var _ = app.Name;
var _ = PPTApplication.Name;
return true;
}
catch (COMException comEx)
@@ -75,8 +74,7 @@ namespace Ink_Canvas.Helpers
{
if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false;
dynamic app = PPTApplication;
slideShowWindows = app.SlideShowWindows;
slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows == null) return false;
dynamic ssw = slideShowWindows;
@@ -130,9 +128,9 @@ namespace Ink_Canvas.Helpers
#endregion
#region Private Fields
private Timer _connectionCheckTimer;
private Timer _slideShowStateCheckTimer;
private Timer _wpsProcessCheckTimer;
private Thread _monitoringThread;
private bool _shouldStop = false;
private readonly object _monitoringLock = new object();
private Process _wpsProcess;
private bool _isModuleUnloading = false;
private bool _hasWpsProcessId;
@@ -147,7 +145,7 @@ namespace Ink_Canvas.Helpers
private dynamic _pptActivePresentation;
private dynamic _pptSlideShowWindow;
private int _polling = 0;
private bool _forcePolling = false;
private bool _forcePolling = true;
private bool _bindingEvents = false;
private DateTime _updateTime;
private int _lastPolledSlideNumber = -1;
@@ -156,36 +154,48 @@ namespace Ink_Canvas.Helpers
#region Constructor & Initialization
public PPTManager()
{
InitializeConnectionTimer();
}
private void InitializeConnectionTimer()
{
_connectionCheckTimer = new Timer(500);
_connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed;
_connectionCheckTimer.AutoReset = true;
_slideShowStateCheckTimer = new Timer(1000);
_slideShowStateCheckTimer.Elapsed += OnSlideShowStateCheckTimerElapsed;
_slideShowStateCheckTimer.AutoReset = true;
}
public void StartMonitoring()
{
if (!_disposed)
if (_disposed) return;
lock (_monitoringLock)
{
_connectionCheckTimer?.Start();
_slideShowStateCheckTimer?.Start();
if (_monitoringThread != null && _monitoringThread.IsAlive)
{
return; // 已经在运行
}
_shouldStop = false;
_monitoringThread = new Thread(PptComService)
{
IsBackground = true,
Name = "PPTMonitoringThread"
};
_monitoringThread.Start();
LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace);
}
}
public void StopMonitoring()
{
_connectionCheckTimer?.Stop();
_slideShowStateCheckTimer?.Stop();
DisconnectFromPPT();
LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace);
lock (_monitoringLock)
{
_shouldStop = true;
if (_monitoringThread != null && _monitoringThread.IsAlive)
{
// 等待线程退出,最多等待2秒
if (!_monitoringThread.Join(2000))
{
LogHelper.WriteLogToFile("等待监控线程退出超时", LogHelper.LogType.Warning);
}
}
DisconnectFromPPT();
LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace);
}
}
#endregion
@@ -287,8 +297,7 @@ namespace Ink_Canvas.Helpers
{
try
{
dynamic app = PPTApplication;
_pptActivePresentation = app.ActivePresentation;
_pptActivePresentation = PPTApplication.ActivePresentation;
if (_pptActivePresentation != null)
{
LogHelper.WriteLogToFile("轮询模式:初始化_pptActivePresentation", LogHelper.LogType.Trace);
@@ -326,8 +335,7 @@ namespace Ink_Canvas.Helpers
try
{
dynamic app = PPTApplication;
activePresentation = app.ActivePresentation;
activePresentation = PPTApplication.ActivePresentation;
if (activePresentation != null && _pptActivePresentation != null && !PPTROTConnectionHelper.AreComObjectsEqual(_pptActivePresentation, activePresentation))
{
@@ -357,12 +365,11 @@ namespace Ink_Canvas.Helpers
bool isSlideShowActive = false;
try
{
dynamic app = PPTApplication;
if (activePresentation == null)
{
try
{
activePresentation = app.ActivePresentation;
activePresentation = PPTApplication.ActivePresentation;
}
catch (Exception ex)
{
@@ -374,7 +381,7 @@ namespace Ink_Canvas.Helpers
{
try
{
dynamic slideShowWindows = app.SlideShowWindows;
dynamic slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null && slideShowWindows.Count > 0)
{
isSlideShowActive = true;
@@ -424,13 +431,9 @@ namespace Ink_Canvas.Helpers
if (isSlideShowActive)
{
// 在轮询模式下,更频繁地检查(每500ms,与定时器同步)
// 否则每3秒检查一次
bool shouldPoll = _forcePolling || (DateTime.Now - _updateTime).TotalMilliseconds > 3000;
if (shouldPoll)
if ((DateTime.Now - _updateTime).TotalMilliseconds > 3000 || _forcePolling)
{
LogHelper.WriteLogToFile($"开始轮询检查: forcePolling={_forcePolling}, 距离上次更新={(DateTime.Now - _updateTime).TotalMilliseconds}ms", LogHelper.LogType.Trace);
LogHelper.WriteLogToFile($"轮询", LogHelper.LogType.Trace);
try
{
@@ -453,7 +456,6 @@ namespace Ink_Canvas.Helpers
{
SlidesCount = 0;
_polling = 0;
LogHelper.WriteLogToFile("轮询: 无法获取总页数", LogHelper.LogType.Trace);
}
else
{
@@ -470,10 +472,7 @@ namespace Ink_Canvas.Helpers
if (currentPage >= tempTotalPage) _polling = 1;
else _polling = 0;
LogHelper.WriteLogToFile($"轮询: 当前页={currentPage}, 总页={tempTotalPage}, 上次页={_lastPolledSlideNumber}, forcePolling={_forcePolling}", LogHelper.LogType.Trace);
// 在轮询模式下,检测页码变化并触发事件
if (_forcePolling && _lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
{
try
{
@@ -501,7 +500,7 @@ namespace Ink_Canvas.Helpers
catch (Exception ex)
{
SlidesCount = 0;
LogHelper.WriteLogToFile($"轮询检查异常: {ex.Message}", LogHelper.LogType.Warning);
LogHelper.WriteLogToFile($"获取总页数失败: {ex.Message}", LogHelper.LogType.Warning);
}
finally
{
@@ -517,17 +516,16 @@ namespace Ink_Canvas.Helpers
{
int currentPage = GetCurrentSlideIndex(_pptSlideShowWindow);
// 在轮询模式下,检测页码变化并触发事件
if (_forcePolling && _lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
if (_lastPolledSlideNumber != -1 && currentPage != _lastPolledSlideNumber)
{
try
{
LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage}", LogHelper.LogType.Trace);
LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace);
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Trace);
LogHelper.WriteLogToFile($"触发轮询模式幻灯片切换事件失败: {ex.Message}", LogHelper.LogType.Warning);
}
}
@@ -683,10 +681,11 @@ namespace Ink_Canvas.Helpers
{
try
{
Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application);
if (appType.IsInstanceOfType(PPTApplication))
// 使用 as 操作符进行类型转换,与 inkeys 保持一致
Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
if (pptAppForEvents != null)
{
Microsoft.Office.Interop.PowerPoint.Application pptAppForEvents = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication;
pptAppForEvents.SlideShowNextSlide += new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
pptAppForEvents.SlideShowBegin += new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
pptAppForEvents.SlideShowEnd += new EApplication_SlideShowEndEventHandler(OnSlideShowEnd);
@@ -701,7 +700,6 @@ namespace Ink_Canvas.Helpers
}
_bindingEvents = true;
_forcePolling = false;
LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace);
}
@@ -732,6 +730,9 @@ namespace Ink_Canvas.Helpers
LogHelper.WriteLogToFile($"无法注册事件: {ex.Message}", LogHelper.LogType.Warning);
}
_bindingEvents = false;
_forcePolling = true;
if (_pptActivePresentation != null)
{
UpdateCurrentPresentationInfo();
@@ -773,10 +774,11 @@ namespace Ink_Canvas.Helpers
{
try
{
Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application);
if (appType.IsInstanceOfType(PPTApplication))
// 使用 as 操作符进行类型转换,与 inkeys 保持一致
Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
if (app != null)
{
Microsoft.Office.Interop.PowerPoint.Application app = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication;
app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEnd);
@@ -789,7 +791,6 @@ namespace Ink_Canvas.Helpers
}
_bindingEvents = false;
_forcePolling = false;
}
}
catch { }
@@ -840,7 +841,7 @@ namespace Ink_Canvas.Helpers
CurrentSlide = null;
SlidesCount = 0;
_polling = 0;
_forcePolling = false;
_forcePolling = true;
_bindingEvents = false;
GC.Collect();
@@ -993,8 +994,7 @@ namespace Ink_Canvas.Helpers
}
else
{
dynamic app = PPTApplication;
activeWindow = app.ActiveWindow;
activeWindow = PPTApplication.ActiveWindow;
if (activeWindow != null)
{
dynamic aw = activeWindow;
@@ -1095,8 +1095,7 @@ namespace Ink_Canvas.Helpers
{
try
{
dynamic app = PPTApplication;
_pptActivePresentation = app.ActivePresentation;
_pptActivePresentation = PPTApplication.ActivePresentation;
_updateTime = DateTime.Now;
}
catch { }
@@ -1122,10 +1121,11 @@ namespace Ink_Canvas.Helpers
{
try
{
Type appType = typeof(Microsoft.Office.Interop.PowerPoint.Application);
if (appType.IsInstanceOfType(PPTApplication))
// 使用 as 操作符进行类型转换,与 inkeys 保持一致
Microsoft.Office.Interop.PowerPoint.Application app = PPTApplication as Microsoft.Office.Interop.PowerPoint.Application;
if (app != null)
{
Microsoft.Office.Interop.PowerPoint.Application app = (Microsoft.Office.Interop.PowerPoint.Application)PPTApplication;
app.SlideShowNextSlide -= new EApplication_SlideShowNextSlideEventHandler(OnSlideShowNextSlide);
app.SlideShowBegin -= new EApplication_SlideShowBeginEventHandler(OnSlideShowBegin);
app.SlideShowEnd -= new EApplication_SlideShowEndEventHandler(OnSlideShowEnd);
@@ -1138,7 +1138,6 @@ namespace Ink_Canvas.Helpers
}
_bindingEvents = false;
_forcePolling = false;
}
DisconnectFromPPT();
@@ -1258,8 +1257,7 @@ namespace Ink_Canvas.Helpers
if (IsInSlideShow)
{
dynamic app = PPTApplication;
slideShowWindows = app.SlideShowWindows;
slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1343,8 +1341,7 @@ namespace Ink_Canvas.Helpers
{
try
{
dynamic app = PPTApplication;
object slideShowWindows = app.SlideShowWindows;
object slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1407,8 +1404,7 @@ namespace Ink_Canvas.Helpers
{
try
{
dynamic app = PPTApplication;
object slideShowWindows = app.SlideShowWindows;
object slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1469,8 +1465,7 @@ namespace Ink_Canvas.Helpers
if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
if (!Marshal.IsComObject(PPTApplication)) return false;
dynamic app = PPTApplication;
slideShowWindows = app.SlideShowWindows;
slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1586,8 +1581,7 @@ namespace Ink_Canvas.Helpers
}
}
dynamic app = PPTApplication;
activeWindow = app.ActiveWindow;
activeWindow = PPTApplication.ActiveWindow;
if (activeWindow != null)
{
dynamic aw = activeWindow;
@@ -1701,8 +1695,7 @@ namespace Ink_Canvas.Helpers
}
}
dynamic app = PPTApplication;
activeWindow = app.ActiveWindow;
activeWindow = PPTApplication.ActiveWindow;
if (activeWindow != null)
{
dynamic aw = activeWindow;
@@ -1824,8 +1817,7 @@ namespace Ink_Canvas.Helpers
return false;
}
dynamic app = PPTApplication;
slideShowWindows = app.SlideShowWindows;
slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows != null)
{
dynamic ssw = slideShowWindows;
@@ -1898,12 +1890,11 @@ namespace Ink_Canvas.Helpers
Process wpsProcess = null;
// 方法1:通过应用程序路径检测
dynamic app = PPTApplication;
if (app.Path.Contains("Kingsoft\\WPS Office\\") ||
app.Path.Contains("WPS Office\\"))
if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") ||
PPTApplication.Path.Contains("WPS Office\\"))
{
uint processId;
GetWindowThreadProcessId((IntPtr)app.HWND, out processId);
GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId);
wpsProcess = Process.GetProcessById((int)processId);
}
@@ -2159,10 +2150,9 @@ namespace Ink_Canvas.Helpers
{
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
{
dynamic app = PPTApplication;
if (app.SlideShowWindows?.Count > 0)
if (PPTApplication.SlideShowWindows?.Count > 0)
{
pptActWindow = app.SlideShowWindows[1];
pptActWindow = PPTApplication.SlideShowWindows[1];
}
}
}
@@ -2332,6 +2322,9 @@ namespace Ink_Canvas.Helpers
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
@@ -2634,6 +2627,185 @@ namespace Ink_Canvas.Helpers
}
#endregion
#region Window Handle Methods
/// <summary>
/// 获取PPT窗口句柄
/// </summary>
/// <returns>窗口句柄,如果获取失败返回 IntPtr.Zero</returns>
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;
}
/// <summary>
/// 从 SlideShowWindow 对象获取窗口句柄
/// </summary>
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;
}
/// <summary>
/// 通过窗口标题匹配获取PPT窗口句柄(备用方法)
/// </summary>
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<IntPtr> candidates = new List<IntPtr>();
// 调用 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()
{