//------------------------------------------------------ // // Patterns Implementation // //------------------------------------------------------ #region ProxyHwnd Methods // ------------------------------------------------------ // // Internal Methods // // ------------------------------------------------------ // Advises proxy that an event has been added. // Maps the Automation Events into WinEvents and add those to the list of WinEvents notification hooks internal virtual void AdviseEventAdded(AutomationEvent eventId, AutomationProperty [] aidProps) { // No RawElementBase creation callback, exit from here if (_createOnEvent == null) { return; } int cEvents = 0; WinEventTracker.EvtIdProperty [] aEvents; // Gets an Array of WinEvents to trap on a per window handle basis if (eventId == AutomationElement.AutomationPropertyChangedEvent) { aEvents = PropertyToWinEvent(aidProps, out cEvents); } else { aEvents = EventToWinEvent(eventId, out cEvents); } // If we have WinEvents to trap, add those to the list of WinEvent // notification list if (cEvents > 0) { WinEventTracker.AddToNotificationList(_hwnd, _createOnEvent, aEvents, cEvents); } }
// a client has stopped listening for an event on object(s) in the specified window. internal void AdviseEventRemoved(IntPtr hwnd, AutomationEvent eventId, AutomationProperty[] properties) { // note: we don't need to worry about removing entries from our tables when windows are destroyed //(EVENT_OBJECT_DESTROY with OBJID_WINDOW) because UIA already watches for this event and calls AdviseEventRemoved. // use a lock to update our tables in one atomic operation. lock (this) { // we should have a 2-nd level table for this hwnd... if (_hwndTable.ContainsKey(hwnd)) { Hashtable eventTable = (Hashtable)(_hwndTable[hwnd]); // for the single event or each of possibly multiple properties decrement the reference count. foreach (AutomationIdentifier key in EventKeys(eventId, properties)) { // we should have an entry in the 2-nd level table for this event or property... if (eventTable.ContainsKey(key)) { // decrement the reference count int refcount = (int)eventTable[key] - 1; Debug.Assert(refcount >= 0); if (refcount > 0) { eventTable[key] = refcount; } else { // if the refcount has gone to zero then remove this entry from the 2-nd level table. eventTable.Remove(key); // if this window doesn't have any more advises then remove it's entry from the top-level table if (eventTable.Count == 0) { _hwndTable.Remove(hwnd); // if there are no more advises then we can stop listening for WinEvents if (_hwndTable.Count == 0) { //Debug.WriteLine("Stop listening for WinEvents.", "NativeMsaaProxy"); WinEventTracker.GetCallbackQueue().PostSyncWorkItem(new QueueItem.MSAAWinEventItem(StopListening)); } } } } else { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "ERROR: AdviseEventRemoved called for {0} and event/property {1} without matching AdviseEventAdded.", hwnd, key), "NativeMsaaProxy"); } } } else { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "ERROR: AdviseEventRemoved called for {0} without matching AdviseEventAdded.", hwnd), "NativeMsaaProxy"); } } }
internal override void AdviseEventRemoved( AutomationEvent eventId, AutomationProperty[] aidProps) { if (eventId == AutomationElementIdentifiers.AutomationPropertyChangedEvent && aidProps.Length > 0 && aidProps[0] == ScrollPatternIdentifiers.HorizontalScrollPercentProperty) { IntPtr upDownHwnd = GetUpDownHwnd(); if (upDownHwnd != IntPtr.Zero) { WinEventTracker.RemoveToNotificationList( upDownHwnd, _upDownEvents, null, 1); } } base.AdviseEventRemoved(eventId, aidProps); }
internal override void AdviseEventRemoved(AutomationEvent eventId, AutomationProperty[] aidProps) { base.AdviseEventRemoved(eventId, aidProps); // For now, ToolTips only raise ToolTip-specific events when they close if (eventId != AutomationElement.ToolTipClosedEvent) { return; } if (_listenerCount > 0) { // decrement the event counter --_listenerCount; WinEventTracker.RemoveToNotificationList(IntPtr.Zero, _toolTipEventIds, new WinEventTracker.ProxyRaiseEvents(OnToolTipEvents), _toolTipEventIds.Length); } }
// a client is listening for an event on object(s) in the specified window. internal void AdviseEventAdded(IntPtr hwnd, AutomationEvent eventId, AutomationProperty[] properties) { // When you start listening for events you listen for all events and then // when you get the event you filter by any hwnd that got advised. But you have enough // information when you get advised to listen only for in process where that hwnd resides. // The SetWinEventHook api takes a process id, you could get the process id from the hwnd // and limit the number of event you have to process. // we have a two-level table structure. the top-level table maps an hwnd to another table. // the second-level table maps an event (or a property in the case of property-changed-event) // in that hwnd to a reference count of the number of clients listening for that event. // use a lock to update our tables in one atomic operation. lock (this) { // if we aren't listening for WinEvents then start listening. if (_hwndTable.Count == 0) { //Debug.WriteLine("Starting listening for WinEvents.", "NativeMsaaProxy"); // Posting an item to the queue to start listening for WinEvents. This makes sure it is done on the proper thread // Notably the same thread WinEventTracker uses, which guarantees the thread that SetWinEventHook is called on is // actively pumping messages, which is required for SetWinEventHook to function properly. WinEventTracker.GetCallbackQueue().PostSyncWorkItem(new QueueItem.MSAAWinEventItem(StartListening)); } // if we already have a 2-nd level table for this hwnd then simply update the 2nd-level table. // otherwise we need to create a 2-nd level table. Hashtable eventTable; if (_hwndTable.ContainsKey(hwnd)) { eventTable = (Hashtable)(_hwndTable[hwnd]); } else { eventTable = new Hashtable(); _hwndTable[hwnd] = eventTable; } // for the single event or each of possibly multiple properties increment the reference counter. foreach (AutomationIdentifier key in EventKeys(eventId, properties)) { eventTable[key] = eventTable.ContainsKey(key) ? (int)eventTable[key] + 1 : 1; } } }
internal override void AdviseEventAdded( AutomationEvent eventId, AutomationProperty[] aidProps) { if (eventId == AutomationElementIdentifiers.AutomationPropertyChangedEvent && aidProps.Length > 0 && aidProps[0] == ScrollPatternIdentifiers.HorizontalScrollPercentProperty) { IntPtr upDownHwnd = GetUpDownHwnd(); if (upDownHwnd != IntPtr.Zero) { // Register for UpDown ValueChange WinEvents, which will be // translated to scrolling events for the tab control. WinEventTracker.AddToNotificationList( upDownHwnd, new WinEventTracker.ProxyRaiseEvents(UpDownControlRaiseEvents), _upDownEvents, 1); } } base.AdviseEventAdded(eventId, aidProps); }
internal override void AdviseEventAdded(AutomationEvent eventId, AutomationProperty[] aidProps) { base.AdviseEventAdded(eventId, aidProps); // If the framework is advising for ToolTipOpenedEvent then go ahead and raise this event now // since the WinEvent we would listen to (usually EVENT_OBJECT_SHOW) has already occurrred // (it is why Advise is being called). No other action is necessary because when this ToolTip // goes away, AdviseEventRemoved is going to be called. In other words, this proxy gets // created when the ToolTip opens and thrown away when it closes so no need to keep state // that we want to listen for more SHOWS or CREATES - there's always one for any one instance. if (eventId == AutomationElement.ToolTipOpenedEvent) { AutomationEventArgs e = new AutomationEventArgs(AutomationElement.ToolTipOpenedEvent); AutomationInteropProvider.RaiseAutomationEvent(AutomationElement.ToolTipOpenedEvent, this, e); } else if (eventId == AutomationElement.ToolTipClosedEvent) { // subscribe to ToolTip specific events, keeping track of how many times the event has been added WinEventTracker.AddToNotificationList(IntPtr.Zero, new WinEventTracker.ProxyRaiseEvents(OnToolTipEvents), _toolTipEventIds, _toolTipEventIds.Length); _listenerCount++; } }
internal override void AdviseEventRemoved(AutomationEvent eventId, AutomationProperty [] aidProps) { // remove combo-related events base.AdviseEventRemoved(eventId, aidProps); NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize); if (GetComboInfo(_hwnd, ref cbInfo)) { // remove edit and list specific events that got remapped into combo's events if (eventId == AutomationElement.AutomationPropertyChangedEvent) { // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST are still editable. if (cbInfo.hwndItem != IntPtr.Zero && IsEditableCombo()) { // un-subscribe from edit-specific notifications // ValueAsString, ValueAsObject, IsReadOnly // create array containing events from which user wants to unsubscribe WinEventTracker.EvtIdProperty [] editPortionEvents; int counter; CreateEditPortionEvents(out editPortionEvents, out counter, aidProps); if (counter > 0) { WinEventTracker.RemoveToNotificationList(cbInfo.hwndItem, editPortionEvents, null, counter); } } } // Need to also remove the advise from the list portions of the combobox. if (cbInfo.hwndList != IntPtr.Zero) { WindowsListBox listbox = new WindowsListBox(cbInfo.hwndList, this, 0, true); listbox.AdviseEventRemoved(eventId, aidProps); } } }
// override the default implementation so we can handle the WinEvents that are send to the edit // portion of ComboBox (Combo proxy will hide edit portion from the user, but will take care of all // the features/functionality that Edit presents) and some(show, hide) events that are send to the List portion of combo // In both cases this events will be presented to the user as Combo's LE events internal override void AdviseEventAdded(AutomationEvent eventId, AutomationProperty [] aidProps) { // call the base class implementation first to add combo-specific things and register combo specific callback base.AdviseEventAdded(eventId, aidProps); NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize); if (GetComboInfo(_hwnd, ref cbInfo)) { if (eventId == AutomationElement.AutomationPropertyChangedEvent) { // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST are still editable. if (cbInfo.hwndItem != IntPtr.Zero && IsEditableCombo()) { // subscribe to edit-specific notifications, that would be presented as combo le event // ValueAsString, ValueAsObject, IsReadOnly // create array containing events that user is interested in WinEventTracker.EvtIdProperty [] editPortionEvents; int counter; CreateEditPortionEvents(out editPortionEvents, out counter, aidProps); if (counter > 0) { WinEventTracker.AddToNotificationList(cbInfo.hwndItem, new WinEventTracker.ProxyRaiseEvents(EditPortionEvents), editPortionEvents, counter); } } } // Need to also advise the list portions of the combobox so that it can raise events. if (cbInfo.hwndList != IntPtr.Zero) { WindowsListBox listbox = new WindowsListBox(cbInfo.hwndList, this, 0, true); listbox.AdviseEventAdded(eventId, aidProps); } } }
private static void RaiseEventsOnClient(IntPtr hwnd, int eventId, object idProp, int idObject, int idChild) { ProxySimple el = null; WindowsListBox wlb = (WindowsListBox)WindowsListBox.Create(hwnd, 0); // Upon creation, a single selection Listbox can have no selection to start with // however once an item has been selection, the selection cannot be removed. // This WinEvent can only be received once, on the first selection. // Once the notification is received the notification handler is removed to not get it a // second time. if ((eventId == NativeMethods.EventObjectSelection || eventId == NativeMethods.EventObjectSelectionAdd) && (idProp as AutomationProperty) == SelectionPattern.IsSelectionRequiredProperty) { // This array must be kept in sync with the array in PropertyToWinEvent WinEventTracker.EvtIdProperty[] aEvtIdProperties = new WinEventTracker.EvtIdProperty[] { new WinEventTracker.EvtIdProperty(NativeMethods.EventObjectSelection, SelectionPattern.IsSelectionRequiredProperty) }; WinEventTracker.RemoveToNotificationList(hwnd, aEvtIdProperties, null, aEvtIdProperties.Length); el = wlb; } else if (eventId == NativeMethods.EventObjectSelection || eventId == NativeMethods.EventObjectSelectionRemove || eventId == NativeMethods.EventObjectSelectionAdd) { bool isMultipleSelection = wlb.IsMultipleSelection(); // User should send SelectionAdd for a Multiselect listbox but it sends instead // Selection. The code below fixes the if (eventId == NativeMethods.EventObjectSelection && isMultipleSelection && wlb.HasOtherSelections(idChild - 1)) { eventId = NativeMethods.EventObjectSelectionAdd; } // The spec says a ElementSelectionEvent should be fired when action causes only one // selection. if ((eventId == NativeMethods.EventObjectSelectionRemove || eventId == NativeMethods.EventObjectSelectionAdd) && isMultipleSelection && wlb.GetSelectionCount() == 1) { // The net result of the user action is that there is only one item selected in the // listbox, so change the event to an EventObjectSelected. idProp = SelectionItemPattern.ElementSelectedEvent; eventId = NativeMethods.EventObjectSelection; // Now need to find what item is selected. int selection = wlb.GetOtherSelection(idChild - 1); if (selection != NativeMethods.LB_ERR) { idChild = selection; } } el = wlb.CreateListboxItem(idChild - 1); } else { el = wlb; } // Special case for logical element change for listbox item if ((idProp as AutomationEvent) == AutomationElement.StructureChangedEvent && (eventId == NativeMethods.EventObjectDestroy || eventId == NativeMethods.EventObjectCreate)) { // Since children are referenced by position in the tree, addition and removal // of items leads to different results when asking properties for the same element // On removal, item + 1 is now item! // Use Children Invalidated to let the client knows that all the cached AutomationInteropProvider.RaiseStructureChangedEvent(wlb, new StructureChangedEventArgs(StructureChangeType.ChildrenInvalidated, wlb.MakeRuntimeId())); return; } if (el != null) { el.DispatchEvents(eventId, idProp, idObject, idChild); } }
// ------------------------------------------------------ // // Constructors // // ------------------------------------------------------ #region Constructors internal WinEventItem (ref WinEventTracker.EventHookParams hp, WinEventTracker.StartStopDelegate ssd) { _hp = hp; _ssd = ssd; }
// Return an array that contains combo's edit portion specific events // These events will be remapped as combo box events private static void CreateEditPortionEvents (out WinEventTracker.EvtIdProperty [] editPortionEvents, out int counter, AutomationProperty [] aidProps) { // count how many events to pass back for the edit part of combo int c = 0; foreach ( AutomationProperty p in aidProps ) { if ( p == ValuePattern.ValueProperty || p == ValuePattern.IsReadOnlyProperty ) { c++; } } if (c == 0) { editPortionEvents = null; counter = 0; return; } // allocate array with the number of events from above editPortionEvents = new WinEventTracker.EvtIdProperty[c]; c = 0; foreach ( AutomationProperty p in aidProps ) { if ( p == ValuePattern.ValueProperty || p == ValuePattern.IsReadOnlyProperty ) { editPortionEvents[c]._idProp = p; editPortionEvents[c]._evtId = (p == ValuePattern.ValueProperty) ? NativeMethods.EventObjectValueChange : NativeMethods.EventObjectStateChange; c++; } } counter = c; }