// 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"); } } }
// 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; } } }