VSTO Outlook:如何检测Explorer/Inspector窗口停止移动
检测Outlook窗口停止移动的实现方案
Outlook对象模型并没有提供直接的「窗口移动结束」事件,需要通过间接方式实现检测,以下是两种可行方案:
方案一:定时器轮询窗口位置(低成本实现)
核心思路
在BeforeMove事件触发时启动定时器,每隔固定短时间(如100ms)检查窗口的Left和Top属性。当连续多次检测到窗口位置不变时,判定移动停止,重置全局变量并关闭定时器。
具体实现(以VSTO C#为例)
private Timer _moveCheckTimer; private int _stablePositionCount; private Point _lastWindowPos; private bool _isWindowMoving; // 你的全局变量 // 初始化事件订阅与定时器 private void InitializeHandlers() { _moveCheckTimer = new Timer { Interval = 100 }; _moveCheckTimer.Tick += MoveCheckTimer_Tick; // 订阅Explorer和Inspector的BeforeMove事件 Globals.ThisAddIn.Application.ExplorerBeforeMove += OnExplorerBeforeMove; Globals.ThisAddIn.Application.InspectorBeforeMove += OnInspectorBeforeMove; } private void OnExplorerBeforeMove(Explorer explorer, ref bool cancel) { StartMoveDetection(explorer.Window); } private void OnInspectorBeforeMove(Inspector inspector, ref bool cancel) { StartMoveDetection(inspector.Window); } private void StartMoveDetection(Microsoft.Office.Interop.Outlook.Window window) { _isWindowMoving = true; _lastWindowPos = new Point(window.Left, window.Top); _stablePositionCount = 0; _moveCheckTimer.Start(); } private void MoveCheckTimer_Tick(object sender, EventArgs e) { var activeWindow = Globals.ThisAddIn.Application.ActiveWindow(); if (activeWindow == null) { _moveCheckTimer.Stop(); _isWindowMoving = false; return; } Point currentPos = new Point(activeWindow.Left, activeWindow.Top); if (currentPos == _lastWindowPos) { _stablePositionCount++; // 连续3次位置不变(300ms)判定为停止移动 if (_stablePositionCount >= 3) { _isWindowMoving = false; _moveCheckTimer.Stop(); } } else { _stablePositionCount = 0; _lastWindowPos = currentPos; } }
注意事项
- 需处理窗口意外关闭的情况,避免定时器持续运行
- VBA环境可使用
Application.OnTime模拟定时器逻辑
方案二:Windows API钩子(精准检测)
核心思路
通过Windows API设置窗口消息钩子,监听窗口的WM_EXITSIZEMOVE消息(值为0x0232),该消息会在窗口结束移动/调整大小时触发,以此精准判定移动停止时机。
具体实现(C#)
private IntPtr _moveHook; private bool _isWindowMoving; // Windows API声明 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); private HookProc _moveHookProc; private const int WH_CALLWNDPROC = 4; private const int WM_EXITSIZEMOVE = 0x0232; private void OnExplorerBeforeMove(Explorer explorer, ref bool cancel) { _isWindowMoving = true; SetMoveHook(explorer.HWnd); } private void OnInspectorBeforeMove(Inspector inspector, ref bool cancel) { _isWindowMoving = true; SetMoveHook(inspector.HWnd); } private void SetMoveHook(IntPtr hWnd) { if (_moveHook != IntPtr.Zero) UnhookWindowsHookEx(_moveHook); _moveHookProc = MoveHookCallback; _moveHook = SetWindowsHookEx(WH_CALLWNDPROC, _moveHookProc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId()); } private IntPtr MoveHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { CWPSTRUCT cwp = Marshal.PtrToStructure<CWPSTRUCT>(lParam); if (cwp.message == WM_EXITSIZEMOVE) { _isWindowMoving = false; UnhookWindowsHookEx(_moveHook); _moveHook = IntPtr.Zero; } } return CallNextHookEx(_moveHook, nCode, wParam, lParam); } [StructLayout(LayoutKind.Sequential)] private struct CWPSTRUCT { public IntPtr lParam; public IntPtr wParam; public int message; public IntPtr hwnd; }
注意事项
- 钩子需与窗口线程关联,需处理线程安全问题
- VBA环境可通过Declare语句调用API,但实现复杂度较高,适合对精度要求高的场景
内容的提问来源于stack exchange,提问作者Willy
相关产品推荐
相关产品推荐

