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