void UpdateEditState(bool moveOnly = false) { _syncContext.Post(delegate { // TODO: This is not right yet - the list box might have the focus... AutomationElement focused; try { focused = AutomationElement.FocusedElement; } catch (ArgumentException aex) { Debug.Print("!!! ERROR: Failed to get Focused Element: " + aex.ToString()); // Not sure why I get this - sometimes with startup screen return; } if (_formulaBar != null && _formulaBar.Equals(focused)) { EditWindowBounds = (Rect)_formulaBar.GetCurrentPropertyValue(AutomationElement.BoundingRectangleProperty); IntPtr hwnd = (IntPtr)(int)_formulaBar.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty); var pt = Win32Helper.GetClientCursorPos(hwnd); CaretPosition = new Point(pt.X, pt.Y); } else if (_inCellEdit != null && _inCellEdit.Equals(focused)) { EditWindowBounds = (Rect)_inCellEdit.GetCurrentPropertyValue(AutomationElement.BoundingRectangleProperty); IntPtr hwnd = (IntPtr)(int)_inCellEdit.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty); var pt = Win32Helper.GetClientCursorPos(hwnd); CaretPosition = new Point(pt.X, pt.Y); } else { // CurrentFormula = null; CurrentPrefix = null; Debug.Print("Don't have a focused text box to update."); } // As long as we have an InCellEdit, we are editing the formula... IsEditingFormula = (_inCellEdit != null); // TODO: Smarter notification...? StateChanged(this, new StateChangeEventArgs(moveOnly ? StateChangeTypeEnum.Move : StateChangeTypeEnum.Undefined)); }, null); }
// 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; } }
readonly WinEventDelegate _handleWinEventDelegate; // Ensures delegate that we pass to SetWinEventHook is not GC'd public WinEventHook(WinEvent eventMin, WinEvent eventMax, SynchronizationContext syncContextAuto, IntPtr hWndFilterOrZero) { if (syncContextAuto == null) { throw new ArgumentNullException(nameof(syncContextAuto)); } _syncContextAuto = syncContextAuto; _hWndFilterOrZero = hWndFilterOrZero; var xllModuleHandle = Win32Helper.GetXllModuleHandle(); var excelProcessId = Win32Helper.GetExcelProcessId(); _handleWinEventDelegate = HandleWinEvent; _hWinEventHook = SetWinEventHook(eventMin, eventMax, xllModuleHandle, _handleWinEventDelegate, excelProcessId, 0, SetWinEventHookFlags.WINEVENT_INCONTEXT); if (_hWinEventHook == IntPtr.Zero) { Logger.WinEvents.Error("SetWinEventHook failed"); // Is SetLastError used? - SetWinEventHook documentation does not indicate so throw new Win32Exception("SetWinEventHook failed"); } Logger.WinEvents.Info($"SetWinEventHook success on thread {Thread.CurrentThread.ManagedThreadId}"); }
void UpdateSelectedItem() { if (_hwndPopupList == IntPtr.Zero) { Logger.WindowWatcher.Verbose($"PopupList UpdateSelectedItem ignored: PopupList is null"); return; } if (!IsVisible) { if (_selectedItemIndex == -1 && SelectedItemText == string.Empty && SelectedItemBounds == Rect.Empty) { // Don't change anything, or fire an updated event return; } // Set to the way things should be when not visible, and fire an updated event _selectedItemIndex = -1; SelectedItemText = string.Empty; SelectedItemBounds = Rect.Empty; ListBounds = Rect.Empty; } else { string text; Rect itemBounds; var hwndListView = Win32Helper.GetFirstChildWindow(_hwndPopupList); ListBounds = Win32Helper.GetWindowBounds(_hwndPopupList); _selectedItemIndex = Win32Helper.GetListViewSelectedItemInfo(hwndListView, out text, out itemBounds); itemBounds.Offset(ListBounds.Left, ListBounds.Top); SelectedItemBounds = itemBounds; SelectedItemText = text; } OnSelectedItemChanged(); }
// Switches to our Automation thread, updates current state and raises StateChanged event void UpdateEditState(bool moveOnly = false) { Logger.WindowWatcher.Verbose("> FormulaEdit UpdateEditState - Posted"); _syncContextAuto.Post(moveOnlyObj => { Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState - Focus: {_formulaEditFocus}"); //// TODO: This is not right yet - the list box might have the focus... ? //AutomationElement focused; //try //{ // focused = AutomationElement.FocusedElement; //} //catch (ArgumentException aex) //{ // Debug.Print($"!!! ERROR: Failed to get Focused Element: {aex}"); // // Not sure why I get this - sometimes with startup screen // return; //} AutomationElement focusedEdit = null; bool prefixChanged = false; if (_formulaEditFocus == FormulaEditFocus.FormulaBar) { focusedEdit = _formulaBar; } else if (_formulaEditFocus == FormulaEditFocus.InCellEdit) { focusedEdit = _inCellEdit; } else { // Neither have the focus, so we don't update anything Logger.WindowWatcher.Verbose("FormulaEdit UpdateEditState End formula editing"); CurrentPrefix = null; if (IsEditingFormula) { UninstallLocationMonitor(); } IsEditingFormula = false; prefixChanged = true; // Debug.Print("Don't have a focused text box to update."); } if (focusedEdit != null) { EditWindowBounds = (Rect)focusedEdit.GetCurrentPropertyValue(AutomationElement.BoundingRectangleProperty); IntPtr hwnd = (IntPtr)(int)focusedEdit.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty); var pt = Win32Helper.GetClientCursorPos(hwnd); if (!IsEditingFormula) { InstallLocationMonitor(GetTopLevelWindow(focusedEdit)); } IsEditingFormula = true; var newPrefix = XlCall.GetFormulaEditPrefix(); // What thread do we have to use here ...? if (CurrentPrefix != newPrefix) { CurrentPrefix = newPrefix; prefixChanged = true; } Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState Formula editing: CurrentPrefix {CurrentPrefix}, EditWindowBounds: {EditWindowBounds}"); } // TODO: Smarter notification...? if ((bool)moveOnlyObj && !prefixChanged) { StateChanged?.Invoke(this, new StateChangeEventArgs(StateChangeType.Move)); } else { OnStateChanged(new StateChangeEventArgs(StateChangeType.Multiple)); } }, moveOnly); }
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); }
// Runs on our Automation thread (via SyncContext passed into the constructor) // CONSIDER: Performance impact of logging (including GetClassName) here void OnWinEventReceived(object winEventArgsObj) { var winEventArgs = (WinEventArgs)winEventArgsObj; #if DEBUG if (winEventArgs.ObjectId != WinEventObjectId.OBJID_CURSOR) { 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 WinEventReceived?.Invoke(this, winEventArgs); }
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; } }
// 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) { 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, e.ObjectId)); break; case _inCellEditClass: InCellEditWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType, e.ObjectId)); break; case _formulaBarClass: FormulaBarWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType, e.ObjectId)); break; case _excelToolTipClass: ExcelToolTipWindowChanged?.Invoke(this, new WindowChangedEventArgs(e.WindowHandle, e.EventType, e.ObjectId)); 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 UpdateEditStateImpl(bool moveOnly = false) { Logger.WindowWatcher.Verbose($"> FormulaEdit UpdateEditState - Thread {Thread.CurrentThread.ManagedThreadId}"); Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState - Focus: {_formulaEditFocus} Window: {(_formulaEditFocus == FormulaEditFocus.FormulaBar ? _hwndFormulaBar : _hwndInCellEdit)}"); IntPtr hwnd = IntPtr.Zero; bool prefixChanged = false; if (_formulaEditFocus == FormulaEditFocus.FormulaBar) { hwnd = _hwndFormulaBar; } else if (_formulaEditFocus == FormulaEditFocus.InCellEdit) { hwnd = _hwndInCellEdit; } else { // Neither have the focus, so we don't update anything Logger.WindowWatcher.Verbose("FormulaEdit UpdateEditState End formula editing"); CurrentPrefix = null; if (IsEditingFormula) { UninstallLocationMonitor(); } IsEditingFormula = false; prefixChanged = true; // Debug.Print("#### FormulaEditWatcher - No Window " + Environment.StackTrace); } if (hwnd != IntPtr.Zero) { EditWindowBounds = Win32Helper.GetWindowBounds(hwnd); if (!IsEditingFormula) { IntPtr hwndTopLevel = Win32Helper.GetRootAncestor(hwnd); InstallLocationMonitor(hwndTopLevel); IsEditingFormula = true; } var newPrefix = XlCall.GetFormulaEditPrefix(); // What thread do we have to use here ...? if (CurrentPrefix != newPrefix) { CurrentPrefix = newPrefix; prefixChanged = true; } Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState Formula editing: CurrentPrefix {CurrentPrefix}, EditWindowBounds: {EditWindowBounds}"); } // TODO: Smarter (or more direct) notification...? if (moveOnly && !prefixChanged) { StateChanged?.Invoke(this, new StateChangeEventArgs(StateChangeType.Move)); } else { OnStateChanged(StateChangeType.Multiple); } }
void _windowLocationChangeHook_WinEventReceived(object sender, WinEventHook.WinEventArgs winEventArgs) { #if DEBUG //Logger.WinEvents.Verbose(string.Format("{winEventArgs.EventType} - Window {winEventArgs.WindowHandle:X} ({Win32Helper.GetClassName(winEventArgs.WindowHandle)} - Object/Child {winEventArgs.ObjectId} / {winEventArgs.ChildId} - Thread {winEventArgs.EventThreadId} at {winEventArgs.EventTimeMs}"); Logger.WinEvents.Verbose(string.Format("{0} - Window {1:X} ({2} - Object/Child {3} / {4} - Thread {5} at {6}", winEventArgs.EventType, winEventArgs.WindowHandle, Win32Helper.GetClassName(winEventArgs.WindowHandle), winEventArgs.ObjectId, winEventArgs.ChildId, winEventArgs.EventThreadId, winEventArgs.EventTimeMs)); #endif LocationChanged.Invoke(this, EventArgs.Empty); }
// Runs on our Automation thread (via SyncContext passed into the constructor) // CONSIDER: Performance impact of logging (including GetClassName) here void OnWinEventReceived(object winEventArgsObj) { var winEventArgs = (WinEventArgs)winEventArgsObj; #if DEBUG if (winEventArgs.ObjectId != WinEventObjectId.OBJID_CURSOR) { //Logger.WinEvents.Verbose(string.Format("{winEventArgs.EventType} - Window {winEventArgs.WindowHandle:X} ({Win32Helper.GetClassName(winEventArgs.WindowHandle)} - Object/Child {winEventArgs.ObjectId} / {winEventArgs.ChildId} - Thread {winEventArgs.EventThreadId} at {winEventArgs.EventTimeMs}"); Logger.WinEvents.Verbose(string.Format("{0} - Window {1:X} ({2} - Object/Child {3} / {4} - Thread {5} at {6}", winEventArgs.EventType, winEventArgs.WindowHandle, Win32Helper.GetClassName(winEventArgs.WindowHandle), winEventArgs.ObjectId, winEventArgs.ChildId, winEventArgs.EventThreadId, winEventArgs.EventTimeMs)); } #endif WinEventReceived.Invoke(this, winEventArgs); }
// Runs on our Automation thread (via SyncContext passed into the constructor) // CONSIDER: Performance impact of logging (including GetClassName) here void OnWinEventReceived(object winEventArgsObj) { var winEventArgs = (WinEventArgs)winEventArgsObj; if (winEventArgs.ObjectId == WinEventObjectId.OBJID_CURSOR) { return; } #if DEBUG Logger.WinEvents.Verbose($"{winEventArgs.EventType} - Window {winEventArgs.WindowHandle:X} {(winEventArgs.WindowHandle != IntPtr.Zero ? Win32Helper.GetClassName(winEventArgs.WindowHandle) : "")} - Object/Child {winEventArgs.ObjectId} / {winEventArgs.ChildId} - Thread {winEventArgs.EventThreadId} at {winEventArgs.EventTimeMs}"); #endif WinEventReceived?.Invoke(this, winEventArgs); }