diff --git a/Source/ExcelDna.IntelliSense/IntelliSenseServer.cs b/Source/ExcelDna.IntelliSense/IntelliSenseServer.cs index ede2a91..9a15549 100644 --- a/Source/ExcelDna.IntelliSense/IntelliSenseServer.cs +++ b/Source/ExcelDna.IntelliSense/IntelliSenseServer.cs @@ -30,7 +30,7 @@ namespace ExcelDna.IntelliSense // REMEMBER: COM events are not necessarily safe macro contexts. public static class IntelliSenseServer { - const string ServerVersion = "1.7.0"; // TODO: Define and manage this somewhere else + const string ServerVersion = "1.7.1"; // TODO: Define and manage this somewhere else // NOTE: Do not change these constants in custom versions. // They are part of the co-operative safety mechanism allowing different add-ins providing IntelliSense to work together safely. diff --git a/Source/ExcelDna.IntelliSense/UIMonitor/WinEvents.cs b/Source/ExcelDna.IntelliSense/UIMonitor/WinEvents.cs index 24b1652..bc1b6e6 100644 --- a/Source/ExcelDna.IntelliSense/UIMonitor/WinEvents.cs +++ b/Source/ExcelDna.IntelliSense/UIMonitor/WinEvents.cs @@ -219,6 +219,7 @@ bool IsSupportedWinEvent(WinEvent winEvent) winEvent == WinEvent.EVENT_SYSTEM_MOVESIZESTART || // Only for the on-demand hook winEvent == WinEvent.EVENT_SYSTEM_MOVESIZEEND || // Only for the on-demand hook winEvent == WinEvent.EVENT_OBJECT_SELECTION || // Only for the PopupList + winEvent == WinEvent.EVENT_OBJECT_LOCATIONCHANGE || winEvent == WinEvent.EVENT_SYSTEM_CAPTURESTART; } diff --git a/Source/ExcelDna.IntelliSense/UIMonitor/WindowLocationWatcher.cs b/Source/ExcelDna.IntelliSense/UIMonitor/WindowLocationWatcher.cs index b5b7907..cdd3e3a 100644 --- a/Source/ExcelDna.IntelliSense/UIMonitor/WindowLocationWatcher.cs +++ b/Source/ExcelDna.IntelliSense/UIMonitor/WindowLocationWatcher.cs @@ -9,28 +9,64 @@ public class WindowLocationWatcher : IDisposable IntPtr _hWnd; SynchronizationContext _syncContextAuto; SynchronizationContext _syncContextMain; - WinEventHook _windowLocationChangeHook; + WinEventHook _windowMoveSizeHook; + WinEventHook _locationChangeEventHook; public event EventHandler LocationChanged; // NOTE: An earlier attempt was to monitor LOCATIONCHANGE only between EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND + // (for the purpose of moving the tooltip to the correct position when the user moves the Excel main window) // This nearly worked, and meant we were watching many fewer events ... // ...but we missed some of the resizing events for the window, leaving our tooltip stranded. - // So until we can find a workaround for that (perhaps a timer would work fine for this), we watch all the LOCATIONCHANGE events. + // We then started to watch all the LOCATIONCHANGE events, but it caused the Excel main window to lag when dragging. + // (This drag issue seems to have been introduced with an Office update around November 2022) + // So until we can find a workaround for that (perhaps a timer would work fine for this), we decided not to bother + // with tracking the tooltip position (we still update it as soon as the Excel main window moving ends). + // We still need to watch the LOCATIONCHANGE events, otherwise the tooltip is not shown at all in some cases. + // To workaround the Excel main window lagging, we unhook from LOCATIONCHANGE upon encountering EVENT_SYSTEM_MOVESIZESTART + // and then hook again upon encountering EVENT_SYSTEM_MOVESIZEEND (see UnhookFromLocationChangeUponDraggingExcelMainWindow). public WindowLocationWatcher(IntPtr hWnd, SynchronizationContext syncContextAuto, SynchronizationContext syncContextMain) { _hWnd = hWnd; _syncContextAuto = syncContextAuto; _syncContextMain = syncContextMain; - _windowLocationChangeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZESTART, WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZEEND, _syncContextAuto, syncContextMain, _hWnd); - _windowLocationChangeHook.WinEventReceived += _windowLocationChangeHook_WinEventReceived; + _windowMoveSizeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZESTART, WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZEEND, _syncContextAuto, syncContextMain, _hWnd); + _windowMoveSizeHook.WinEventReceived += _windowMoveSizeHook_WinEventReceived; + + SetUpLocationChangeEventListener(); } - void _windowLocationChangeHook_WinEventReceived(object sender, WinEventHook.WinEventArgs winEventArgs) + void SetUpLocationChangeEventListener() + { + + _locationChangeEventHook = new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE, WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE, _syncContextAuto, _syncContextMain, IntPtr.Zero); + _locationChangeEventHook.WinEventReceived += _windowMoveSizeHook_WinEventReceived; + } + + // This allows us to temporarily stop listening to EVENT_OBJECT_LOCATIONCHANGE events when the user is dragging the Excel main window. + // Otherwise we are going to bump into https://github.com/Excel-DNA/IntelliSense/issues/123. The rest of the time we need to stay + // hooked to EVENT_OBJECT_LOCATIONCHANGE for IntelliSense to work correctly (see https://github.com/Excel-DNA/IntelliSense/issues/124). + void UnhookFromLocationChangeUponDraggingExcelMainWindow(WinEventHook.WinEventArgs e) + { + if (e.EventType == WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZESTART) + { + _syncContextMain.Post(_ => _locationChangeEventHook?.Dispose(), null); + } + + if (e.EventType == WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZEEND) + { + _syncContextMain.Post(_ => SetUpLocationChangeEventListener(), null); + } + } + + void _windowMoveSizeHook_WinEventReceived(object sender, WinEventHook.WinEventArgs winEventArgs) { #if DEBUG Logger.WinEvents.Verbose($"{winEventArgs.EventType} - Window {winEventArgs.WindowHandle:X} ({Win32Helper.GetClassName(winEventArgs.WindowHandle)} - Object/Child {winEventArgs.ObjectId} / {winEventArgs.ChildId} - Thread {winEventArgs.EventThreadId} at {winEventArgs.EventTimeMs}"); #endif + + UnhookFromLocationChangeUponDraggingExcelMainWindow(winEventArgs); + LocationChanged?.Invoke(this, EventArgs.Empty); } @@ -38,10 +74,16 @@ void _windowLocationChangeHook_WinEventReceived(object sender, WinEventHook.WinE public void Dispose() { Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1); - if (_windowLocationChangeHook != null) + if (_windowMoveSizeHook != null) + { + _windowMoveSizeHook.Dispose(); + _windowMoveSizeHook = null; + } + + if (_locationChangeEventHook != null) { - _windowLocationChangeHook.Dispose(); - _windowLocationChangeHook = null; + _locationChangeEventHook.Dispose(); + _locationChangeEventHook = null; } } } diff --git a/Source/ExcelDna.IntelliSense/UIMonitor/WindowWatcher.cs b/Source/ExcelDna.IntelliSense/UIMonitor/WindowWatcher.cs index a8bfca7..8388ba1 100644 --- a/Source/ExcelDna.IntelliSense/UIMonitor/WindowWatcher.cs +++ b/Source/ExcelDna.IntelliSense/UIMonitor/WindowWatcher.cs @@ -69,6 +69,10 @@ internal WindowChangedEventArgs(IntPtr windowHandle, WinEventHook.WinEvent winEv case WinEventHook.WinEvent.EVENT_OBJECT_FOCUS: Type = ChangeType.Focus; break; + case WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE: + Type = ChangeType.LocationChange; + ObjectId = ChangeObjectId.Caret; + break; case WinEventHook.WinEvent.EVENT_SYSTEM_MOVESIZEEND: Type = ChangeType.LocationChange; break; @@ -113,6 +117,9 @@ internal WindowChangedEventArgs(IntPtr windowHandle, WinEventHook.WinEvent winEv const string _nuiDialogClass = "NUIDialog"; const string _selectDataSourceTitle = "Select Data Source"; // TODO: How does localization work? + readonly SynchronizationContext _syncContextAuto; + readonly SynchronizationContext _syncContextMain; + List _windowStateChangeHooks = new List(); // These track keyboard focus for Windows in the Excel process @@ -134,6 +141,9 @@ internal WindowChangedEventArgs(IntPtr windowHandle, WinEventHook.WinEvent winEv public WindowWatcher(SynchronizationContext syncContextAuto, SynchronizationContext syncContextMain) { + _syncContextAuto = syncContextAuto; + _syncContextMain = syncContextMain; + #pragma warning disable CS0618 // Type or member is obsolete (GetCurrentThreadId) - But for debugging we want to monitor this anyway // Debug.Print($"### WindowWatcher created on thread: Managed {Thread.CurrentThread.ManagedThreadId}, Native {AppDomain.GetCurrentThreadId()}"); #pragma warning restore CS0618 // Type or member is obsolete @@ -151,8 +161,6 @@ public WindowWatcher(SynchronizationContext syncContextAuto, SynchronizationCont // EVENT_OBJECT_SELECTIONREMOVE // EVENT_OBJECT_SELECTIONWITHIN // EVENT_OBJECT_STATECHANGE (0x800A = 32778) - // NB: Including the next event 'EVENT_OBJECT_LOCATIONCHANGE (0x800B = 32779)' will cause the Excel main window to lag when dragging. - // This drag issue seems to have been introduced with an Office update around November 2022. _windowStateChangeHooks.Add(new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_CREATE, WinEventHook.WinEvent.EVENT_OBJECT_STATECHANGE, syncContextAuto, syncContextMain, IntPtr.Zero)); _windowStateChangeHooks.Add(new WinEventHook(WinEventHook.WinEvent.EVENT_SYSTEM_CAPTURESTART, WinEventHook.WinEvent.EVENT_SYSTEM_CAPTURESTART, syncContextAuto, syncContextMain, IntPtr.Zero)); @@ -220,6 +228,7 @@ void _windowStateChangeHook_WinEventReceived(object sender, WinEventHook.WinEven { Debug.Fail("WinEvent with window 0!?"); } + if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS) { // Might raise change event for Unfocus