// NOTE: An earlier attempt was to monitor LOCATIONCHANGE only between EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND // 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. public WindowLocationWatcher(IntPtr hWnd, SynchronizationContext syncContextAuto) { _hWnd = hWnd; _syncContextAuto = syncContextAuto; _windowLocationChangeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE, WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE, _syncContextAuto, _hWnd); _windowLocationChangeHook.WinEventReceived += _windowLocationChangeHook_WinEventReceived; }
void _windowLocationChangeHook_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 LocationChanged?.Invoke(this, EventArgs.Empty); }
public void Dispose() { if (_windowLocationChangeHook != null) { _windowLocationChangeHook.Dispose(); _windowLocationChangeHook = null; } }
public void Dispose() { if (_windowStateChangeHook != null) { _windowStateChangeHook.Stop(); _windowStateChangeHook = null; } }
public event EventHandler PopupListListChanged = delegate { }; // Might start off with nothing. Changes at most once. public WindowWatcher(string xllName) { Debug.Print("### WindowWatcher created on thread: " + Thread.CurrentThread.ManagedThreadId); // Using WinEvents instead of Automation so that we can watch top-level changes, but only from the right process. _windowStateChangeHook = new WinEventHook(WindowStateChange, WinEventHook.WinEvent.EVENT_OBJECT_CREATE, WinEventHook.WinEvent.EVENT_OBJECT_FOCUS, xllName); }
// Must run on the main thread public void Dispose() { Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1); if (_windowStateChangeHook != null) { _windowStateChangeHook.Dispose(); _windowStateChangeHook = null; } }
// public event EventHandler<WindowChangedEventArgs> SelectDataSourceWindowChanged; public WindowWatcher(SynchronizationContext syncContextAuto) { #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 // Using WinEvents instead of Automation so that we can watch top-level window changes, but only from the right (current Excel) process. // TODO: We need to dramatically reduce the number of events we grab here... _windowStateChangeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_CREATE, WinEventHook.WinEvent.EVENT_OBJECT_FOCUS, syncContextAuto, IntPtr.Zero); // _windowStateChangeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_CREATE, WinEventHook.WinEvent.EVENT_OBJECT_END, syncContextAuto); _windowStateChangeHook.WinEventReceived += _windowStateChangeHook_WinEventReceived; }
// Runs on our automation thread void InstallEventHandlers() { Logger.WindowWatcher.Verbose(string.Format("PopupList Installing event handlers on thread {0}", Thread.CurrentThread.ManagedThreadId)); try { // TODO: Clean up var hwndListView = Win32Helper.GetFirstChildWindow(_hwndPopupList); _selectionChangeHook = new WinEventHook(WinEventHook.WinEvent.EVENT_OBJECT_SELECTION, WinEventHook.WinEvent.EVENT_OBJECT_SELECTION, _syncContextAuto, _syncContextMain, hwndListView); _selectionChangeHook.WinEventReceived += _selectionChangeHook_WinEventReceived; Logger.WindowWatcher.Verbose("PopupList selection event handler added"); } catch (Exception ex) { // Probably no longer visible Logger.WindowWatcher.Warn(string.Format("PopupList event handler error {0}", ex)); _hwndPopupList = IntPtr.Zero; IsVisible = false; } }
internal static bool IsSupportedWinEvent(WinEventHook.WinEvent winEvent) { return winEvent == WinEventHook.WinEvent.EVENT_OBJECT_CREATE || winEvent == WinEventHook.WinEvent.EVENT_OBJECT_DESTROY || winEvent == WinEventHook.WinEvent.EVENT_OBJECT_SHOW || winEvent == WinEventHook.WinEvent.EVENT_OBJECT_HIDE || winEvent == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS || winEvent == WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE; // Only for the on-demand hook }
// This runs on the Automation thread, via SyncContextAuto passed in to WinEventHook when we created this WindowWatcher // CONSIDER: We would be able to run all the watcher updates from WinEvents, including Location and Selection changes, // but since WinEvents have no hwnd filter, UIAutomation events might be more efficient. // CONSIDER: Performance optimisation would keep a list of window handles we know about, preventing the class name check every time void _windowStateChangeHook_WinEventReceived(object sender, WinEventHook.WinEventArgs e) { if (!WindowChangedEventArgs.IsSupportedWinEvent(e.EventType)) return; var className = Win32Helper.GetClassName(e.WindowHandle); if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS) { // Might raise change event for Unfocus if (!UpdateFocus(e.WindowHandle, className)) { // We already have the right focus return; } } // Debug.Print("### Thread receiving WindowStateChange: " + Thread.CurrentThread.ManagedThreadId); switch (className) { //case _sheetWindowClass: // if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW) // { // // Maybe a new workbook is on top... // // Note that there is also an EVENT_OBJECT_PARENTCHANGE (which we are not subscribing to at the moment // } // break; case _popupListClass: PopupListWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType)); break; case _inCellEditClass: InCellEditWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType)); break; case _formulaBarClass: FormulaBarWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType)); break; case _excelToolTipClass: ExcelToolTipWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType)); break; //case _nuiDialogClass: // // Debug.Print($"SelectDataSource {_selectDataSourceClass} Window update: {e.WindowHandle:X}, EventType: {e.EventType}, idChild: {e.ChildId}"); // if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_CREATE) // { // // Get the name of this window - maybe ours or some other NUIDialog // var windowTitle = Win32Helper.GetText(e.WindowHandle); // if (windowTitle.Equals(_selectDataSourceTitle, StringComparison.OrdinalIgnoreCase)) // { // SelectDataSourceWindow = e.WindowHandle; // SelectDataSourceWindowChanged?.Invoke(this, // new WindowChangedEventArgs { Type = WindowChangedEventArgs.ChangeType.Create }); // } // } // else if (SelectDataSourceWindow == e.WindowHandle && e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW) // { // IsSelectDataSourceWindowVisible = true; // SelectDataSourceWindowChanged?.Invoke(this, // new WindowChangedEventArgs { Type = WindowChangedEventArgs.ChangeType.Create }); // } // else if (SelectDataSourceWindow == e.WindowHandle && e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_HIDE) // { // IsSelectDataSourceWindowVisible = false; // SelectDataSourceWindowChanged?.Invoke(this, new WindowChangedEventArgs { Type = WindowChangedEventArgs.ChangeType.Hide }); // } // else if (SelectDataSourceWindow == e.WindowHandle && e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_DESTROY) // { // IsSelectDataSourceWindowVisible = false; // SelectDataSourceWindow = IntPtr.Zero; // SelectDataSourceWindowChanged?.Invoke(this, new WindowChangedEventArgs { Type = WindowChangedEventArgs.ChangeType.Destroy }); // } // break; default: //InCellEditWindowChanged(this, EventArgs.Empty); break; } }
void WindowStateChange(IntPtr hWinEventHook, WinEventHook.WinEvent eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { // This runs on the main application thread. Debug.Print("### Thread receiving WindowStateChange: " + Thread.CurrentThread.ManagedThreadId); switch (Win32Helper.GetClassName(hWnd)) { case _classMain: //if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS || // eventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW) { Debug.Print("MainWindow update: " + hWnd.ToString("X") + ", EventType: " + eventType); UpdateMainWindow(hWnd); } if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_DESTROY) { } break; case _classPopupList: if (PopupListWindow == IntPtr.Zero && (eventType == WinEventHook.WinEvent.EVENT_OBJECT_CREATE || eventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW)) { PopupListWindow = hWnd; PopupListWindowChanged(this, EventArgs.Empty); } else { Debug.Assert(PopupListWindow == hWnd); } break; case _classInCellEdit: Debug.Print("InCell Window update: " + hWnd.ToString("X") + ", EventType: " + eventType + ", idChild: " + idChild); if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_CREATE || eventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW) { InCellEditWindow = hWnd; InCellEditWindowChanged(this, EventArgs.Empty); } else if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_HIDE) { InCellEditWindow = IntPtr.Zero; InCellEditWindowChanged(this, EventArgs.Empty); } else if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS) { InCellEditFocused(this, EventArgs.Empty); } break; case _classFormulaBar: Debug.Print("FormulaBar Window update: " + hWnd.ToString("X") + ", EventType: " + eventType + ", idChild: " + idChild); if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_CREATE || eventType == WinEventHook.WinEvent.EVENT_OBJECT_SHOW) { FormulaBarWindow = hWnd; FormulaBarWindowChanged(this, EventArgs.Empty); } else if (eventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS) { FormulaBarFocused(this, EventArgs.Empty); } break; default: //InCellEditWindowChanged(this, EventArgs.Empty); break; } }
internal WindowChangedEventArgs(IntPtr windowHandle, WinEventHook.WinEvent winEvent) { WindowHandle = windowHandle; switch (winEvent) { case WinEventHook.WinEvent.EVENT_OBJECT_CREATE: Type = ChangeType.Create; break; case WinEventHook.WinEvent.EVENT_OBJECT_DESTROY: Type = ChangeType.Destroy; break; case WinEventHook.WinEvent.EVENT_OBJECT_SHOW: Type = ChangeType.Show; break; case WinEventHook.WinEvent.EVENT_OBJECT_HIDE: Type = ChangeType.Hide; break; case WinEventHook.WinEvent.EVENT_OBJECT_FOCUS: Type = ChangeType.Focus; break; case WinEventHook.WinEvent.EVENT_OBJECT_LOCATIONCHANGE: Type = ChangeType.LocationChange; break; default: throw new ArgumentException("Unexpected WinEvent type", nameof(winEvent)); } }